feat: update mongo doc

This commit is contained in:
王性驊 2025-11-12 16:17:23 +08:00
parent 3a3b3c99f0
commit 09c72c5167
78 changed files with 897 additions and 377 deletions

View File

@ -7,22 +7,33 @@ LDFLAGS := -s -w
VERSION="v1.0.0" VERSION="v1.0.0"
DOCKER_REPO="refactor-service" DOCKER_REPO="refactor-service"
# 默認目標
.DEFAULT_GOAL := help
# 顏色定義
GREEN := \033[0;32m
YELLOW := \033[0;33m
NC := \033[0m # No Color
help: ## 顯示幫助訊息
@echo "$(GREEN)可用命令:$(NC)"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(YELLOW)%-20s$(NC) %s\n", $$1, $$2}'
.PHONY: test .PHONY: test
test: # 進行測試 test: ## 進行測試
go test -v --cover ./... go test -v --cover ./...
.PHONY: gen-api .PHONY: gen-api
gen-api: # 產生 api gen-api: ## 產生 api
goctl api go -api ./generate/api/gateway.api -dir . -style go_zero goctl api go -api ./generate/api/gateway.api -dir . -style go_zero
.PHONY: gen-doc .PHONY: gen-doc
gen-doc: # 生成 Swagger 文檔 gen-doc: ## 生成 Swagger 文檔
# go-doc openapi --api ./generate/api/gateway.api --filename gateway.json --host dev-api.truheart.com.tw --basepath /api/v1 # go-doc openapi --api ./generate/api/gateway.api --filename gateway.json --host dev-api.truheart.com.tw --basepath /api/v1
go-doc -a generate/api/gateway.api -d ./ -f gateway -s openapi3.0 go-doc -a generate/api/gateway.api -d ./ -f gateway -s openapi3.0
.PHONY: mock-gen .PHONY: mock-gen
mock-gen: # 建立 mock 資料 mock-gen: ## 建立 mock 資料
mockgen -source=./pkg/member/domain/repository/account.go -destination=./pkg/member/mock/repository/account.go -package=mock mockgen -source=./pkg/member/domain/repository/account.go -destination=./pkg/member/mock/repository/account.go -package=mock
mockgen -source=./pkg/member/domain/repository/account_uid.go -destination=./pkg/member/mock/repository/account_uid.go -package=mock mockgen -source=./pkg/member/domain/repository/account_uid.go -destination=./pkg/member/mock/repository/account_uid.go -package=mock
mockgen -source=./pkg/member/domain/repository/auto_id.go -destination=./pkg/member/mock/repository/auto_id.go -package=mock mockgen -source=./pkg/member/domain/repository/auto_id.go -destination=./pkg/member/mock/repository/auto_id.go -package=mock
@ -45,48 +56,19 @@ mock-gen: # 建立 mock 資料
@echo "Generate mock files successfully" @echo "Generate mock files successfully"
.PHONY: fmt .PHONY: fmt
fmt: # 格式優化 fmt: ## 格式優化
$(GOFMT) -w $(GOFILES) $(GOFMT) -w $(GOFILES)
goimports -w ./ goimports -w ./
.PHONY: build
build: # 編譯專案
go build -ldflags "$(LDFLAGS)" -o bin/gateway cmd/gateway/main.go
.PHONY: run .PHONY: run
run: # 運行專案 run: ## 運行專案
go run cmd/gateway/main.go go run geteway.go
.PHONY: clean .PHONY: clean
clean: # 清理編譯文件 clean: ## 清理編譯文件
rm -rf bin/ rm -rf bin/
.PHONY: docker-build
docker-build: # 構建 Docker 映像
docker build -t $(DOCKER_REPO):$(VERSION) .
.PHONY: docker-run
docker-run: # 運行 Docker 容器
docker run -p 8888:8888 $(DOCKER_REPO):$(VERSION)
.PHONY: install .PHONY: install
install: # 安裝依賴 install: ## 安裝依賴
go mod tidy go mod tidy
go mod download go mod download
.PHONY: help
help: # 顯示幫助信息
@echo "Available commands:"
@echo " test - 運行測試"
@echo " gen-api - 產生 api"
@echo " gen-doc - 生成 Swagger 文檔"
@echo " mock-gen - 建立 mock 資料"
@echo " fmt - 格式化代碼"
@echo " build - 編譯專案"
@echo " run - 運行專案"
@echo " clean - 清理編譯文件"
@echo " docker-build - 構建 Docker 映像"
@echo " docker-run - 運行 Docker 容器"
@echo " install - 安裝依賴"
@echo " help - 顯示幫助信息"

View File

@ -7,7 +7,7 @@ type UploadImgReq {
} }
// 影片上傳請求(使用 multipart/form-data 文件上傳) // 影片上傳請求(使用 multipart/form-data 文件上傳)
// 注意:文件字段需要在 handler 中通過 r.FormFile("file") 獲取 // 注意:文件需要在 handler 中通過 r.FormFile("file") 獲取
type UploadVideoReq { type UploadVideoReq {
Authorization Authorization
} }

View File

@ -1,2 +1 @@
use digimon_member
db.count.dropIndex("name_1"); db.count.dropIndex("name_1");

View File

@ -1,2 +1 @@
use digimon_member
db.count.createIndex({ "name": 1 }, { unique: true }); db.count.createIndex({ "name": 1 }, { unique: true });

View File

@ -0,0 +1,3 @@
db.account.dropIndex("login_id_1_platform_1");
db.account.dropIndex("create_at_1");

View File

@ -1,3 +1,2 @@
use digimon_member;
db.account.createIndex({ "login_id": 1, "platform": 1}, {unique: true}) db.account.createIndex({ "login_id": 1, "platform": 1}, {unique: true})
db.account.createIndex({"create_at": 1}) db.account.createIndex({"create_at": 1})

View File

@ -0,0 +1,4 @@
db.account_uid_binding.dropIndex("login_id_1");
db.account_uid_binding.dropIndex("uid_1");
db.account_uid_binding.dropIndex("create_at_1");

View File

@ -1,4 +1,3 @@
use digimon_member;
db.account_uid_binding.createIndex({"login_id": 1}, {unique: true}) db.account_uid_binding.createIndex({"login_id": 1}, {unique: true})
db.account_uid_binding.createIndex({"uid": 1}) db.account_uid_binding.createIndex({"uid": 1})
db.account_uid_binding.createIndex({"create_at": 1}) db.account_uid_binding.createIndex({"create_at": 1})

View File

@ -0,0 +1,3 @@
db.user_info.dropIndex("uid_1");
db.user_info.dropIndex("create_at_1");

View File

@ -1,3 +1,2 @@
use digimon_member;
db.user_info.createIndex({"uid": 1},{unique: true}) db.user_info.createIndex({"uid": 1},{unique: true})
db.user_info.createIndex({"create_at": 1}) db.user_info.createIndex({"create_at": 1})

View File

@ -1,22 +0,0 @@
use digimon_product;
# 精確查詢索引(針對 owner_uid, is_published, is_visible, created_at
db.product.createIndex({"owner_uid": 1, "is_published": 1, "is_visible": 1, "created_at": -1})
# 範圍查詢索引(針對 start_time, end_time, created_at
db.product.createIndex({"start_time": 1, "end_time": 1, "created_at": -1})
# 金額範圍索引(針對 target_amount, created_at
db.product.createIndex({"target_amount": 1, "created_at": -1})
# 成功排序專用索引(針對 finished, updated_at
db.product.createIndex({"finished": 1, "updated_at": -1})
# URL 精確查詢索引(針對 url_slug
db.product.createIndex({"url_slug": 1})
# 基礎排序索引(針對 created_at 單列)
db.product.createIndex({"created_at": -1})
# URL 精確查詢索引(針對 url_slug
db.product.createIndex({"category": 1})

View File

@ -1,9 +0,0 @@
use digimon_product;
# 精確查詢與範圍條件組合索引
db.product_item.createIndex({"reference_id": 1}) # 精確匹配 reference_id
db.product_item.createIndex({"sale_start_time": 1, "sale_end_time": 1}) # 範圍查詢索引,提升 SaleStartTime 與 SaleEndTime 查詢效率
db.product_item.createIndex({"status": 1}) # 精確查詢 status
# 排序索引
db.product_item.createIndex({"created_at": -1}) # 用於按 created_at 倒序排序

View File

@ -1,8 +0,0 @@
use digimon_product;
# 精確查詢與範圍條件組合索引
db.supplementary_info.createIndex({"reference_id": 1}) # 精確匹配 reference_id
db.supplementary_info.createIndex({"info_type": 1}) # 精確查詢 status
# 排序索引
db.supplementary_info.createIndex({"created_at": -1}) # 用於按 created_at 倒序排序

View File

@ -1,6 +0,0 @@
use digimon_cart;
# 精確查詢與範圍條件組合索引
db.cart.createIndex({ "uid": 1, "product_id": 1 }, { unique: true })
# 排序索引
db.supplementary_info.createIndex({"created_at": -1}) # 用於按 created_at 倒序排序

View File

@ -0,0 +1,7 @@
db.permission.dropIndex("name_1");
db.permission.dropIndex("http_path_1_http_method_1");
db.permission.dropIndex("status_1");
db.permission.dropIndex("parent_id_1");
db.permission.dropIndex("type_1_status_1");
db.permission.dropIndex("create_time_1");

View File

@ -0,0 +1,7 @@
db.permission.createIndex({"name": 1}, {unique: true});
db.permission.createIndex({"http_path": 1, "http_method": 1}, {unique: true, sparse: true});
db.permission.createIndex({"status": 1});
db.permission.createIndex({"parent_id": 1});
db.permission.createIndex({"type": 1, "status": 1});
db.permission.createIndex({"create_time": 1});

View File

@ -0,0 +1,8 @@
db.role.dropIndex("uid_1");
db.role.dropIndex("client_id_1_name_1");
db.role.dropIndex("client_id_1");
db.role.dropIndex("status_1");
db.role.dropIndex("client_id_1_status_1");
db.role.dropIndex("create_time_1");
db.role.dropIndex("update_time_-1");

View File

@ -0,0 +1,8 @@
db.role.createIndex({"uid": 1}, {unique: true});
db.role.createIndex({"client_id": 1, "name": 1}, {unique: true});
db.role.createIndex({"client_id": 1});
db.role.createIndex({"status": 1});
db.role.createIndex({"client_id": 1, "status": 1});
db.role.createIndex({"create_time": 1});
db.role.createIndex({"update_time": -1});

View File

@ -0,0 +1,6 @@
db.role_permission.dropIndex("role_id_1_permission_id_1");
db.role_permission.dropIndex("role_id_1");
db.role_permission.dropIndex("permission_id_1");
db.role_permission.dropIndex("permission_id_1_status_1");
db.role_permission.dropIndex("create_time_1");

View File

@ -0,0 +1,6 @@
db.role_permission.createIndex({"role_id": 1, "permission_id": 1}, {unique: true});
db.role_permission.createIndex({"role_id": 1});
db.role_permission.createIndex({"permission_id": 1});
db.role_permission.createIndex({"permission_id": 1, "status": 1});
db.role_permission.createIndex({"create_time": 1});

View File

@ -0,0 +1,8 @@
db.user_role.dropIndex("uid_1");
db.user_role.dropIndex("role_id_1");
db.user_role.dropIndex("brand_1");
db.user_role.dropIndex("status_1");
db.user_role.dropIndex("brand_1_role_id_1");
db.user_role.dropIndex("brand_1_status_1");
db.user_role.dropIndex("create_time_1");

View File

@ -0,0 +1,8 @@
db.user_role.createIndex({"uid": 1}, {unique: true});
db.user_role.createIndex({"role_id": 1});
db.user_role.createIndex({"brand": 1});
db.user_role.createIndex({"status": 1});
db.user_role.createIndex({"brand": 1, "role_id": 1});
db.user_role.createIndex({"brand": 1, "status": 1});
db.user_role.createIndex({"create_time": 1});

View File

@ -22,11 +22,13 @@ db.role.insertMany([
"status": 1, "status": 1,
"create_time": NumberLong(1728745200), "create_time": NumberLong(1728745200),
"update_time": NumberLong(1728745200) "update_time": NumberLong(1728745200)
} },
]); {
"client_id": 1,
// 建立索引 "uid": "PLAYER",
db.role.createIndex({ "uid": 1 }, { unique: true }); "name": "陪玩專員",
db.role.createIndex({ "client_id": 1 }); "status": 1,
db.role.createIndex({ "status": 1 }); "create_time": NumberLong(1728745200),
"update_time": NumberLong(1728745200)
},
]);

View File

@ -0,0 +1,457 @@
# MongoDB 資料庫結構文檔
本文檔描述所有 MongoDB 集合Collection的結構定義。
## 目錄
- [Member 模組](#member-模組)
- [account](#account)
- [account_uid_binding](#account_uid_binding)
- [user_info](#user_info)
- [count](#count)
- [Permission 模組](#permission-模組)
- [permission](#permission)
- [role](#role)
- [role_permission](#role_permission)
- [user_role](#user_role)
---
## Member 模組
### account
**集合名稱**: `account`
**說明**: 用戶帳號認證資訊,儲存登入憑證、加密密碼和平台相關資料。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `login_id` | string | 是 | 唯一登入識別碼email、phone、username |
| `token` | string | 是 | 加密後的密碼或平台特定的 token |
| `platform` | int8 | 是 | 平台類型1=credentials, 2=google, 3=line, 4=apple |
| `create_at` | int64 | 否 | 建立時間Unix 時間戳,納秒) |
| `update_at` | int64 | 否 | 更新時間Unix 時間戳,納秒) |
#### 索引
1. **複合唯一索引**: `{login_id: 1, platform: 1}`
- 確保同一平台下 login_id 唯一
2. **時間索引**: `{create_at: 1}`
- 用於按建立時間排序和查詢
#### Platform 枚舉值
- `1` = credentials (Digimon 平台)
- `2` = google
- `3` = line
- `4` = apple
#### AccountType 枚舉值
- `1` = phone (手機)
- `2` = email (信箱)
- `3` = platform (自定義帳號)
#### AlarmType 枚舉值
- `0` = uninitialized (未初始化)
- `1` = no_alert (未告警)
- `2` = system_alert (系統告警中)
---
### account_uid_binding
**集合名稱**: `account_uid_binding`
**說明**: 帳號與 UID 的綁定關係,用於將多個帳號(不同平台)綁定到同一個用戶。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `login_id` | string | 是 | 登入識別碼 |
| `uid` | string | 是 | 用戶唯一識別碼 |
| `type` | int32 | 是 | 帳號類型1=phone, 2=email, 3=platform |
| `create_at` | int64 | 否 | 建立時間Unix 時間戳,納秒) |
| `update_at` | int64 | 否 | 更新時間Unix 時間戳,納秒) |
#### 索引
1. **唯一索引**: `{login_id: 1}`
- 確保 login_id 唯一
2. **索引**: `{uid: 1}`
- 用於查詢某用戶的所有帳號綁定
3. **時間索引**: `{create_at: 1}`
- 用於按建立時間排序
---
### user_info
**集合名稱**: `user_info`
**說明**: 用戶個人資料,包含詳細的用戶資訊,與認證憑證分離。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `uid` | string | 是 | 用戶唯一識別碼 |
| `avatar_url` | string | 否 | 用戶頭像 URL |
| `full_name` | string | 否 | 用戶全名 |
| `nickname` | string | 否 | 用戶暱稱 |
| `gender_code` | int64 | 否 | 性別代碼 |
| `birthdate` | int64 | 否 | 生日格式19930417 |
| `address` | string | 否 | 用戶地址 |
| `alarm_category` | int32 | 是 | 通知設定類型0=未初始化, 1=未告警, 2=系統告警中 |
| `user_status` | int32 | 是 | 用戶帳號狀態0=未初始化, 1=未驗證, 2=啟用中, 3=停權中 |
| `preferred_language` | string | 是 | 偏好語言 |
| `currency` | string | 是 | 偏好貨幣 |
| `phone_number` | string | 否 | 電話號碼(驗證後顯示) |
| `email` | string | 否 | 電子郵件(驗證後顯示) |
| `create_at` | int64 | 否 | 建立時間Unix 時間戳,納秒) |
| `update_at` | int64 | 否 | 更新時間Unix 時間戳,納秒) |
#### 索引
1. **唯一索引**: `{uid: 1}`
- 確保 uid 唯一
2. **時間索引**: `{create_at: 1}`
- 用於按建立時間排序
#### UserStatus 枚舉值
- `0` = uninitialized (未初始化)
- `1` = unverified (尚未驗證)
- `2` = active (啟用中)
- `3` = suspended (停權中)
---
### count
**集合名稱**: `count`
**說明**: 自增 ID 計數器,用於生成唯一序號。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `name` | string | 是 | 計數器名稱(唯一) |
| `counter` | uint64 | 是 | 當前計數值 |
| `create_at` | int64 | 否 | 建立時間Unix 時間戳,納秒) |
| `update_at` | int64 | 否 | 更新時間Unix 時間戳,納秒) |
#### 索引
1. **唯一索引**: `{name: 1}`
- 確保計數器名稱唯一
---
## Permission 模組
### permission
**集合名稱**: `permission`
**說明**: 權限實體,定義系統中的各種權限,支援階層結構和 API 權限。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `parent_id` | ObjectID | 否 | 父權限 ID用於階層結構 |
| `name` | string | 是 | 權限名稱(唯一) |
| `http_method` | string | 否 | HTTP 方法GET、POST、PUT、DELETE 等) |
| `http_path` | string | 否 | HTTP 路徑API 端點) |
| `status` | int8 | 是 | 權限狀態0=停用, 1=啟用, 2=已刪除 |
| `type` | int8 | 是 | 權限類型1=後台權限, 2=前台權限 |
| `create_time` | int64 | 是 | 建立時間Unix 時間戳,秒) |
| `update_time` | int64 | 是 | 更新時間Unix 時間戳,秒) |
#### 索引
1. **唯一索引**: `{name: 1}`
- 確保權限名稱唯一
2. **複合唯一稀疏索引**: `{http_path: 1, http_method: 1}` (sparse: true)
- 確保 API 權限的路徑和方法組合唯一(只索引存在這些欄位的文檔)
3. **查詢索引**: `{status: 1}`
- 用於查詢啟用/停用的權限
4. **查詢索引**: `{parent_id: 1}`
- 用於查詢子權限
5. **複合索引**: `{type: 1, status: 1}`
- 用於按類型和狀態組合查詢
6. **時間戳索引**: `{create_time: 1}`
- 用於排序和時間範圍查詢
#### RecordState 枚舉值
- `0` = inactive (停用)
- `1` = active (啟用)
- `2` = deleted (已刪除)
#### Type 枚舉值
- `1` = backend (後台權限)
- `2` = frontend (前台權限)
---
### role
**集合名稱**: `role`
**說明**: 角色實體,定義系統中的角色,每個角色可以擁有多個權限。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `client_id` | int | 是 | 客戶端 ID |
| `uid` | string | 是 | 角色唯一識別碼(唯一) |
| `name` | string | 是 | 角色名稱 |
| `status` | int8 | 是 | 角色狀態0=停用, 1=啟用, 2=已刪除 |
| `create_time` | int64 | 是 | 建立時間Unix 時間戳,秒) |
| `update_time` | int64 | 是 | 更新時間Unix 時間戳,秒) |
#### 索引
1. **唯一索引**: `{uid: 1}`
- 確保角色 UID 唯一
2. **複合唯一索引**: `{client_id: 1, name: 1}`
- 確保同一客戶端下角色名稱唯一
3. **查詢索引**: `{client_id: 1}`
- 用於按客戶端查詢
4. **查詢索引**: `{status: 1}`
- 用於按狀態查詢
5. **複合索引**: `{client_id: 1, status: 1}`
- 用於按客戶端和狀態組合查詢
6. **時間戳索引**: `{create_time: 1}`
- 用於排序和時間範圍查詢
7. **時間戳索引**: `{update_time: -1}` (降序)
- 用於按更新時間排序
#### 預設角色數據
系統會自動插入以下初始角色:
- `ADMIN` - 管理員
- `OPERATOR` - 操作員
- `USER` - 一般使用者
- `PLAYER` - 陪玩專員
---
### role_permission
**集合名稱**: `role_permission`
**說明**: 角色權限關聯表,建立角色與權限的多對多關係。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `role_id` | ObjectID | 是 | 角色 ID |
| `permission_id` | ObjectID | 是 | 權限 ID |
| `create_time` | int64 | 是 | 建立時間Unix 時間戳,秒) |
| `update_time` | int64 | 是 | 更新時間Unix 時間戳,秒) |
#### 索引
1. **複合唯一索引**: `{role_id: 1, permission_id: 1}`
- 確保角色和權限的組合唯一(避免重複關聯)
2. **查詢索引**: `{role_id: 1}`
- 用於查詢某角色的所有權限
3. **查詢索引**: `{permission_id: 1}`
- 用於查詢擁有某權限的所有角色
4. **複合索引**: `{permission_id: 1, status: 1}`
- 用於按權限和狀態組合查詢
5. **時間戳索引**: `{create_time: 1}`
- 用於排序和時間範圍查詢
---
### user_role
**集合名稱**: `user_role`
**說明**: 用戶角色關聯表,建立用戶與角色的關係(一個用戶只能有一個角色)。
#### 欄位定義
| 欄位名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `_id` | ObjectID | 是 | MongoDB 自動生成的唯一識別碼 |
| `brand` | string | 是 | 品牌識別碼 |
| `uid` | string | 是 | 用戶唯一識別碼(唯一) |
| `role_id` | string | 是 | 角色 ID角色 UID |
| `status` | int8 | 是 | 狀態0=停用, 1=啟用, 2=已刪除 |
| `create_time` | int64 | 是 | 建立時間Unix 時間戳,秒) |
| `update_time` | int64 | 是 | 更新時間Unix 時間戳,秒) |
#### 索引
1. **唯一索引**: `{uid: 1}`
- 確保一個用戶只能有一個角色
2. **查詢索引**: `{role_id: 1}`
- 用於查詢某角色的所有用戶
3. **查詢索引**: `{brand: 1}`
- 用於按品牌查詢
4. **查詢索引**: `{status: 1}`
- 用於按狀態查詢
5. **複合索引**: `{brand: 1, role_id: 1}`
- 用於按品牌和角色組合查詢
6. **複合索引**: `{brand: 1, status: 1}`
- 用於按品牌和狀態組合查詢
7. **時間戳索引**: `{create_time: 1}`
- 用於排序和時間範圍查詢
---
## 資料類型說明
### 時間戳記
- **Member 模組**: 使用 `int64` 類型單位為納秒nanoseconds欄位名為 `create_at`、`update_at`
- **Permission 模組**: 使用 `int64` 類型單位為秒seconds欄位名為 `create_time`、`update_time`
### 枚舉類型
#### Platform (平台類型)
- `1` = credentials (Digimon 平台)
- `2` = google
- `3` = line
- `4` = apple
#### Status (帳號狀態)
- `0` = uninitialized (未初始化)
- `1` = unverified (尚未驗證)
- `2` = active (啟用中)
- `3` = suspended (停權中)
#### RecordState (記錄狀態)
- `0` = inactive (停用)
- `1` = active (啟用)
- `2` = deleted (已刪除)
#### Type (權限類型)
- `1` = backend (後台權限)
- `2` = frontend (前台權限)
#### AccountType (帳號類型)
- `1` = phone (手機)
- `2` = email (信箱)
- `3` = platform (自定義帳號)
#### AlarmType (通知類型)
- `0` = uninitialized (未初始化)
- `1` = no_alert (未告警)
- `2` = system_alert (系統告警中)
---
## 索引總覽
### Member 模組索引
| 集合 | 索引類型 | 索引欄位 | 唯一 | 說明 |
|------|---------|---------|------|------|
| account | 複合唯一 | login_id, platform | ✅ | 確保同一平台下 login_id 唯一 |
| account | 單一 | create_at | ❌ | 時間排序 |
| account_uid_binding | 唯一 | login_id | ✅ | 確保 login_id 唯一 |
| account_uid_binding | 單一 | uid | ❌ | 查詢用戶的所有帳號 |
| account_uid_binding | 單一 | create_at | ❌ | 時間排序 |
| user_info | 唯一 | uid | ✅ | 確保 uid 唯一 |
| user_info | 單一 | create_at | ❌ | 時間排序 |
| count | 唯一 | name | ✅ | 確保計數器名稱唯一 |
### Permission 模組索引
| 集合 | 索引類型 | 索引欄位 | 唯一 | 稀疏 | 說明 |
|------|---------|---------|------|------|------|
| permission | 唯一 | name | ✅ | ❌ | 權限名稱唯一 |
| permission | 複合唯一稀疏 | http_path, http_method | ✅ | ✅ | API 權限唯一 |
| permission | 單一 | status | ❌ | ❌ | 狀態查詢 |
| permission | 單一 | parent_id | ❌ | ❌ | 子權限查詢 |
| permission | 複合 | type, status | ❌ | ❌ | 類型和狀態組合查詢 |
| permission | 單一 | create_time | ❌ | ❌ | 時間排序 |
| role | 唯一 | uid | ✅ | ❌ | 角色 UID 唯一 |
| role | 複合唯一 | client_id, name | ✅ | ❌ | 同客戶端下角色名稱唯一 |
| role | 單一 | client_id | ❌ | ❌ | 客戶端查詢 |
| role | 單一 | status | ❌ | ❌ | 狀態查詢 |
| role | 複合 | client_id, status | ❌ | ❌ | 客戶端和狀態組合查詢 |
| role | 單一 | create_time | ❌ | ❌ | 時間排序 |
| role | 單一 | update_time | ❌ | ❌ | 更新時間排序(降序) |
| role_permission | 複合唯一 | role_id, permission_id | ✅ | ❌ | 避免重複關聯 |
| role_permission | 單一 | role_id | ❌ | ❌ | 角色權限查詢 |
| role_permission | 單一 | permission_id | ❌ | ❌ | 權限角色查詢 |
| role_permission | 複合 | permission_id, status | ❌ | ❌ | 權限和狀態組合查詢 |
| role_permission | 單一 | create_time | ❌ | ❌ | 時間排序 |
| user_role | 唯一 | uid | ✅ | ❌ | 一個用戶一個角色 |
| user_role | 單一 | role_id | ❌ | ❌ | 角色用戶查詢 |
| user_role | 單一 | brand | ❌ | ❌ | 品牌查詢 |
| user_role | 單一 | status | ❌ | ❌ | 狀態查詢 |
| user_role | 複合 | brand, role_id | ❌ | ❌ | 品牌和角色組合查詢 |
| user_role | 複合 | brand, status | ❌ | ❌ | 品牌和狀態組合查詢 |
| user_role | 單一 | create_time | ❌ | ❌ | 時間排序 |
---
## 關聯關係
### Member 模組
```
account (1) ──< (N) account_uid_binding (N) >── (1) user_info
```
- 一個 `account` 可以對應一個 `account_uid_binding`
- 一個 `user_info` 可以對應多個 `account_uid_binding`(多個帳號綁定到同一用戶)
### Permission 模組
```
role (N) <──> (N) permission (透過 role_permission)
user_role (N) >── (1) role
```
- 角色和權限是多對多關係(透過 `role_permission`
- 用戶和角色是一對一關係(透過 `user_role`,一個用戶只能有一個角色)
---
## 注意事項
1. **時間戳記差異**
- Member 模組使用納秒級時間戳(`create_at`、`update_at`
- Permission 模組使用秒級時間戳(`create_time`、`update_time`
2. **唯一性約束**
- `account`: `login_id + platform` 組合必須唯一
- `account_uid_binding`: `login_id` 必須唯一
- `user_info`: `uid` 必須唯一
- `permission`: `name` 必須唯一,`http_path + http_method` 組合必須唯一(稀疏索引)
- `role`: `uid` 必須唯一,`client_id + name` 組合必須唯一
- `role_permission`: `role_id + permission_id` 組合必須唯一
- `user_role`: `uid` 必須唯一(一個用戶只能有一個角色)
3. **稀疏索引**
- `permission` 集合的 `http_path + http_method` 索引是稀疏索引,只索引存在這些欄位的文檔
4. **預設數據**
- `role` 集合會自動插入 4 個預設角色ADMIN、OPERATOR、USER、PLAYER

View File

@ -0,0 +1,52 @@
# MongoDB 資料庫文檔
本目錄包含 MongoDB 資料庫的結構文檔和相關說明。
## 文件說明
### DATABASE_SCHEMA.md
完整的資料庫結構文檔,包含:
- **所有集合Collection的結構定義**
- 欄位名稱、類型、必填性、說明
- 索引定義和用途
- 枚舉值說明
- **索引總覽**
- 所有集合的索引列表
- 索引類型和唯一性說明
- **關聯關係**
- 集合之間的關聯圖
- 一對一、一對多、多對多關係說明
- **資料類型說明**
- 時間戳記格式差異
- 枚舉類型定義
## 集合列表
### Member 模組
- `account` - 用戶帳號認證資訊
- `account_uid_binding` - 帳號與 UID 綁定關係
- `user_info` - 用戶個人資料
- `count` - 自增 ID 計數器
### Permission 模組
- `permission` - 權限實體
- `role` - 角色實體
- `role_permission` - 角色權限關聯表
- `user_role` - 用戶角色關聯表
## 使用方式
1. **查看集合結構**:打開 `DATABASE_SCHEMA.md`,搜尋集合名稱
2. **了解索引**:查看「索引總覽」章節
3. **了解關聯**:查看「關聯關係」章節
4. **了解枚舉值**:查看各集合的「枚舉值」說明
## 更新說明
當資料庫結構發生變更時,請更新 `DATABASE_SCHEMA.md` 文檔,確保文檔與實際結構保持一致。

2
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ses v1.34.9 github.com/aws/aws-sdk-go-v2/service/ses v1.34.9
github.com/go-playground/validator/v10 v10.28.0 github.com/go-playground/validator/v10 v10.28.0
github.com/golang-jwt/jwt/v4 v4.5.2 github.com/golang-jwt/jwt/v4 v4.5.2
github.com/google/uuid v1.6.0
github.com/matcornic/hermes/v2 v2.1.0 github.com/matcornic/hermes/v2 v2.1.0
github.com/minchao/go-mitake v1.0.0 github.com/minchao/go-mitake v1.0.0
github.com/panjf2000/ants/v2 v2.11.3 github.com/panjf2000/ants/v2 v2.11.3
@ -63,7 +64,6 @@ require (
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/snappy v1.0.0 // indirect github.com/golang/snappy v1.0.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/grafana/pyroscope-go v1.2.7 // indirect github.com/grafana/pyroscope-go v1.2.7 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect

View File

@ -7,6 +7,7 @@ import (
"backend/internal/logic/fileStorage" "backend/internal/logic/fileStorage"
"backend/internal/svc" "backend/internal/svc"
"backend/internal/types" "backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
) )

View File

@ -7,6 +7,7 @@ import (
"backend/internal/logic/user" "backend/internal/logic/user"
"backend/internal/svc" "backend/internal/svc"
"backend/internal/types" "backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
) )

View File

@ -5,9 +5,10 @@ import (
"backend/pkg/member/domain/usecase" "backend/pkg/member/domain/usecase"
"backend/pkg/permission/domain/token" "backend/pkg/permission/domain/token"
"context" "context"
"google.golang.org/protobuf/proto"
"time" "time"
"google.golang.org/protobuf/proto"
"backend/internal/svc" "backend/internal/svc"
"backend/internal/types" "backend/internal/types"

View File

@ -6,6 +6,7 @@ import (
"backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/entity"
"backend/pkg/permission/domain/token" "backend/pkg/permission/domain/token"
"context" "context"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"backend/pkg/permission/domain/usecase" "backend/pkg/permission/domain/usecase"

View File

@ -8,6 +8,7 @@ import (
"backend/pkg/member/repository" "backend/pkg/member/repository"
uc "backend/pkg/member/usecase" uc "backend/pkg/member/usecase"
"context" "context"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/cache"

View File

@ -3,6 +3,7 @@ package svc
import ( import (
errs "backend/pkg/library/errors" errs "backend/pkg/library/errors"
"context" "context"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )

View File

@ -6,6 +6,7 @@ import (
errs "backend/pkg/library/errors" errs "backend/pkg/library/errors"
"backend/pkg/library/errors/code" "backend/pkg/library/errors/code"
"context" "context"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
fileStorageUC "backend/pkg/fileStorage/domain/usecase" fileStorageUC "backend/pkg/fileStorage/domain/usecase"

View File

@ -7,6 +7,7 @@ import (
"backend/pkg/permission/repository" "backend/pkg/permission/repository"
uc "backend/pkg/permission/usecase" uc "backend/pkg/permission/usecase"
"context" "context"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon" "github.com/zeromicro/go-zero/core/stores/mon"

View File

@ -9,17 +9,18 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"io" "io"
"net/http" "net/http"
"os" "os"
"path" "path"
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
) )
type AwsS3FileStorageRepositoryParam struct { type AwsS3FileStorageRepositoryParam struct {

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"backend/pkg/library/errors/code" "backend/pkg/library/errors/code"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"backend/pkg/library/errors/code" "backend/pkg/library/errors/code"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
) )

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"backend/pkg/library/errors/code" "backend/pkg/library/errors/code"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"backend/pkg/library/errors/code" "backend/pkg/library/errors/code"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )

View File

@ -4,6 +4,7 @@ import (
"backend/pkg/notification/config" "backend/pkg/notification/config"
"backend/pkg/notification/domain/repository" "backend/pkg/notification/domain/repository"
"context" "context"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/ses" "github.com/aws/aws-sdk-go-v2/service/ses"

View File

@ -4,6 +4,7 @@ import (
"backend/pkg/notification/config" "backend/pkg/notification/config"
"backend/pkg/notification/domain/repository" "backend/pkg/notification/domain/repository"
"context" "context"
"github.com/minchao/go-mitake" "github.com/minchao/go-mitake"
) )

View File

@ -4,6 +4,7 @@ import (
"backend/pkg/notification/config" "backend/pkg/notification/config"
"backend/pkg/notification/domain/repository" "backend/pkg/notification/domain/repository"
"context" "context"
"gopkg.in/gomail.v2" "gopkg.in/gomail.v2"
) )

View File

@ -14,7 +14,7 @@ import (
type TemplateUseCaseParam struct { type TemplateUseCaseParam struct {
TemplateRepo repository.TemplateRepository // 可選的資料庫模板 repository TemplateRepo repository.TemplateRepository // 可選的資料庫模板 repository
Logger errs.Logger // 日誌記錄器 Logger errs.Logger // 日誌記錄器
} }
type TemplateUseCase struct { type TemplateUseCase struct {

View File

@ -240,4 +240,3 @@ func TestTokenConfig_AllDefaults(t *testing.T) {
assert.Equal(t, token.MaxTokensPerUser, config.MaxTokensPerUser) assert.Equal(t, token.MaxTokensPerUser, config.MaxTokensPerUser)
assert.Equal(t, token.MaxTokensPerDevice, config.MaxTokensPerDevice) assert.Equal(t, token.MaxTokensPerDevice, config.MaxTokensPerDevice)
} }

View File

@ -7,4 +7,3 @@ import (
var ( var (
ErrMissingSecret = fmt.Errorf("missing JWT secret key") ErrMissingSecret = fmt.Errorf("missing JWT secret key")
) )

View File

@ -30,4 +30,3 @@ func (b *BlacklistEntry) Validate() error {
} }
return nil return nil
} }

View File

@ -9,9 +9,9 @@ import (
func TestBlacklistEntry_IsExpired(t *testing.T) { func TestBlacklistEntry_IsExpired(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
entry *BlacklistEntry entry *BlacklistEntry
expected bool expected bool
}{ }{
{ {
name: "expired entry", name: "expired entry",
@ -58,9 +58,9 @@ func TestBlacklistEntry_IsExpired(t *testing.T) {
func TestBlacklistEntry_Validate(t *testing.T) { func TestBlacklistEntry_Validate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
entry *BlacklistEntry entry *BlacklistEntry
wantErr bool wantErr bool
expectedErr error expectedErr error
}{ }{
{ {
@ -126,7 +126,7 @@ func TestBlacklistEntry_Validate(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := tt.entry.Validate() err := tt.entry.Validate()
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
if tt.expectedErr != nil { if tt.expectedErr != nil {
@ -191,4 +191,3 @@ func TestBlacklistEntry_Reason(t *testing.T) {
}) })
} }
} }

View File

@ -2,6 +2,7 @@ package entity
import ( import (
"backend/pkg/permission/domain/permission" "backend/pkg/permission/domain/permission"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
) )

View File

@ -82,4 +82,3 @@ type DoTokenByDeviceIDReq struct {
type CancelOneTimeTokenReq struct { type CancelOneTimeTokenReq struct {
Token []string `json:"token"` // 一次性 Token 列表 Token []string `json:"token"` // 一次性 Token 列表
} }

View File

@ -2,6 +2,7 @@ package entity
import ( import (
"backend/pkg/permission/domain/permission" "backend/pkg/permission/domain/permission"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
) )

View File

@ -2,6 +2,7 @@ package entity
import ( import (
"backend/pkg/permission/domain/permission" "backend/pkg/permission/domain/permission"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
) )

View File

@ -2,7 +2,7 @@ package entity
import ( import (
"time" "time"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
@ -62,4 +62,4 @@ func (t *Token) Validate() error {
return ErrInvalidAccessToken return ErrInvalidAccessToken
} }
return nil return nil
} }

View File

@ -122,7 +122,7 @@ func TestToken_RedisRefreshExpiredSec(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
result := tt.token.RedisRefreshExpiredSec() result := tt.token.RedisRefreshExpiredSec()
if tt.expected == 0 { if tt.expected == 0 {
assert.Equal(t, 0, result) assert.Equal(t, 0, result)
} else { } else {
@ -195,7 +195,7 @@ func TestToken_Validate(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := tt.token.Validate() err := tt.token.Validate()
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
if tt.expectedErr != nil { if tt.expectedErr != nil {
@ -315,4 +315,3 @@ func TestToken_Timestamps(t *testing.T) {
assert.Equal(t, now, token.AccessCreateAt) assert.Equal(t, now, token.AccessCreateAt)
assert.True(t, token.RefreshCreateAt.After(token.AccessCreateAt)) assert.True(t, token.RefreshCreateAt.After(token.AccessCreateAt))
} }

View File

@ -2,6 +2,7 @@ package entity
import ( import (
"backend/pkg/permission/domain/permission" "backend/pkg/permission/domain/permission"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
) )

View File

@ -3,6 +3,7 @@ package repository
import ( import (
"backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/entity"
"context" "context"
mongodriver "go.mongodb.org/mongo-driver/v2/mongo" mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
) )

View File

@ -4,6 +4,7 @@ import (
"backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/entity"
"backend/pkg/permission/domain/permission" "backend/pkg/permission/domain/permission"
"context" "context"
mongodriver "go.mongodb.org/mongo-driver/v2/mongo" mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
) )

View File

@ -3,11 +3,12 @@ package repository
import ( import (
"context" "context"
"time" "time"
"backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/entity"
) )
// TokenRepository 定義了與 Redis 相關的 Token 操作方法 // TokenRepository 定義了與 Redis 相關的 Token 操作方法
//
//nolint:interfacebloat //nolint:interfacebloat
type TokenRepository interface { type TokenRepository interface {
// Create 建立新的 Token 並存儲至 Redis // Create 建立新的 Token 並存儲至 Redis
@ -36,7 +37,7 @@ type TokenRepository interface {
DeleteAccessTokensByUID(ctx context.Context, uid string) error DeleteAccessTokensByUID(ctx context.Context, uid string) error
// DeleteAccessTokensByDeviceID 根據裝置 ID 刪除該裝置的所有存取 Token // DeleteAccessTokensByDeviceID 根據裝置 ID 刪除該裝置的所有存取 Token
DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error
// Blacklist operations // Blacklist operations
// AddToBlacklist 將 JWT token 加入黑名單 // AddToBlacklist 將 JWT token 加入黑名單
AddToBlacklist(ctx context.Context, entry *entity.BlacklistEntry, ttl time.Duration) error AddToBlacklist(ctx context.Context, entry *entity.BlacklistEntry, ttl time.Duration) error
@ -46,4 +47,4 @@ type TokenRepository interface {
RemoveFromBlacklist(ctx context.Context, jti string) error RemoveFromBlacklist(ctx context.Context, jti string) error
// GetBlacklistedTokensByUID 獲取用戶的所有黑名單 token // GetBlacklistedTokensByUID 獲取用戶的所有黑名單 token
GetBlacklistedTokensByUID(ctx context.Context, uid string) ([]*entity.BlacklistEntry, error) GetBlacklistedTokensByUID(ctx context.Context, uid string) ([]*entity.BlacklistEntry, error)
} }

View File

@ -4,6 +4,7 @@ import (
"backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/entity"
"backend/pkg/permission/domain/permission" "backend/pkg/permission/domain/permission"
"context" "context"
mongodriver "go.mongodb.org/mongo-driver/v2/mongo" mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
) )

View File

@ -23,4 +23,3 @@ const (
ClientCredentials GrantType = "client_credentials" ClientCredentials GrantType = "client_credentials"
Refreshing GrantType = "refresh_token" Refreshing GrantType = "refresh_token"
) )

View File

@ -157,4 +157,3 @@ func TestGrantType_CaseSensitive(t *testing.T) {
assert.False(t, gt2.IsValid()) assert.False(t, gt2.IsValid())
}) })
} }

View File

@ -337,4 +337,3 @@ func TestKeyPrefixUniqueness(t *testing.T) {
assert.Equal(t, len(prefixes), len(seen)) assert.Equal(t, len(prefixes), len(seen))
}) })
} }

View File

@ -21,13 +21,13 @@ type PermissionUseCase interface {
// PermissionResponse 權限回應 // PermissionResponse 權限回應
type PermissionResponse struct { type PermissionResponse struct {
ID string `json:"id"` ID string `json:"id"`
ParentID string `json:"parent_id"` ParentID string `json:"parent_id"`
Name string `json:"name"` Name string `json:"name"`
HTTPPath string `json:"http_path,omitempty"` HTTPPath string `json:"http_path,omitempty"`
HTTPMethod string `json:"http_method,omitempty"` HTTPMethod string `json:"http_method,omitempty"`
Status permission.AccessState `json:"status"` Status permission.AccessState `json:"status"`
Type permission.Type `json:"type"` Type permission.Type `json:"type"`
} }
// PermissionTreeNode 權限樹節點 // PermissionTreeNode 權限樹節點

View File

@ -30,9 +30,9 @@ type CreateRoleRequest struct {
// UpdateRoleRequest 更新角色請求 // UpdateRoleRequest 更新角色請求
type UpdateRoleRequest struct { type UpdateRoleRequest struct {
Name *string `json:"name"` Name *string `json:"name"`
Status *permission.RecordState `json:"status"` Status *permission.RecordState `json:"status"`
Permissions permission.Permissions `json:"permissions"` Permissions permission.Permissions `json:"permissions"`
} }
// RoleFilterRequest 角色查詢過濾請求 // RoleFilterRequest 角色查詢過濾請求

View File

@ -7,12 +7,14 @@ import (
domainRepo "backend/pkg/permission/domain/repository" domainRepo "backend/pkg/permission/domain/repository"
"context" "context"
"fmt" "fmt"
"github.com/alicebob/miniredis/v2"
"github.com/zeromicro/go-zero/core/stores/redis"
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis/v2"
"github.com/zeromicro/go-zero/core/stores/redis"
mgo "backend/pkg/library/mongo" mgo "backend/pkg/library/mongo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/cache"

View File

@ -105,7 +105,7 @@ func TestRolePermissionRepository_Create(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
// 注意:由於 ObjectID 生成機制GetByRoleID 無法查到剛創建的數據 // 注意:由於 ObjectID 生成機制GetByRoleID 無法查到剛創建的數據
// 這是因為 roleID 每次轉換為 ObjectID 時都會生成不同的值 // 這是因為 roleID 每次轉換為 ObjectID 時都會生成不同的值
// 在實際使用中應該使用真實的 ObjectID 而不是 int64 // 在實際使用中應該使用真實的 ObjectID 而不是 int64
@ -116,7 +116,7 @@ func TestRolePermissionRepository_Create(t *testing.T) {
func TestRolePermissionRepository_GetByRoleID(t *testing.T) { func TestRolePermissionRepository_GetByRoleID(t *testing.T) {
t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID") t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID")
repo, tearDown, err := setupRolePermissionRepo("testDB") repo, tearDown, err := setupRolePermissionRepo("testDB")
defer tearDown() defer tearDown()
assert.NoError(t, err) assert.NoError(t, err)
@ -132,7 +132,7 @@ func TestRolePermissionRepository_GetByRoleID(t *testing.T) {
func TestRolePermissionRepository_GetByRoleIDs(t *testing.T) { func TestRolePermissionRepository_GetByRoleIDs(t *testing.T) {
t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID") t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID")
repo, tearDown, err := setupRolePermissionRepo("testDB") repo, tearDown, err := setupRolePermissionRepo("testDB")
defer tearDown() defer tearDown()
assert.NoError(t, err) assert.NoError(t, err)
@ -148,7 +148,7 @@ func TestRolePermissionRepository_GetByRoleIDs(t *testing.T) {
func TestRolePermissionRepository_GetByPermissionIDs(t *testing.T) { func TestRolePermissionRepository_GetByPermissionIDs(t *testing.T) {
t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID") t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID")
repo, tearDown, err := setupRolePermissionRepo("testDB") repo, tearDown, err := setupRolePermissionRepo("testDB")
defer tearDown() defer tearDown()
assert.NoError(t, err) assert.NoError(t, err)
@ -164,7 +164,7 @@ func TestRolePermissionRepository_GetByPermissionIDs(t *testing.T) {
func TestRolePermissionRepository_GetRolesByPermission(t *testing.T) { func TestRolePermissionRepository_GetRolesByPermission(t *testing.T) {
t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID") t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID")
repo, tearDown, err := setupRolePermissionRepo("testDB") repo, tearDown, err := setupRolePermissionRepo("testDB")
defer tearDown() defer tearDown()
assert.NoError(t, err) assert.NoError(t, err)
@ -180,7 +180,7 @@ func TestRolePermissionRepository_GetRolesByPermission(t *testing.T) {
func TestRolePermissionRepository_Update(t *testing.T) { func TestRolePermissionRepository_Update(t *testing.T) {
t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID") t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID")
repo, tearDown, err := setupRolePermissionRepo("testDB") repo, tearDown, err := setupRolePermissionRepo("testDB")
defer tearDown() defer tearDown()
assert.NoError(t, err) assert.NoError(t, err)
@ -195,7 +195,7 @@ func TestRolePermissionRepository_Update(t *testing.T) {
func TestRolePermissionRepository_Delete(t *testing.T) { func TestRolePermissionRepository_Delete(t *testing.T) {
t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID") t.Skip("跳過:由於 int64 到 ObjectID 轉換問題,此測試無法正常運行。實際使用時應使用真實的 ObjectID")
repo, tearDown, err := setupRolePermissionRepo("testDB") repo, tearDown, err := setupRolePermissionRepo("testDB")
defer tearDown() defer tearDown()
assert.NoError(t, err) assert.NoError(t, err)
@ -210,7 +210,7 @@ func TestRolePermissionRepository_Delete(t *testing.T) {
func TestRolePermissionRepository_CreateDuplicateShouldFail(t *testing.T) { func TestRolePermissionRepository_CreateDuplicateShouldFail(t *testing.T) {
t.Skip("跳過:由於 ObjectID 生成機制,每次創建都會生成新的 roleID ObjectID無法觸發唯一索引衝突") t.Skip("跳過:由於 ObjectID 生成機制,每次創建都會生成新的 roleID ObjectID無法觸發唯一索引衝突")
repo, tearDown, err := setupRolePermissionRepo("testDB") repo, tearDown, err := setupRolePermissionRepo("testDB")
defer tearDown() defer tearDown()
assert.NoError(t, err) assert.NoError(t, err)
@ -246,4 +246,3 @@ func TestRolePermissionRepository_IndexCreation(t *testing.T) {
assert.NotNil(t, cursor) assert.NotNil(t, cursor)
}) })
} }

View File

@ -6,11 +6,12 @@ import (
domainRepo "backend/pkg/permission/domain/repository" domainRepo "backend/pkg/permission/domain/repository"
"context" "context"
"fmt" "fmt"
"github.com/alicebob/miniredis/v2"
"github.com/zeromicro/go-zero/core/stores/redis"
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis/v2"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/cache"

View File

@ -51,4 +51,3 @@ func startMongoContainer() (string, string, func(), error) {
return host, port.Port(), tearDown, nil return host, port.Port(), tearDown, nil
} }

View File

@ -91,7 +91,7 @@ func TestTokenRepository_Blacklist(t *testing.T) {
t.Run("GetBlacklistedTokensByUID", func(t *testing.T) { t.Run("GetBlacklistedTokensByUID", func(t *testing.T) {
uid := "user789" uid := "user789"
// Add multiple entries for the same user // Add multiple entries for the same user
entries := []*entity.BlacklistEntry{ entries := []*entity.BlacklistEntry{
{ {
@ -102,7 +102,7 @@ func TestTokenRepository_Blacklist(t *testing.T) {
CreatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
}, },
{ {
JTI: "jti-2", JTI: "jti-2",
UID: uid, UID: uid,
TokenID: "token-2", TokenID: "token-2",
ExpiresAt: time.Now().Add(time.Hour).Unix(), ExpiresAt: time.Now().Add(time.Hour).Unix(),
@ -245,7 +245,7 @@ func TestTokenRepository_CreateAndGet(t *testing.T) {
t.Run("GetAccessTokenCountByUID", func(t *testing.T) { t.Run("GetAccessTokenCountByUID", func(t *testing.T) {
uid := "user789" uid := "user789"
now := time.Now() now := time.Now()
// Create multiple tokens for the user // Create multiple tokens for the user
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
token := entity.Token{ token := entity.Token{
@ -296,7 +296,7 @@ func TestTokenRepository_CreateAndGet(t *testing.T) {
t.Run("DeleteAccessTokensByUID", func(t *testing.T) { t.Run("DeleteAccessTokensByUID", func(t *testing.T) {
uid := "delete-user-uid" uid := "delete-user-uid"
now := time.Now() now := time.Now()
// Create multiple tokens for the user // Create multiple tokens for the user
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
token := entity.Token{ token := entity.Token{
@ -355,12 +355,11 @@ func TestTokenRepository_OneTimeToken(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("DeleteOneTimeToken", func(t *testing.T) { t.Run("DeleteOneTimeToken", func(t *testing.T) {
// Create one-time tokens // Create one-time tokens
keys := []string{"delete-key-1", "delete-key-2"} keys := []string{"delete-key-1", "delete-key-2"}
ticket := entity.Ticket{ ticket := entity.Ticket{
Data: map[string]string{"test": "data"}, Data: map[string]string{"test": "data"},
Token: entity.Token{ID: "test-token"}, Token: entity.Token{ID: "test-token"},
} }

View File

@ -6,11 +6,12 @@ import (
domainRepo "backend/pkg/permission/domain/repository" domainRepo "backend/pkg/permission/domain/repository"
"context" "context"
"fmt" "fmt"
"github.com/alicebob/miniredis/v2"
"github.com/zeromicro/go-zero/core/stores/redis"
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis/v2"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/cache"

View File

@ -314,4 +314,3 @@ func (t *PermissionTree) detectCircular(node *PermissionNode, visited map[string
return nil return nil
} }

View File

@ -156,4 +156,3 @@ func TestPermissionTree_DetectCircularDependency(t *testing.T) {
err := tree.DetectCircularDependency() err := tree.DetectCircularDependency()
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -235,4 +235,3 @@ func ConvertInt64ToOID(id int64) bson.ObjectID {
} }
return bson.NewObjectIDFromTimestamp(time.Unix(id, 0)) return bson.NewObjectIDFromTimestamp(time.Unix(id, 0))
} }

View File

@ -202,4 +202,3 @@ func (uc *rolePermissionUseCase) getAllPermissions(ctx context.Context) (permiss
return permissions, nil return permissions, nil
} }

View File

@ -276,4 +276,3 @@ func (uc *roleUseCase) toResponseList(ctx context.Context, roles []*entity.Role,
return result return result
} }

View File

@ -49,7 +49,7 @@ func TestRoleUseCase_Create(t *testing.T) {
mockSetup: func() { mockSetup: func() {
mockRoleRepo.EXPECT().NextID(ctx).Return(int64(1), nil) mockRoleRepo.EXPECT().NextID(ctx).Return(int64(1), nil)
mockRoleRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil) mockRoleRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
role := &entity.Role{ role := &entity.Role{
ID: bson.NewObjectID(), ID: bson.NewObjectID(),
UID: "ROLE0000000001", UID: "ROLE0000000001",

View File

@ -87,21 +87,21 @@ func TestTokenUseCase_NewToken(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -223,21 +223,21 @@ func TestTokenUseCase_RefreshToken(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -342,21 +342,21 @@ func TestTokenUseCase_ValidationToken(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -462,21 +462,21 @@ func TestTokenUseCase_CancelToken(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -567,21 +567,21 @@ func TestTokenUseCase_CancelTokens(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -644,21 +644,21 @@ func TestTokenUseCase_CancelTokenByDeviceID(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -757,21 +757,21 @@ func TestTokenUseCase_GetUserTokensByUID(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -851,21 +851,21 @@ func TestTokenUseCase_GetUserTokensByDeviceID(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -986,21 +986,21 @@ func TestTokenUseCase_NewOneTimeToken(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -1064,21 +1064,21 @@ func TestTokenUseCase_CancelOneTimeToken(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -1156,21 +1156,21 @@ func TestTokenUseCase_ReadTokenBasicData(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -1262,21 +1262,21 @@ func TestTokenUseCase_BlacklistToken(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -1296,11 +1296,11 @@ func TestTokenUseCase_IsTokenBlacklisted(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tests := []struct { tests := []struct {
name string name string
jti string jti string
mockSetup func(*mockRepo.MockTokenRepository) mockSetup func(*mockRepo.MockTokenRepository)
wantBlacklisted bool wantBlacklisted bool
wantErr bool wantErr bool
}{ }{
{ {
name: "Token 在黑名單中", name: "Token 在黑名單中",
@ -1349,21 +1349,21 @@ func TestTokenUseCase_IsTokenBlacklisted(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -1403,9 +1403,9 @@ func TestTokenUseCase_BlacklistAllUserTokens(t *testing.T) {
// 為每個 token 創建 JWT // 為每個 token 創建 JWT
claims1 := entity.Claims{ claims1 := entity.Claims{
Data: map[string]string{ Data: map[string]string{
"uid": "user123", "uid": "user123",
"jti": "jti1", "jti": "jti1",
"exp": fmt.Sprintf("%d", expires), "exp": fmt.Sprintf("%d", expires),
}, },
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
ID: "jti1", ID: "jti1",
@ -1418,9 +1418,9 @@ func TestTokenUseCase_BlacklistAllUserTokens(t *testing.T) {
claims2 := entity.Claims{ claims2 := entity.Claims{
Data: map[string]string{ Data: map[string]string{
"uid": "user123", "uid": "user123",
"jti": "jti2", "jti": "jti2",
"exp": fmt.Sprintf("%d", expires), "exp": fmt.Sprintf("%d", expires),
}, },
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
ID: "jti2", ID: "jti2",
@ -1495,21 +1495,21 @@ func TestTokenUseCase_BlacklistAllUserTokens(t *testing.T) {
TokenRepo: mockTokenRepo, TokenRepo: mockTokenRepo,
Config: &config.Config{ Config: &config.Config{
Token: struct { Token: struct {
AccessSecret string AccessSecret string
RefreshSecret string RefreshSecret string
AccessTokenExpiry time.Duration AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration RefreshTokenExpiry time.Duration
OneTimeTokenExpiry time.Duration OneTimeTokenExpiry time.Duration
MaxTokensPerUser int MaxTokensPerUser int
MaxTokensPerDevice int MaxTokensPerDevice int
}{ }{
AccessSecret: "test-secret", AccessSecret: "test-secret",
AccessTokenExpiry: time.Hour, AccessTokenExpiry: time.Hour,
RefreshTokenExpiry: time.Hour * 24, RefreshTokenExpiry: time.Hour * 24,
RefreshSecret: "refresh-secret", RefreshSecret: "refresh-secret",
OneTimeTokenExpiry: time.Minute * 5, OneTimeTokenExpiry: time.Minute * 5,
MaxTokensPerUser: 10, MaxTokensPerUser: 10,
MaxTokensPerDevice: 5, MaxTokensPerDevice: 5,
}, },
}, },
}) })
@ -1524,4 +1524,3 @@ func TestTokenUseCase_BlacklistAllUserTokens(t *testing.T) {
}) })
} }
} }

View File

@ -149,4 +149,3 @@ func (uc *userRoleUseCase) toResponse(userRole *entity.UserRole) *usecase.UserRo
UpdateTime: time.Unix(userRole.UpdateTime, 0).UTC().Format(time.RFC3339), UpdateTime: time.Unix(userRole.UpdateTime, 0).UTC().Format(time.RFC3339),
} }
} }

View File

@ -148,7 +148,7 @@ func TestUserRoleUseCase_Update(t *testing.T) {
Status: domain.RecordActive, Status: domain.RecordActive,
} }
mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000002").Return(role, nil) mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000002").Return(role, nil)
updatedUserRole := &entity.UserRole{ updatedUserRole := &entity.UserRole{
ID: bson.NewObjectID(), ID: bson.NewObjectID(),
Brand: "brand1", Brand: "brand1",
@ -354,7 +354,7 @@ func TestUserRoleUseCase_GetByRole(t *testing.T) {
Status: domain.RecordActive, Status: domain.RecordActive,
} }
mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil) mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
userRoles := []*entity.UserRole{ userRoles := []*entity.UserRole{
{ID: bson.NewObjectID(), UID: "user1", RoleID: "ROLE0000000001"}, {ID: bson.NewObjectID(), UID: "user1", RoleID: "ROLE0000000001"},
{ID: bson.NewObjectID(), UID: "user2", RoleID: "ROLE0000000001"}, {ID: bson.NewObjectID(), UID: "user2", RoleID: "ROLE0000000001"},