diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index e8e3c77..f51386d 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -58,15 +58,19 @@ jobs:
with:
go-version: '1.27'
+ - name: Read version
+ id: version
+ run: echo "value=$(cat GAHversion)" >> "$GITHUB_OUTPUT"
+
- name: Build
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
- run: go build -trimpath -ldflags "-s -w" -o GoAccountHub-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }} .
+ run: go build -trimpath -ldflags "-s -w" -o GoAccountHub-${{ steps.version.outputs.value }}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }} .
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
- name: GoAccountHub-${{ matrix.goos }}-${{ matrix.goarch }}
- path: GoAccountHub-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}
+ name: GoAccountHub-${{ steps.version.outputs.value }}-${{ matrix.goos }}-${{ matrix.goarch }}
+ path: GoAccountHub-${{ steps.version.outputs.value }}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}
diff --git a/GAHFrontend/package-lock.json b/GAHFrontend/package-lock.json
index 8412348..fc5c4e5 100644
--- a/GAHFrontend/package-lock.json
+++ b/GAHFrontend/package-lock.json
@@ -10,7 +10,9 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.14.5",
+ "monaco-editor": "^0.52.2",
"vue": "^3.5.41",
+ "vue-cookies": "^1.8.6",
"vue-router": "^5.3.1"
},
"devDependencies": {
@@ -1217,6 +1219,12 @@
"pathe": "^2.0.1"
}
},
+ "node_modules/monaco-editor": {
+ "version": "0.52.2",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
+ "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
+ "license": "MIT"
+ },
"node_modules/muggle-string": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
@@ -1603,6 +1611,12 @@
"integrity": "sha512-LwcxzeliO9fkQcpJG0PoX8X5kmAhKmH9wkpDLxNabwzkQ9Zeib2YVHwFV4pcWmMLfXVfjr/dSV+DaJ3cIPgSNA==",
"license": "MIT"
},
+ "node_modules/vue-cookies": {
+ "version": "1.8.6",
+ "resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.8.6.tgz",
+ "integrity": "sha512-e2kYaHj1Y/zVsBSM3KWlOoVJ5o3l4QZjytNU7xdCgmkw3521CMUerqHekBGZKXXC1oRxYljBeeOK2SCel6cKuw==",
+ "license": "MIT"
+ },
"node_modules/vue-router": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.3.1.tgz",
diff --git a/GAHFrontend/package.json b/GAHFrontend/package.json
index 3d57a4b..de3da5f 100644
--- a/GAHFrontend/package.json
+++ b/GAHFrontend/package.json
@@ -11,7 +11,9 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.14.5",
+ "monaco-editor": "^0.52.2",
"vue": "^3.5.41",
+ "vue-cookies": "^1.8.6",
"vue-router": "^5.3.1"
},
"devDependencies": {
diff --git a/GAHFrontend/src/components/component_test.vue b/GAHFrontend/src/components/component_test.vue
new file mode 100644
index 0000000..cc340bc
--- /dev/null
+++ b/GAHFrontend/src/components/component_test.vue
@@ -0,0 +1 @@
+
diff --git a/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue b/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue
new file mode 100644
index 0000000..4694858
--- /dev/null
+++ b/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue
@@ -0,0 +1,52 @@
+
+ emit('update:modelValue', val)">
+
+
+
+
+
+
+
+
+ 是否可以添加管理员
+ 是否可以删除管理员
+ 是否可以修改管理员
+ 是否可以获取管理员列表
+ 是否可以操作用户
+ 是否可以操作角色
+
+
+
+ 取消
+ 确定
+
+
+
+
+
diff --git a/GAHFrontend/src/components/customize/AddCharacterDialogFrom.vue b/GAHFrontend/src/components/customize/AddCharacterDialogFrom.vue
new file mode 100644
index 0000000..33255c6
--- /dev/null
+++ b/GAHFrontend/src/components/customize/AddCharacterDialogFrom.vue
@@ -0,0 +1,45 @@
+
+ emit('update:modelValue', val)">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
diff --git a/GAHFrontend/src/components/customize/AddUserDialogFrom.vue b/GAHFrontend/src/components/customize/AddUserDialogFrom.vue
new file mode 100644
index 0000000..f73c73a
--- /dev/null
+++ b/GAHFrontend/src/components/customize/AddUserDialogFrom.vue
@@ -0,0 +1,41 @@
+
+ emit('update:modelValue', val)">
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
diff --git a/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue b/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue
new file mode 100644
index 0000000..ed15606
--- /dev/null
+++ b/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue
@@ -0,0 +1,68 @@
+
+ emit('update:modelValue', val)">
+
+
+
+
+
+ 是否可以添加管理员
+ 是否可以删除管理员
+ 是否可以修改管理员
+ 是否可以获取管理员列表
+ 是否可以操作用户
+ 是否可以操作角色
+
+
+
+ 取消
+ 确定
+
+
+
+
+
diff --git a/GAHFrontend/src/components/customize/EditCharacterDialog.vue b/GAHFrontend/src/components/customize/EditCharacterDialog.vue
new file mode 100644
index 0000000..9e9aaab
--- /dev/null
+++ b/GAHFrontend/src/components/customize/EditCharacterDialog.vue
@@ -0,0 +1,62 @@
+
+ emit('update:modelValue', val)">
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
diff --git a/GAHFrontend/src/components/customize/EditUserDialog.vue b/GAHFrontend/src/components/customize/EditUserDialog.vue
new file mode 100644
index 0000000..6860646
--- /dev/null
+++ b/GAHFrontend/src/components/customize/EditUserDialog.vue
@@ -0,0 +1,60 @@
+
+ emit('update:modelValue', val)">
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
diff --git a/GAHFrontend/src/components/customize/MetaDataEditor.vue b/GAHFrontend/src/components/customize/MetaDataEditor.vue
new file mode 100644
index 0000000..911dbc4
--- /dev/null
+++ b/GAHFrontend/src/components/customize/MetaDataEditor.vue
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
diff --git a/GAHFrontend/src/components/login/login.vue b/GAHFrontend/src/components/login/login.vue
index e9fec19..5ef402c 100644
--- a/GAHFrontend/src/components/login/login.vue
+++ b/GAHFrontend/src/components/login/login.vue
@@ -36,7 +36,7 @@ import { reactive, ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { User, Lock } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
-import { post, get } from '../../lib/request'
+import { post } from '../../lib/request'
const router = useRouter()
const loginFormRef = ref()
@@ -62,7 +62,7 @@ const rules = {
// 已登录则直接跳转主页
onMounted(async () => {
try {
- await get('/admin/check_token', {}, { showError: false, autoRedirect401: false })
+ await post('/admin/check_token', {}, { showError: false, autoRedirect401: false })
router.push('/main/home')
} catch {
// 未登录,留在登录页
diff --git a/GAHFrontend/src/components/main/bar.vue b/GAHFrontend/src/components/main/bar.vue
index 3220f95..b3ac187 100644
--- a/GAHFrontend/src/components/main/bar.vue
+++ b/GAHFrontend/src/components/main/bar.vue
@@ -65,7 +65,8 @@
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Odometer, Fold, Expand, User, SetUp, Grid } from '@element-plus/icons-vue'
-import { get, post, put, del } from '../../lib/request'
+import { post, put, del } from '../../lib/request'
+import VueCookies from 'vue-cookies'
const route = useRoute()
const router = useRouter()
@@ -83,8 +84,8 @@ const handleLogout = async () => {
// 即使后端出错也继续清理并跳转
}
// 删除本地 cookie
- document.cookie = 'admin_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
- document.cookie = 'admin_name=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
+ VueCookies.remove('admin_token')
+ VueCookies.remove('admin_name')
router.push('/')
}
@@ -110,13 +111,13 @@ const canGetCharacter = computed(() => {
onMounted(()=>{
// Check Admin Token
- get('/admin/check_token').then(respond=>{
+ post('/admin/check_token').then(respond=>{
if(respond.code === 200){
}else{
}
})
// Get Admin Info
- get('/admin/info').then(respond=>{
+ post('/admin/info').then(respond=>{
if(respond.code === 200){
adminInfo.value = respond.data
}
diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue
index 068743e..79760ed 100644
--- a/GAHFrontend/src/components/main/page/admin.vue
+++ b/GAHFrontend/src/components/main/page/admin.vue
@@ -3,46 +3,338 @@
操作
-
-
+
+
权限筛选:
-
-
-
-
-
-
+
+
+
+
+
+
- 查询
- 添加管理员
+
+ 查询
+ 重置
+ 添加管理员
+
+
+
+
+ 批量操作
+
+ 已选中 {{ selectedIds.length }} 条
+
+ 取消选中
+ 删除选中管理员
+
+
+
+
管理员列表
-
-
+
+
+
+
+
+ 已加载全部 {{ adminList.length }} 条
+
+
+
+
+
+
+
+
diff --git a/GAHFrontend/src/components/main/page/character.vue b/GAHFrontend/src/components/main/page/character.vue
index ae105e0..074ade9 100644
--- a/GAHFrontend/src/components/main/page/character.vue
+++ b/GAHFrontend/src/components/main/page/character.vue
@@ -1,32 +1,394 @@
-
- 角色管理
-
+
+
+ 操作
+
+
+
+
+
+
+ 查询
+ 重置
+ 添加角色
+
+
+
+
+
+
+
+ 批量操作
+
+ 已选中 {{ selectedIds.length }} 条
+
+ 取消选中
+ 删除选中角色
+
+
+
+
+
+
+
+ 角色列表(共 {{ characterCount }} 个)
+
+
+
+
+
+
+
+
+ 已加载全部 {{ visibleList.length }} 条
+
+
+
+
+
+
+
+
+
\ No newline at end of file
+
diff --git a/GAHFrontend/src/components/main/page/home.vue b/GAHFrontend/src/components/main/page/home.vue
index 687d9e5..0bb6de0 100644
--- a/GAHFrontend/src/components/main/page/home.vue
+++ b/GAHFrontend/src/components/main/page/home.vue
@@ -40,7 +40,7 @@
\ No newline at end of file
+
diff --git a/GAHFrontend/src/lib/rangeAdmin.js b/GAHFrontend/src/lib/rangeAdmin.js
new file mode 100644
index 0000000..7def547
--- /dev/null
+++ b/GAHFrontend/src/lib/rangeAdmin.js
@@ -0,0 +1,12 @@
+import { post } from './request.js'
+
+const rangeAdmin = async (condition, start, length) => {
+ const res = await post('/admin/range', {
+ begin_table_id: start,
+ length: length,
+ search_condition: condition
+ })
+ return res.data
+}
+
+export default rangeAdmin
diff --git a/GAHFrontend/src/lib/rangeCharacter.js b/GAHFrontend/src/lib/rangeCharacter.js
new file mode 100644
index 0000000..ba5b4d1
--- /dev/null
+++ b/GAHFrontend/src/lib/rangeCharacter.js
@@ -0,0 +1,12 @@
+import { post } from './request.js'
+
+const rangeCharacter = async (condition, start, length) => {
+ const res = await post('/character/range', {
+ begin_table_id: start,
+ length: length,
+ search_condition: condition
+ })
+ return res.data
+}
+
+export default rangeCharacter
diff --git a/GAHFrontend/src/lib/rangeUser.js b/GAHFrontend/src/lib/rangeUser.js
new file mode 100644
index 0000000..b1f7a7c
--- /dev/null
+++ b/GAHFrontend/src/lib/rangeUser.js
@@ -0,0 +1,12 @@
+import { post } from './request.js'
+
+const rangeUser = async (condition, start, length) => {
+ const res = await post('/user/range', {
+ begin_table_id: start,
+ length: length,
+ search_condition: condition
+ })
+ return res.data
+}
+
+export default rangeUser
diff --git a/GAHFrontend/src/router/index.js b/GAHFrontend/src/router/index.js
index 5e034c8..1e91219 100644
--- a/GAHFrontend/src/router/index.js
+++ b/GAHFrontend/src/router/index.js
@@ -5,6 +5,7 @@ import Home from '../components/main/page/home.vue'
import Admin from '../components/main/page/admin.vue'
import User from '../components/main/page/user.vue'
import Character from '../components/main/page/character.vue'
+import ComponentTest from '../components/component_test.vue'
const routes = [
{
@@ -12,6 +13,11 @@ const routes = [
name: 'Login',
component: Login,
},
+ {
+ path: '/component',
+ name: 'ComponentTest',
+ component: () => import('../components/component_test.vue'),
+ },
{
path: '/main',
component: Bar,
diff --git a/GAHversion b/GAHversion
new file mode 100644
index 0000000..2c99d92
--- /dev/null
+++ b/GAHversion
@@ -0,0 +1 @@
+beta-0.1.0
\ No newline at end of file
diff --git a/README.md b/README.md
index 85f7de4..8bb8b08 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,92 @@
-# GoAccountHub
-A User Hub in Go
+GoAccountHub
+
+
+
+
+
+
+
+
+
+
+## 🌐Language
+[English](README.md) | [简体中文](README_zh-cn.md)
+
+## 📖 Introduction
+Go Account Hub,or GAH,is a user Hub in Go language,which can storage metadata of user and support multiple "sub user"(called "character" in this project), with a vue frontend management web page.
+
+This project need a PostgreSQL database to store.
+
+## 🛠️ Installation & Start
+**__Now it only can be install compiling.We plan to add a docker-compose file in the future.__**
+
+- 📦 Install
+ 1. Clone the repository.
+ 2. Install PostgreSQL database and Node.js.
+ 3. Type `go mod tidy` in `./GoAccountHub` to install go dependencies.
+ 4. Type `npm install` in `./GoAccountHub/GAHFrontend` to install vue dependencies.
+ 5. Compile the Go project in `./GoAccountHub`.Type `go build` to compile the project.
+- ▶️ Start
+ **__⚠️Attention: Before start the api server,you MUST to launch the Api server.And ALSO make sure there is `.env` file in the frontend root directory.Because when Api server start,it will change the `.env` file according to the config.__**
+
+ + 🖥️ Api Server
+ 1. Type this command to generate the setting config (default config name is `config.json`)
+ ```bash
+ gah generate
+ ```
+ 2. Type this command to set the root admin password:
+ ```bash
+ gah password
+ ```
+ **__⚠️Warning: The GAH `root` admin is the top privilege admin,which can do anything in the system.__**
+ 3. Open the Config File, to set the config:
+ ```json
+ {
+ "port": "8081",
+ "database_name": "GoAccountHub",
+ "database_host": "127.0.0.1",
+ "database_port": "5432",
+ "database_user": "postgres",
+ "database_password": "postgres",
+ "frontend_port": "8082",
+ "root_admin_password_hash": "a665a45920422f9d417......",
+ "root_admin_uu_hash": "0fbaf45ee863c0......",
+ "switch_config": {
+ "allow_multi_character": true,
+ "allow_admin_logout": false
+ }
+ }
+ ```
+ Description of the config:
+ - `port`: The port number of the server.
+ - `database_name`: The name of the database.
+ - `database_host`: The host of the database.
+ - `database_port`: The port number of the database.
+ - `database_user`: The username of the database.
+ - `database_password`: The password of the database.
+ - `frontend_port`: The port number of the frontend.It will write into the `.env` file when Api server start.
+
+ - `root_admin_password_hash`: The password hash of the root admin.Generated by Cil, do not need to set it.
+ - `root_admin_uu_hash`: The uu hash of the root admin.Generated by Cil, do not need to set it.
+
+ - `switch_config`: The switch config of the system.
+ - `switch_config.allow_multi_character`: Whether to allow multiple characters.
+ - `switch_config.allow_admin_logout`: Whether to allow admin logout(Development wrong,will be removed).
+ 4. Type this command to start the api server:
+ ```bash
+ gah start
+ ```
+ Now you can access the GAH api in `http://localhost:`.It will return:
+ ```json
+ {"code":200,"message":"Welcome to GoAccountHub"}
+ ```
+ + 🎨 Frontend Server
+ 1. Type this command to start the frontend server:
+ ```bash
+ npm run dev
+ ```
+ Now you can access the GAH frontend in `http://localhost:`.
+
+**__More Information,Please See The [`docs/api.md`](docs/api.md)__.**
diff --git a/README_zh-cn.md b/README_zh-cn.md
new file mode 100644
index 0000000..8ad18b2
--- /dev/null
+++ b/README_zh-cn.md
@@ -0,0 +1,92 @@
+GoAccountHub
+
+
+
+
+
+
+
+
+
+
+## 🌐Language
+[English](README.md) | [简体中文](README_zh-cn.md)
+
+## 📖 简介
+Go Account Hub(简称 GAH)是一个用 Go 语言编写的用户中心,可以存储用户的元数据,并支持多个「子用户」(在本项目中称为「角色」),同时提供一个 Vue 前端管理页面。
+
+本项目需要一个 PostgreSQL 数据库来存储数据。
+
+## 🛠️ 安装与启动
+**目前只能通过编译安装。我们计划在未来添加 docker-compose 文件。**
+
+- 📦 安装
+ 1. 克隆本仓库。
+ 2. 安装 PostgreSQL 数据库和 Node.js。
+ 3. 在 `./GoAccountHub` 目录下执行 `go mod tidy` 以安装 Go 依赖。
+ 4. 在 `./GoAccountHub/GAHFrontend` 目录下执行 `npm install` 以安装 Vue 依赖。
+ 5. 在 `./GoAccountHub` 目录下编译 Go 项目,执行 `go build` 进行编译。
+- ▶️ 启动
+ **⚠️注意:在启动 API 服务之前,你**必须**先启动 API 服务。同时请确保前端根目录下存在 `.env` 文件。因为 API 服务启动时会根据配置修改该 `.env` 文件。**
+
+ + 🖥️ Api 服务
+ 1. 执行以下命令生成配置文件(默认配置名为 `config.json`)
+ ```bash
+ gah generate
+ ```
+ 2. 执行以下命令设置 root 管理员密码:
+ ```bash
+ gah password
+ ```
+ **__⚠️警告:GAH 的 `root` 管理员是最高权限管理员,可以操作系统中的任何内容。__**
+ 3. 打开配置文件,设置配置项:
+ ```json
+ {
+ "port": "8081",
+ "database_name": "GoAccountHub",
+ "database_host": "127.0.0.1",
+ "database_port": "5432",
+ "database_user": "postgres",
+ "database_password": "postgres",
+ "frontend_port": "8082",
+ "root_admin_password_hash": "a665a45920422f9d417......",
+ "root_admin_uu_hash": "0fbaf45ee863c0......",
+ "switch_config": {
+ "allow_multi_character": true,
+ "allow_admin_logout": false
+ }
+ }
+ ```
+ 配置项说明:
+ - `port`:服务的端口号。
+ - `database_name`:数据库名称。
+ - `database_host`:数据库主机地址。
+ - `database_port`:数据库端口号。
+ - `database_user`:数据库用户名。
+ - `database_password`:数据库密码。
+ - `frontend_port`:前端端口号。API 服务启动时会写入 `.env` 文件。
+
+ - `root_admin_password_hash`:root 管理员的密码哈希。由 CLI 生成,无需手动设置。
+ - `root_admin_uu_hash`:root 管理员的 uu 哈希。由 CLI 生成,无需手动设置。
+
+ - `switch_config`:系统的开关配置。
+ - `switch_config.allow_multi_character`:是否允许多角色。
+ - `switch_config.allow_admin_logout`:是否允许管理员登出(开发有误,将会移除)。
+ 4. 执行以下命令启动 API 服务:
+ ```bash
+ gah start
+ ```
+ 现在你可以通过 `http://localhost:` 访问 GAH API。它将返回:
+ ```json
+ {"code":200,"message":"Welcome to GoAccountHub"}
+ ```
+ + 🎨 前端服务
+ 1. 执行以下命令启动前端服务:
+ ```bash
+ npm run dev
+ ```
+ 现在你可以通过 `http://localhost:` 访问 GAH 前端。
+
+**__更多信息,请参阅 [`docs/api_zh-cn.md`](docs/api_zh-cn.md)。__**
diff --git a/adminControllor/CharacterCount.go b/adminControllor/CharacterCount.go
new file mode 100644
index 0000000..66da2f5
--- /dev/null
+++ b/adminControllor/CharacterCount.go
@@ -0,0 +1,28 @@
+package adminControllor
+
+import (
+ "net/http"
+
+ "github.com/TrafficLight6/GoAccountHub/sqlTable"
+ "github.com/gin-gonic/gin"
+ "gorm.io/gorm"
+)
+
+func CharacterCount(c *gin.Context) {
+ //Get Db
+ db := c.Value("db").(*gorm.DB)
+
+ //Count Characters (soft-deleted rows are excluded automatically)
+ var characterCount int64
+ if err := db.Model(&sqlTable.Character{}).Count(&characterCount).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Failed To Count Characters"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "code": http.StatusOK,
+ "data": gin.H{
+ "character_count": characterCount,
+ },
+ })
+}
diff --git a/adminControllor/adminCount.go b/adminControllor/adminCount.go
new file mode 100644
index 0000000..35d88c6
--- /dev/null
+++ b/adminControllor/adminCount.go
@@ -0,0 +1,28 @@
+package adminControllor
+
+import (
+ "net/http"
+
+ "github.com/TrafficLight6/GoAccountHub/sqlTable"
+ "github.com/gin-gonic/gin"
+ "gorm.io/gorm"
+)
+
+func AdminCount(c *gin.Context) {
+ //Get Db
+ db := c.Value("db").(*gorm.DB)
+
+ //Count Admins (soft-deleted rows are excluded automatically)
+ var adminCount int64
+ if err := db.Model(&sqlTable.Admin{}).Count(&adminCount).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Failed To Count Admins"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "code": http.StatusOK,
+ "data": gin.H{
+ "admin_count": adminCount,
+ },
+ })
+}
diff --git a/adminControllor/adminLogin.go b/adminControllor/adminLogin.go
index f9249a8..1e041ac 100644
--- a/adminControllor/adminLogin.go
+++ b/adminControllor/adminLogin.go
@@ -44,10 +44,10 @@ func AdminLogin(c *gin.Context) {
//Set cookie (must be before c.JSON, otherwise headers are already flushed)
//admin_token 保持 httpOnly(JS 不可读,防 XSS);admin_name 仅用于前端展示,允许 JS 读取
if body.IsRemember {
- c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true)
+ c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, false)
c.SetCookie("admin_name", "root", 30*24*60*60, "/", "", false, false)
} else {
- c.SetCookie("admin_token", token, 0, "/", "", false, true)
+ c.SetCookie("admin_token", token, 0, "/", "", false, false)
c.SetCookie("admin_name", "root", 0, "/", "", false, false)
}
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "login success", "token": token})
@@ -76,10 +76,10 @@ func AdminLogin(c *gin.Context) {
//Set cookie (must be before c.JSON, otherwise headers are already flushed)
//admin_token 保持 httpOnly(JS 不可读,防 XSS);admin_name 仅用于前端展示,允许 JS 读取
if body.IsRemember {
- c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true)
+ c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, false)
c.SetCookie("admin_name", admin.Username, 30*24*60*60, "/", "", false, false)
} else {
- c.SetCookie("admin_token", token, 0, "/", "", false, true)
+ c.SetCookie("admin_token", token, 0, "/", "", false, false)
c.SetCookie("admin_name", admin.Username, 0, "/", "", false, false)
}
diff --git a/adminControllor/adminRange.go b/adminControllor/adminRange.go
index 68a601c..7c43d8f 100644
--- a/adminControllor/adminRange.go
+++ b/adminControllor/adminRange.go
@@ -18,9 +18,16 @@ type AdminRangeRequestBody struct {
type AdminSearchCondition struct {
//If The Field is Empty, It Means Ignore This Condition
- Username string `json:"username"`
- PasswordHash string `json:"password_hash"`
- UUHash string `json:"uu_hash"`
+ Username string `json:"username"`
+ UUHash string `json:"uu_hash"`
+
+ //Permission Filter (Bool, false means ignore)
+ CanAddAdmin bool `json:"can_add_admin"`
+ CanDeleteAdmin bool `json:"can_delete_admin"`
+ CanEditAdmin bool `json:"can_edit_admin"`
+ CanGetAdmin bool `json:"can_get_admin"`
+ CanOperateUser bool `json:"can_operate_user"`
+ CanOperateCharacter bool `json:"can_operate_character"`
}
func AdminRange(c *gin.Context) {
@@ -57,18 +64,35 @@ func AdminRange(c *gin.Context) {
conditions = append(conditions, "username LIKE ?")
args = append(args, "%"+condition.Username+"%")
}
- if condition.PasswordHash != "" {
- conditions = append(conditions, "password_hash LIKE ?")
- args = append(args, "%"+condition.PasswordHash+"%")
- }
if condition.UUHash != "" {
conditions = append(conditions, "uu_hash LIKE ?")
args = append(args, "%"+condition.UUHash+"%")
}
+ //Permission Filter: only checked (true) fields are applied, all combined with AND
+ if condition.CanAddAdmin {
+ conditions = append(conditions, "permission ->> 'can_add_admin' = 'true'")
+ }
+ if condition.CanDeleteAdmin {
+ conditions = append(conditions, "permission ->> 'can_delete_admin' = 'true'")
+ }
+ if condition.CanEditAdmin {
+ conditions = append(conditions, "permission ->> 'can_edit_admin' = 'true'")
+ }
+ if condition.CanGetAdmin {
+ conditions = append(conditions, "permission ->> 'can_get_admin' = 'true'")
+ }
+ if condition.CanOperateUser {
+ conditions = append(conditions, "permission ->> 'can_operate_user' = 'true'")
+ }
+ if condition.CanOperateCharacter {
+ conditions = append(conditions, "permission ->> 'can_operate_character' = 'true'")
+ }
query := db.Model(&sqlTable.Admin{})
if len(conditions) > 0 {
query = query.Where(strings.Join(conditions, " AND "), args...)
}
+ //Must Be Ordered, Otherwise Limit/Offset Follows Physical Row Order And Pages Are Not Stable
+ query = query.Order("id ASC")
//Search
var admins []sqlTable.Admin
if isReturnAll {
diff --git a/adminControllor/characterEdit.go b/adminControllor/characterEdit.go
index df9b0b9..6b65a68 100644
--- a/adminControllor/characterEdit.go
+++ b/adminControllor/characterEdit.go
@@ -48,6 +48,12 @@ func CharacterEdit(c *gin.Context) {
c.Abort()
return
}
+ //Check is Same Name Character
+ if user.UUHash == characterOld.UserUUHash {
+ c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Same Name Character Can Not Be Edited, Please Trun to User Edit Api"})
+ c.Abort()
+ return
+ }
//Update Character
characterNew := characterOld
if body.CharacterName != "" {
diff --git a/adminControllor/characterRange.go b/adminControllor/characterRange.go
index ad63cff..5ed2a66 100644
--- a/adminControllor/characterRange.go
+++ b/adminControllor/characterRange.go
@@ -19,7 +19,6 @@ type CharacterRangeRequestBody struct {
type CharacterSearchCondition struct {
//If The Field is Empty, It Means Ignore This Condition
CharacterName string `json:"character_name"`
- PasswordHash string `json:"password_hash"`
UserUUHash string `json:"user_uu_hash"`
UUHash string `json:"uu_hash"`
}
@@ -52,10 +51,6 @@ func CharacterRange(c *gin.Context) {
conditions = append(conditions, "character_name LIKE ?")
args = append(args, "%"+condition.CharacterName+"%")
}
- if condition.PasswordHash != "" {
- conditions = append(conditions, "password_hash LIKE ?")
- args = append(args, "%"+condition.PasswordHash+"%")
- }
if condition.UserUUHash != "" {
conditions = append(conditions, "user_uu_hash LIKE ?")
args = append(args, "%"+condition.UserUUHash+"%")
@@ -68,6 +63,8 @@ func CharacterRange(c *gin.Context) {
if len(conditions) > 0 {
query = query.Where(strings.Join(conditions, " AND "), args...)
}
+ //Must Be Ordered, Otherwise Limit/Offset Follows Physical Row Order And Pages Are Not Stable
+ query = query.Order("id ASC")
//Search
var characters []sqlTable.Character
if isReturnAll {
diff --git a/adminControllor/userCount.go b/adminControllor/userCount.go
new file mode 100644
index 0000000..3f33631
--- /dev/null
+++ b/adminControllor/userCount.go
@@ -0,0 +1,28 @@
+package adminControllor
+
+import (
+ "net/http"
+
+ "github.com/TrafficLight6/GoAccountHub/sqlTable"
+ "github.com/gin-gonic/gin"
+ "gorm.io/gorm"
+)
+
+func UserCount(c *gin.Context) {
+ //Get Db
+ db := c.Value("db").(*gorm.DB)
+
+ //Count Users (soft-deleted rows are excluded automatically)
+ var userCount int64
+ if err := db.Model(&sqlTable.User{}).Count(&userCount).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Failed To Count Users"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "code": http.StatusOK,
+ "data": gin.H{
+ "user_count": userCount,
+ },
+ })
+}
diff --git a/adminControllor/userEdit.go b/adminControllor/userEdit.go
index d948f97..697b831 100644
--- a/adminControllor/userEdit.go
+++ b/adminControllor/userEdit.go
@@ -26,12 +26,6 @@ func UserEdit(c *gin.Context) {
}
//Get Db
db := c.Value("db").(*gorm.DB)
- //Check Username or Password is Empty
- if body.Username == "" || body.Password == "" {
- c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Username or Password Is Empty"})
- c.Abort()
- return
- }
//Check User Exist
var userOld sqlTable.User
if err := db.Where("uu_hash = ?", body.UUHash).First(&userOld).Error; err != nil {
@@ -39,7 +33,7 @@ func UserEdit(c *gin.Context) {
c.Abort()
return
}
- //Edit User
+ //Edit User (Empty Field Means Keep The Old Value)
userNew := userOld
if body.Username != "" {
userNew.Username = body.Username
diff --git a/adminControllor/userRanger.go b/adminControllor/userRanger.go
index c18b681..c6545e5 100644
--- a/adminControllor/userRanger.go
+++ b/adminControllor/userRanger.go
@@ -18,9 +18,8 @@ type UserRangeRequestBody struct {
type UserSearchCondition struct {
//If The Field is Empty, It Means Ignore This Condition
- Username string `json:"username"`
- PasswordHash string `json:"password_hash"`
- UUHash string `json:"uu_hash"`
+ Username string `json:"username"`
+ UUHash string `json:"uu_hash"`
}
func UserRange(c *gin.Context) {
@@ -57,10 +56,6 @@ func UserRange(c *gin.Context) {
conditions = append(conditions, "username LIKE ?")
args = append(args, "%"+condition.Username+"%")
}
- if condition.PasswordHash != "" {
- conditions = append(conditions, "password_hash LIKE ?")
- args = append(args, "%"+condition.PasswordHash+"%")
- }
if condition.UUHash != "" {
conditions = append(conditions, "uu_hash LIKE ?")
args = append(args, "%"+condition.UUHash+"%")
@@ -69,6 +64,8 @@ func UserRange(c *gin.Context) {
if len(conditions) > 0 {
query = query.Where(strings.Join(conditions, " AND "), args...)
}
+ //Must Be Ordered, Otherwise Limit/Offset Follows Physical Row Order And Pages Are Not Stable
+ query = query.Order("id ASC")
//Search
var users []sqlTable.User
if isReturnAll {
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 0000000..fd3622f
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,782 @@
+# 🚀 GoAccountHub API Reference
+**__Generated by Ai,If there is any error,please report it in the issue tracker.__**
+
+## 🌐Language
+[English](api.md) | [简体中文](api_zh-cn.md)
+
+REST API for **GoAccountHub (GAH)** — a user hub written in Go that stores user metadata and supports multiple sub-users (called **characters**).
+
+- **Base URL:** `http://:`
+- **API version prefix:** `/api/v1`
+- **Content type:** `application/json` (request bodies are JSON, including `DELETE` / `PUT`)
+- **Auth model:** cookie based (`admin_token` for admins, `app_key` for third-party apps) + a login `token` returned by login for app users
+
+## 📑 Table of Contents
+
+- [Conventions](#-conventions)
+- [Cookie & Auth Model](#-cookie--auth-model)
+- [Permission Model](#-permission-model)
+- [Quick Reference](#-quick-reference)
+- [🌐 Root](#-root)
+- [🛡️ Admin API](#️-admin-api)
+- [📱 App API](#-app-api)
+
+## 🧭 Conventions
+
+| Topic | Rule |
+| --- | --- |
+| Success | HTTP `200` with `{"code": 200, ...}` |
+| Error | Non-2xx status with `{"code": , "error": ""}` (some endpoints use `"message"` instead of `"error"`) |
+| Booleans | Empty / `false` search fields are ignored |
+| Pagination | `begin_table_id` starts at **1**; `length = -1` means **return all**; `offset = (begin_table_id - 1) * length` |
+| Object fields | `User` / `Character` / `Admin` objects are serialized with **PascalCase** keys (e.g. `Username`, `UUHash`, `MetaData`); `Permission` uses **snake_case** (e.g. `can_add_admin`) |
+
+## 🍪 Cookie & Auth Model
+
+| Cookie | Set by | Used by | Meaning |
+| --- | --- | --- | --- |
+| `admin_token` | `POST /api/v1/admin/login` | All `/api/v1/*` admin endpoints | Admin session token |
+| `admin_name` | `POST /api/v1/admin/login` | Frontend display only | Current admin username |
+| `app_key` | External (issued out-of-band) | All `/api/v1/app/*` endpoints **except** the two `metadata` endpoints | Third-party application key |
+
+> `is_remember = true` → cookie/session lasts **30 days**; otherwise **1 hour** (session cookie for the admin token).
+
+## 🔑 Permission Model
+
+Normal admins carry a `permission` object:
+
+```json
+{
+ "can_add_admin": false,
+ "can_delete_admin": false,
+ "can_edit_admin": false,
+ "can_get_admin": false,
+ "can_operate_user": false,
+ "can_operate_character": false
+}
+```
+
+- The built-in `root` admin is stored in the config file (not in the database) and **bypasses all permission checks**.
+- Missing or `false` permission → HTTP `403` `Permission Denied`.
+- Invalid / expired `admin_token` → HTTP `401`.
+
+| Permission | Grants access to |
+| --- | --- |
+| `can_get_admin` | `POST /admin/count`, `POST /admin/get`, `POST /admin/range` |
+| `can_add_admin` | `POST /admin/add` |
+| `can_delete_admin` | `DELETE /admin/delete` |
+| `can_edit_admin` | `PUT /admin/edit` |
+| `can_operate_user` | All `/api/v1/user/*` admin endpoints |
+| `can_operate_character` | All `/api/v1/character/*` admin endpoints |
+
+## 📋 Quick Reference
+
+| Method | Path | 🍪 Cookie | 🔑 Permission |
+| --- | --- | --- | --- |
+| `GET` | `/` | ❌ | — |
+| `POST` | `/api/v1/admin/login` | ❌ | — |
+| `DELETE` | `/api/v1/admin/logout` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/info` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/admin/check_token` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/admin/info` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/admin/count` | ✅ `admin_token` | `can_get_admin` |
+| `POST` | `/api/v1/admin/add` | ✅ `admin_token` | `can_add_admin` |
+| `DELETE` | `/api/v1/admin/delete` | ✅ `admin_token` | `can_delete_admin` |
+| `PUT` | `/api/v1/admin/edit` | ✅ `admin_token` | `can_edit_admin` |
+| `POST` | `/api/v1/admin/get` | ✅ `admin_token` | `can_get_admin` |
+| `POST` | `/api/v1/admin/range` | ✅ `admin_token` | `can_get_admin` |
+| `POST` | `/api/v1/user/count` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/user/add` | ✅ `admin_token` | `can_operate_user` |
+| `DELETE` | `/api/v1/user/delete` | ✅ `admin_token` | `can_operate_user` |
+| `PUT` | `/api/v1/user/edit` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/user/get` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/user/range` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/character/count` | ✅ `admin_token` | `can_operate_character` + multi-character switch |
+| `POST` | `/api/v1/character/add` | ✅ `admin_token` | `can_operate_character` + multi-character switch |
+| `DELETE` | `/api/v1/character/delete` | ✅ `admin_token` | `can_operate_character` + multi-character switch |
+| `PUT` | `/api/v1/character/edit` | ✅ `admin_token` | `can_operate_character` + multi-character switch |
+| `POST` | `/api/v1/character/get` | ✅ `admin_token` | `can_operate_character` + multi-character switch |
+| `POST` | `/api/v1/character/range` | ✅ `admin_token` | `can_operate_character` + multi-character switch |
+| `POST` | `/api/v1/app/user/login` | ✅ `app_key` | — |
+| `DELETE` | `/api/v1/app/user/logout` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/add` | ✅ `app_key` | — |
+| `DELETE` | `/api/v1/app/user/delete` | ✅ `app_key` | — |
+| `PUT` | `/api/v1/app/user/edit` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/get` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/range` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/character/add` | ✅ `app_key` | — |
+| `DELETE` | `/api/v1/app/character/delete` | ✅ `app_key` | — |
+| `PUT` | `/api/v1/app/character/edit` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/character/get` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/character/range` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/get/metadata` | ❌ | — |
+| `POST` | `/api/v1/app/character/get/metadata` | ❌ | — |
+
+---
+
+## 🌐 Root
+
+### `GET` `/`
+
+Health / welcome check.
+
+- 🍪 **Cookie required:** No
+- 🔑 **Permission required:** —
+
+**Response**
+
+```json
+{ "code": 200, "message": "Welcome to GoAccountHub" }
+```
+
+---
+
+## 🛡️ Admin API
+
+Service-side management API. After login, the browser holds the `admin_token` cookie.
+
+### 🔐 Auth
+
+#### `POST` `/api/v1/admin/login`
+
+Log in as `root` or a normal admin. Sets the `admin_token` (and `admin_name`) cookie on success.
+
+- 🍪 **Cookie required:** No
+- 🔑 **Permission required:** —
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | Admin username (`root` for the root admin) |
+| `password` | string | ✅ | Plaintext password (server hashes it with SHA-256) |
+| `is_remember` | bool | ❌ | `true` = 30-day session, `false`/omitted = 1-hour session |
+
+```json
+{ "username": "root", "password": "secret", "is_remember": true }
+```
+
+**Response**
+
+```json
+{ "code": 200, "message": "login success", "token": "", "admin": "root" }
+```
+
+> Invalid credentials → `400` `{"code":400,"message":"invalid credentials"}`.
+
+#### `DELETE` `/api/v1/admin/logout`
+
+Invalidate the current `admin_token` and delete it from the database.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** —
+
+**Request Body:** none
+
+**Response**
+
+```json
+{ "code": 200, "message": "Logout success" }
+```
+
+#### `POST` `/api/v1/info`
+
+Dashboard statistics for any logged-in admin.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** —
+
+**Request Body:** none
+
+**Response**
+
+```json
+{
+ "code": 200,
+ "data": {
+ "user_count": 10,
+ "character_count": 25,
+ "total_token_count": 7,
+ "user_token_count": 5,
+ "admin_token_count": 2,
+ "admin_count": 3
+ }
+}
+```
+
+> `admin_count` includes the root admin when configured.
+
+#### `POST` `/api/v1/admin/check_token`
+
+Validate the current `admin_token`.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** —
+
+**Request Body:** none
+
+**Response**
+
+```json
+{ "code": 200, "message": "admin_token valid" }
+```
+
+> Missing/invalid token → `400`.
+
+#### `POST` `/api/v1/admin/info`
+
+Return the current admin's identity and permissions.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** —
+
+**Request Body:** none
+
+**Response (normal admin)**
+
+```json
+{
+ "code": 200,
+ "message": "Admin Info",
+ "data": {
+ "username": "alice",
+ "uu_hash": "",
+ "is_root": false,
+ "permission": { "can_add_admin": true, "can_operate_user": true }
+ }
+}
+```
+
+**Response (root admin)** — `is_root: true` with all permissions `true`.
+
+### 👤 Admin Management
+
+#### `POST` `/api/v1/admin/count`
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_get_admin`
+- **Request Body:** none
+
+```json
+{ "code": 200, "data": { "admin_count": 3 } }
+```
+
+#### `POST` `/api/v1/admin/add`
+
+Create a new (non-root) admin.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_add_admin`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | Must not be `root` and must be unique |
+| `password` | string | ✅ | Plaintext password |
+| `permission` | object | ✅ | Permission map (see [Permission Model](#-permission-model)) |
+
+```json
+{
+ "username": "alice",
+ "password": "p@ssw0rd",
+ "permission": { "can_add_admin": true, "can_operate_user": true }
+}
+```
+
+**Response**
+
+```json
+{ "code": 200, "message": "Admin Added", "admin": { "ID": 2, "Username": "alice", "UUHash": "", "Permission": { } } }
+```
+
+> Username `root` is rejected; duplicate username or empty username/password → `400`.
+
+#### `DELETE` `/api/v1/admin/delete`
+
+Delete an admin by `uu_hash`.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_delete_admin`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | Target admin `uu_hash` |
+
+```json
+{ "uu_hash": "" }
+```
+
+**Response**
+
+```json
+{ "code": 200, "message": "Delete Admin Success" }
+```
+
+> The root admin cannot be deleted, and an admin cannot delete itself → `400`.
+
+#### `PUT` `/api/v1/admin/edit`
+
+Edit an admin's password and/or permissions.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_edit_admin`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | Target admin `uu_hash` |
+| `admin_info.password` | string | ❌ | New password; empty = keep current |
+| `admin_info.permission` | object | ❌ | New permission map |
+
+```json
+{
+ "uu_hash": "",
+ "admin_info": {
+ "password": "new-password",
+ "permission": { "can_operate_user": true }
+ }
+}
+```
+
+**Response**
+
+```json
+{ "code": 200, "message": "Success" }
+```
+
+> The root admin cannot be edited, and an admin cannot edit itself → `400`.
+
+#### `POST` `/api/v1/admin/get`
+
+Fetch a single admin by `uu_hash`.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_get_admin`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | Target admin `uu_hash` |
+
+**Response (normal admin)**
+
+```json
+{ "code": 200, "admin": { "ID": 2, "Username": "alice", "UUHash": "", "Permission": { } }, "is_root": false }
+```
+
+**Response (root admin)** — `{ "code": 200, "admin": null, "is_root": true }`
+Not found → `404`.
+
+#### `POST` `/api/v1/admin/range`
+
+Paginated / filtered admin list. **The root admin is not included.**
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_get_admin`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `begin_table_id` | int | ✅ | Page index, starts at `1` (must be `>= 0`) |
+| `length` | int | ✅ | Page size; `-1` = return all (must be `>= -1`) |
+| `search_condition` | object | ❌ | Filters below (empty fields ignored, combined with `AND`) |
+
+**`search_condition`**
+
+| Field | Type | Description |
+| --- | --- | --- |
+| `username` | string | Fuzzy match (`LIKE %value%`) |
+| `uu_hash` | string | Fuzzy match (`LIKE %value%`) |
+| `can_add_admin` | bool | Only admins **having** this permission when `true` |
+| `can_delete_admin` | bool | Same as above |
+| `can_edit_admin` | bool | Same as above |
+| `can_get_admin` | bool | Same as above |
+| `can_operate_user` | bool | Same as above |
+| `can_operate_character` | bool | Same as above |
+
+```json
+{
+ "begin_table_id": 1,
+ "length": 20,
+ "search_condition": { "username": "ali", "can_get_admin": true }
+}
+```
+
+**Response**
+
+```json
+{ "code": 200, "message": "Success(not include root admin)", "data": [ { "ID": 2, "Username": "alice" } ] }
+```
+
+### 🧑 User Management
+
+#### `POST` `/api/v1/user/count`
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_user`
+- **Request Body:** none
+
+```json
+{ "code": 200, "data": { "user_count": 10 } }
+```
+
+#### `POST` `/api/v1/user/add`
+
+Create a user. A **same-name character** is created automatically.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_user`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | Unique |
+| `password` | string | ✅ | Plaintext password |
+| `meta_data` | string | ❌ | Arbitrary metadata (JSON / XML / YAML / plain text) |
+
+**Response**
+
+```json
+{ "code": 200, "message": "User Added Successfully,And Same Name Character Added Successfully", "user": { "ID": 1, "Username": "bob", "UUHash": "", "MetaData": "" } }
+```
+
+#### `DELETE` `/api/v1/user/delete`
+
+Delete a user **and all characters belonging to it**.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_user`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | Target user `uu_hash` |
+
+**Response**
+
+```json
+{ "code": 200, "message": "User Deleted" }
+```
+
+#### `PUT` `/api/v1/user/edit`
+
+Edit a user. Empty fields keep their current value.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_user`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | Target user `uu_hash` |
+| `username` | string | ❌ | New username (also renames the same-name character) |
+| `password` | string | ❌ | New password |
+| `meta_data` | string | ❌ | New metadata |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Success" }
+```
+
+#### `POST` `/api/v1/user/get`
+
+Fetch a single user plus the number of characters it owns.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_user`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | Target user `uu_hash` |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Success", "user": { "ID": 1, "Username": "bob" }, "character_number": 2 }
+```
+
+#### `POST` `/api/v1/user/range`
+
+Paginated / filtered user list.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_user`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `begin_table_id` | int | ✅ | Page index, starts at `1` |
+| `length` | int | ✅ | Page size; `-1` = return all |
+| `search_condition` | object | ❌ | Filters below |
+
+**`search_condition`**
+
+| Field | Type | Description |
+| --- | --- | --- |
+| `username` | string | Fuzzy match (`LIKE %value%`) |
+| `uu_hash` | string | Fuzzy match (`LIKE %value%`) |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Success", "data": [ { "ID": 1, "Username": "bob" } ] }
+```
+
+### 🎭 Character Management
+
+> ⚠️ These endpoints (and the whole `/api/v1/app/*` group, see below) are only reachable when the config switch **`allow_multi_character`** is `true`. Otherwise → `403` `Server Not Allow Multi Character`.
+
+#### `POST` `/api/v1/character/count`
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_character`
+
+```json
+{ "code": 200, "data": { "character_count": 25 } }
+```
+
+#### `POST` `/api/v1/character/add`
+
+Add a character under an existing user.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_character`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `character_name` | string | ✅ | Globally unique |
+| `password` | string | ✅ | Plaintext password |
+| `user_uu_hash` | string | ✅ | Owner user `uu_hash` (must exist) |
+| `meta_data` | string | ❌ | Arbitrary metadata |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Character Added Successfully", "character": { "ID": 5, "CharacterName": "bob_alt", "UserUUHash": "", "UUHash": "" } }
+```
+
+#### `DELETE` `/api/v1/character/delete`
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_character`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `user_uu_hash` | string | ✅ | Owner user `uu_hash` |
+| `character_uu_hash` | string | ✅ | Target character `uu_hash` |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Character Deleted" }
+```
+
+> The same-name character (`character_uu_hash == user_uu_hash`) cannot be deleted here; delete the user instead.
+
+#### `PUT` `/api/v1/character/edit`
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_character`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `user_uu_hash` | string | ✅ | Owner user `uu_hash` |
+| `character_uu_hash` | string | ✅ | Target character `uu_hash` |
+| `character_name` | string | ❌ | New name (must not clash) |
+| `password` | string | ❌ | New password |
+| `meta_data` | string | ❌ | New metadata |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Edit Character Success" }
+```
+
+> The same-name character must be edited through the user endpoints.
+
+#### `POST` `/api/v1/character/get`
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_character`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `user_uu_hash` | string | ✅ | Owner user `uu_hash` |
+| `character_uu_hash` | string | ✅ | Target character `uu_hash` |
+
+**Response**
+
+```json
+{ "code": 200, "character": { "ID": 5, "CharacterName": "bob_alt", "UserUUHash": "" } }
+```
+
+#### `POST` `/api/v1/character/range`
+
+Paginated / filtered character list.
+
+- 🍪 **Cookie required:** Yes — `admin_token`
+- 🔑 **Permission required:** `can_operate_character`
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `begin_table_id` | int | ✅ | Page index, starts at `1` |
+| `length` | int | ✅ | Page size; `-1` = return all |
+| `search_condition` | object | ❌ | Filters below |
+
+**`search_condition`**
+
+| Field | Type | Description |
+| --- | --- | --- |
+| `character_name` | string | Fuzzy match (`LIKE %value%`) |
+| `user_uu_hash` | string | Fuzzy match (`LIKE %value%`) |
+| `uu_hash` | string | Fuzzy match (`LIKE %value%`) |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Success", "data": [ { "ID": 5, "CharacterName": "bob_alt" } ] }
+```
+
+---
+
+## 📱 App API
+
+Third-party application API under `/api/v1/app`. All endpoints require the **`app_key`** cookie — except the two `metadata` endpoints which authenticate with the login `token` in the body.
+
+> ⚠️ Because of group middleware inheritance, **all** `/api/v1/app/*` endpoints are also gated by the **`allow_multi_character`** switch (including user endpoints).
+
+### 🔐 Auth
+
+#### `POST` `/api/v1/app/user/login`
+
+Log in a user with a specific character. Returns a `token` (used for the app user's own operations and the metadata endpoints).
+
+- 🍪 **Cookie required:** Yes — `app_key`
+- 🔑 **Permission required:** —
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | User name |
+| `character_name` | string | ✅ | Character belonging to the user |
+| `password` | string | ✅ | User password (plaintext) |
+| `is_remember` | bool | ❌ | `true` = 30-day token, otherwise 1 hour |
+
+```json
+{ "username": "bob", "character_name": "bob", "password": "secret", "is_remember": true }
+```
+
+**Response**
+
+```json
+{ "code": 200, "message": "Login success", "token": "" }
+```
+
+> Unknown user / wrong password / unknown character name → `400`.
+
+#### `DELETE` `/api/v1/app/user/logout`
+
+Invalidate a user `token`.
+
+- 🍪 **Cookie required:** Yes — `app_key`
+- 🔑 **Permission required:** —
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `token` | string | ✅ | Token returned by app login |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Logout success" }
+```
+
+### 🧑 User Endpoints (App)
+
+Identical handlers to their admin counterparts, but guarded by `app_key` instead of `admin_token` + permissions.
+
+| Method | Path | 🍪 Cookie | Body |
+| --- | --- | --- | --- |
+| `POST` | `/api/v1/app/user/add` | `app_key` | `username`, `password`, `meta_data` |
+| `DELETE` | `/api/v1/app/user/delete` | `app_key` | `uu_hash` |
+| `PUT` | `/api/v1/app/user/edit` | `app_key` | `uu_hash`, `username?`, `password?`, `meta_data?` |
+| `POST` | `/api/v1/app/user/get` | `app_key` | `uu_hash` |
+| `POST` | `/api/v1/app/user/range` | `app_key` | `begin_table_id`, `length`, `search_condition{ username, uu_hash }` |
+
+### 🎭 Character Endpoints (App)
+
+| Method | Path | 🍪 Cookie | Body |
+| --- | --- | --- | --- |
+| `POST` | `/api/v1/app/character/add` | `app_key` | `character_name`, `password`, `user_uu_hash`, `meta_data` |
+| `DELETE` | `/api/v1/app/character/delete` | `app_key` | `user_uu_hash`, `character_uu_hash` |
+| `PUT` | `/api/v1/app/character/edit` | `app_key` | `user_uu_hash`, `character_uu_hash`, `character_name?`, `password?`, `meta_data?` |
+| `POST` | `/api/v1/app/character/get` | `app_key` | `user_uu_hash`, `character_uu_hash` |
+| `POST` | `/api/v1/app/character/range` | `app_key` | `begin_table_id`, `length`, `search_condition{ character_name, user_uu_hash, uu_hash }` |
+
+### 📦 Metadata Endpoints
+
+> 🍪 These two endpoints do **not** require `app_key`; they authenticate with the app user `token` in the body.
+
+#### `POST` `/api/v1/app/user/get/metadata`
+
+Return the metadata of the user owning the token.
+
+- 🍪 **Cookie required:** No
+- 🔑 **Permission required:** —
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `token` | string | ✅ | App user token |
+
+**Response**
+
+```json
+{ "code": 200, "message": "Success", "meta_data": "" }
+```
+
+#### `POST` `/api/v1/app/character/get/metadata`
+
+Return the metadata of the character owning the token.
+
+- 🍪 **Cookie required:** No
+- 🔑 **Permission required:** —
+
+**Request Body**
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `token` | string | ✅ | App user token |
+
+**Response**
+
+```json
+{ "code": 200, "message": "success", "meta_data": "" }
+```
diff --git a/docs/api_zh-cn.md b/docs/api_zh-cn.md
new file mode 100644
index 0000000..15f224b
--- /dev/null
+++ b/docs/api_zh-cn.md
@@ -0,0 +1,782 @@
+# 🚀 GoAccountHub API 参考
+**__由 AI 生成,如有错误,请在 issue 中反馈。__**
+
+## 🌐Language
+[English](api.md) | [简体中文](api_zh-cn.md)
+
+**GoAccountHub (GAH)** 的 REST API —— 一个用 Go 编写的用户中心,可存储用户元数据,并支持多个子用户(在本项目中称为「角色」)。
+
+- **基础 URL:** `http://:`
+- **API 版本前缀:** `/api/v1`
+- **内容类型:** `application/json`(请求体均为 JSON,包括 `DELETE` / `PUT`)
+- **鉴权模型:** 基于 Cookie(管理员使用 `admin_token`,第三方应用使用 `app_key`)+ 应用用户登录后返回的 `token`
+
+## 📑 目录
+
+- [约定](#-约定)
+- [Cookie 与鉴权模型](#-cookie-与鉴权模型)
+- [权限模型](#-权限模型)
+- [快速参考](#-快速参考)
+- [🌐 根路由](#-根路由)
+- [🛡️ 管理员 API](#️-管理员-api)
+- [📱 应用 API](#-应用-api)
+
+## 🧭 约定
+
+| 主题 | 规则 |
+| --- | --- |
+| 成功 | HTTP `200`,响应体为 `{"code": 200, ...}` |
+| 错误 | 非 2xx 状态码,响应体为 `{"code": , "error": ""}`(部分端点使用 `"message"` 而非 `"error"`) |
+| 布尔条件 | 搜索条件中为空 / `false` 的字段会被忽略 |
+| 分页 | `begin_table_id` 从 **1** 开始;`length = -1` 表示**返回全部**;`offset = (begin_table_id - 1) * length` |
+| 对象字段 | `User` / `Character` / `Admin` 对象序列化时使用 **PascalCase** 键名(如 `Username`、`UUHash`、`MetaData`);`Permission` 使用 **snake_case**(如 `can_add_admin`) |
+
+## 🍪 Cookie 与鉴权模型
+
+| Cookie | 设置方 | 使用者 | 含义 |
+| --- | --- | --- | --- |
+| `admin_token` | `POST /api/v1/admin/login` | 所有 `/api/v1/*` 管理员端点 | 管理员会话令牌 |
+| `admin_name` | `POST /api/v1/admin/login` | 仅前端展示 | 当前管理员用户名 |
+| `app_key` | 外部(线下发放) | 所有 `/api/v1/app/*` 端点,**两个 `metadata` 端点除外** | 第三方应用密钥 |
+
+> `is_remember = true` → Cookie / 会话有效期 **30 天**;否则为 **1 小时**(管理员令牌为会话 Cookie)。
+
+## 🔑 权限模型
+
+普通管理员携带一个 `permission` 对象:
+
+```json
+{
+ "can_add_admin": false,
+ "can_delete_admin": false,
+ "can_edit_admin": false,
+ "can_get_admin": false,
+ "can_operate_user": false,
+ "can_operate_character": false
+}
+```
+
+- 内置的 `root` 管理员存储于配置文件中(而非数据库),**绕过所有权限检查**。
+- 权限字段缺失或为 `false` → HTTP `403` `Permission Denied`。
+- `admin_token` 无效 / 过期 → HTTP `401`。
+
+| 权限 | 可访问的端点 |
+| --- | --- |
+| `can_get_admin` | `POST /admin/count`、`POST /admin/get`、`POST /admin/range` |
+| `can_add_admin` | `POST /admin/add` |
+| `can_delete_admin` | `DELETE /admin/delete` |
+| `can_edit_admin` | `PUT /admin/edit` |
+| `can_operate_user` | 所有 `/api/v1/user/*` 管理员端点 |
+| `can_operate_character` | 所有 `/api/v1/character/*` 管理员端点 |
+
+## 📋 快速参考
+
+| 方法 | 路径 | 🍪 Cookie | 🔑 权限 |
+| --- | --- | --- | --- |
+| `GET` | `/` | ❌ | — |
+| `POST` | `/api/v1/admin/login` | ❌ | — |
+| `DELETE` | `/api/v1/admin/logout` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/info` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/admin/check_token` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/admin/info` | ✅ `admin_token` | — |
+| `POST` | `/api/v1/admin/count` | ✅ `admin_token` | `can_get_admin` |
+| `POST` | `/api/v1/admin/add` | ✅ `admin_token` | `can_add_admin` |
+| `DELETE` | `/api/v1/admin/delete` | ✅ `admin_token` | `can_delete_admin` |
+| `PUT` | `/api/v1/admin/edit` | ✅ `admin_token` | `can_edit_admin` |
+| `POST` | `/api/v1/admin/get` | ✅ `admin_token` | `can_get_admin` |
+| `POST` | `/api/v1/admin/range` | ✅ `admin_token` | `can_get_admin` |
+| `POST` | `/api/v1/user/count` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/user/add` | ✅ `admin_token` | `can_operate_user` |
+| `DELETE` | `/api/v1/user/delete` | ✅ `admin_token` | `can_operate_user` |
+| `PUT` | `/api/v1/user/edit` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/user/get` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/user/range` | ✅ `admin_token` | `can_operate_user` |
+| `POST` | `/api/v1/character/count` | ✅ `admin_token` | `can_operate_character` + 多角色开关 |
+| `POST` | `/api/v1/character/add` | ✅ `admin_token` | `can_operate_character` + 多角色开关 |
+| `DELETE` | `/api/v1/character/delete` | ✅ `admin_token` | `can_operate_character` + 多角色开关 |
+| `PUT` | `/api/v1/character/edit` | ✅ `admin_token` | `can_operate_character` + 多角色开关 |
+| `POST` | `/api/v1/character/get` | ✅ `admin_token` | `can_operate_character` + 多角色开关 |
+| `POST` | `/api/v1/character/range` | ✅ `admin_token` | `can_operate_character` + 多角色开关 |
+| `POST` | `/api/v1/app/user/login` | ✅ `app_key` | — |
+| `DELETE` | `/api/v1/app/user/logout` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/add` | ✅ `app_key` | — |
+| `DELETE` | `/api/v1/app/user/delete` | ✅ `app_key` | — |
+| `PUT` | `/api/v1/app/user/edit` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/get` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/range` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/character/add` | ✅ `app_key` | — |
+| `DELETE` | `/api/v1/app/character/delete` | ✅ `app_key` | — |
+| `PUT` | `/api/v1/app/character/edit` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/character/get` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/character/range` | ✅ `app_key` | — |
+| `POST` | `/api/v1/app/user/get/metadata` | ❌ | — |
+| `POST` | `/api/v1/app/character/get/metadata` | ❌ | — |
+
+---
+
+## 🌐 根路由
+
+### `GET` `/`
+
+健康检查 / 欢迎检查。
+
+- 🍪 **是否需要 Cookie:** 否
+- 🔑 **所需权限:** —
+
+**响应**
+
+```json
+{ "code": 200, "message": "Welcome to GoAccountHub" }
+```
+
+---
+
+## 🛡️ 管理员 API
+
+服务端管理 API。登录后,浏览器持有 `admin_token` Cookie。
+
+### 🔐 鉴权
+
+#### `POST` `/api/v1/admin/login`
+
+以 `root` 或普通管理员身份登录。成功后设置 `admin_token`(以及 `admin_name`)Cookie。
+
+- 🍪 **是否需要 Cookie:** 否
+- 🔑 **所需权限:** —
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | 管理员用户名(root 管理员为 `root`) |
+| `password` | string | ✅ | 明文密码(服务端使用 SHA-256 哈希) |
+| `is_remember` | bool | ❌ | `true` = 30 天会话,`false` / 省略 = 1 小时会话 |
+
+```json
+{ "username": "root", "password": "secret", "is_remember": true }
+```
+
+**响应**
+
+```json
+{ "code": 200, "message": "login success", "token": "", "admin": "root" }
+```
+
+> 凭据无效 → `400` `{"code":400,"message":"invalid credentials"}`。
+
+#### `DELETE` `/api/v1/admin/logout`
+
+使当前 `admin_token` 失效,并从数据库中删除。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** —
+
+**请求体:** 无
+
+**响应**
+
+```json
+{ "code": 200, "message": "Logout success" }
+```
+
+#### `POST` `/api/v1/info`
+
+任何已登录管理员均可查看的仪表盘统计数据。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** —
+
+**请求体:** 无
+
+**响应**
+
+```json
+{
+ "code": 200,
+ "data": {
+ "user_count": 10,
+ "character_count": 25,
+ "total_token_count": 7,
+ "user_token_count": 5,
+ "admin_token_count": 2,
+ "admin_count": 3
+ }
+}
+```
+
+> 若已配置,`admin_count` 会包含 root 管理员。
+
+#### `POST` `/api/v1/admin/check_token`
+
+校验当前 `admin_token`。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** —
+
+**请求体:** 无
+
+**响应**
+
+```json
+{ "code": 200, "message": "admin_token valid" }
+```
+
+> 令牌缺失 / 无效 → `400`。
+
+#### `POST` `/api/v1/admin/info`
+
+返回当前管理员的身份与权限。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** —
+
+**请求体:** 无
+
+**响应(普通管理员)**
+
+```json
+{
+ "code": 200,
+ "message": "Admin Info",
+ "data": {
+ "username": "alice",
+ "uu_hash": "",
+ "is_root": false,
+ "permission": { "can_add_admin": true, "can_operate_user": true }
+ }
+}
+```
+
+**响应(root 管理员)** —— `is_root: true`,且所有权限均为 `true`。
+
+### 👤 管理员管理
+
+#### `POST` `/api/v1/admin/count`
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_get_admin`
+- **请求体:** 无
+
+```json
+{ "code": 200, "data": { "admin_count": 3 } }
+```
+
+#### `POST` `/api/v1/admin/add`
+
+创建一个新的(非 root)管理员。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_add_admin`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | 不能为 `root`,且必须唯一 |
+| `password` | string | ✅ | 明文密码 |
+| `permission` | object | ✅ | 权限映射(见[权限模型](#-权限模型)) |
+
+```json
+{
+ "username": "alice",
+ "password": "p@ssw0rd",
+ "permission": { "can_add_admin": true, "can_operate_user": true }
+}
+```
+
+**响应**
+
+```json
+{ "code": 200, "message": "Admin Added", "admin": { "ID": 2, "Username": "alice", "UUHash": "", "Permission": { } } }
+```
+
+> 用户名为 `root` 会被拒绝;用户名重复或用户名 / 密码为空 → `400`。
+
+#### `DELETE` `/api/v1/admin/delete`
+
+根据 `uu_hash` 删除管理员。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_delete_admin`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | 目标管理员的 `uu_hash` |
+
+```json
+{ "uu_hash": "" }
+```
+
+**响应**
+
+```json
+{ "code": 200, "message": "Delete Admin Success" }
+```
+
+> root 管理员不可删除,管理员也不能删除自己 → `400`。
+
+#### `PUT` `/api/v1/admin/edit`
+
+修改管理员的密码和 / 或权限。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_edit_admin`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | 目标管理员的 `uu_hash` |
+| `admin_info.password` | string | ❌ | 新密码;为空表示保持不变 |
+| `admin_info.permission` | object | ❌ | 新的权限映射 |
+
+```json
+{
+ "uu_hash": "",
+ "admin_info": {
+ "password": "new-password",
+ "permission": { "can_operate_user": true }
+ }
+}
+```
+
+**响应**
+
+```json
+{ "code": 200, "message": "Success" }
+```
+
+> root 管理员不可修改,管理员也不能修改自己 → `400`。
+
+#### `POST` `/api/v1/admin/get`
+
+根据 `uu_hash` 获取单个管理员。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_get_admin`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | 目标管理员的 `uu_hash` |
+
+**响应(普通管理员)**
+
+```json
+{ "code": 200, "admin": { "ID": 2, "Username": "alice", "UUHash": "", "Permission": { } }, "is_root": false }
+```
+
+**响应(root 管理员)** —— `{ "code": 200, "admin": null, "is_root": true }`
+未找到 → `404`。
+
+#### `POST` `/api/v1/admin/range`
+
+分页 / 过滤的管理员列表。**不包含 root 管理员。**
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_get_admin`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `begin_table_id` | int | ✅ | 页码,从 `1` 开始(必须 `>= 0`) |
+| `length` | int | ✅ | 每页数量;`-1` 表示返回全部(必须 `>= -1`) |
+| `search_condition` | object | ❌ | 过滤条件(见下,空字段忽略,使用 `AND` 组合) |
+
+**`search_condition`**
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| `username` | string | 模糊匹配(`LIKE %value%`) |
+| `uu_hash` | string | 模糊匹配(`LIKE %value%`) |
+| `can_add_admin` | bool | 为 `true` 时仅返回**拥有**该权限的管理员 |
+| `can_delete_admin` | bool | 同上 |
+| `can_edit_admin` | bool | 同上 |
+| `can_get_admin` | bool | 同上 |
+| `can_operate_user` | bool | 同上 |
+| `can_operate_character` | bool | 同上 |
+
+```json
+{
+ "begin_table_id": 1,
+ "length": 20,
+ "search_condition": { "username": "ali", "can_get_admin": true }
+}
+```
+
+**响应**
+
+```json
+{ "code": 200, "message": "Success(not include root admin)", "data": [ { "ID": 2, "Username": "alice" } ] }
+```
+
+### 🧑 用户管理
+
+#### `POST` `/api/v1/user/count`
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_user`
+- **请求体:** 无
+
+```json
+{ "code": 200, "data": { "user_count": 10 } }
+```
+
+#### `POST` `/api/v1/user/add`
+
+创建用户。会自动创建一个**同名角色**。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_user`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | 唯一 |
+| `password` | string | ✅ | 明文密码 |
+| `meta_data` | string | ❌ | 任意元数据(JSON / XML / YAML / 纯文本) |
+
+**响应**
+
+```json
+{ "code": 200, "message": "User Added Successfully,And Same Name Character Added Successfully", "user": { "ID": 1, "Username": "bob", "UUHash": "", "MetaData": "" } }
+```
+
+#### `DELETE` `/api/v1/user/delete`
+
+删除用户**及其所属的所有角色**。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_user`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | 目标用户的 `uu_hash` |
+
+**响应**
+
+```json
+{ "code": 200, "message": "User Deleted" }
+```
+
+#### `PUT` `/api/v1/user/edit`
+
+修改用户。字段为空时保持原值。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_user`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | 目标用户的 `uu_hash` |
+| `username` | string | ❌ | 新用户名(会同时重命名同名角色) |
+| `password` | string | ❌ | 新密码 |
+| `meta_data` | string | ❌ | 新元数据 |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Success" }
+```
+
+#### `POST` `/api/v1/user/get`
+
+获取单个用户及其拥有的角色数量。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_user`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `uu_hash` | string | ✅ | 目标用户的 `uu_hash` |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Success", "user": { "ID": 1, "Username": "bob" }, "character_number": 2 }
+```
+
+#### `POST` `/api/v1/user/range`
+
+分页 / 过滤的用户列表。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_user`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `begin_table_id` | int | ✅ | 页码,从 `1` 开始 |
+| `length` | int | ✅ | 每页数量;`-1` 表示返回全部 |
+| `search_condition` | object | ❌ | 过滤条件(见下) |
+
+**`search_condition`**
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| `username` | string | 模糊匹配(`LIKE %value%`) |
+| `uu_hash` | string | 模糊匹配(`LIKE %value%`) |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Success", "data": [ { "ID": 1, "Username": "bob" } ] }
+```
+
+### 🎭 角色管理
+
+> ⚠️ 当配置开关 **`allow_multi_character`** 为 `true` 时,这些端点(以及下文整个 `/api/v1/app/*` 分组)才可访问。否则 → `403` `Server Not Allow Multi Character`。
+
+#### `POST` `/api/v1/character/count`
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_character`
+
+```json
+{ "code": 200, "data": { "character_count": 25 } }
+```
+
+#### `POST` `/api/v1/character/add`
+
+在已存在的用户下新增角色。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_character`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `character_name` | string | ✅ | 全局唯一 |
+| `password` | string | ✅ | 明文密码 |
+| `user_uu_hash` | string | ✅ | 所属用户的 `uu_hash`(必须存在) |
+| `meta_data` | string | ❌ | 任意元数据 |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Character Added Successfully", "character": { "ID": 5, "CharacterName": "bob_alt", "UserUUHash": "", "UUHash": "" } }
+```
+
+#### `DELETE` `/api/v1/character/delete`
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_character`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `user_uu_hash` | string | ✅ | 所属用户的 `uu_hash` |
+| `character_uu_hash` | string | ✅ | 目标角色的 `uu_hash` |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Character Deleted" }
+```
+
+> 同名角色(`character_uu_hash == user_uu_hash`)不能在此删除;请改为删除该用户。
+
+#### `PUT` `/api/v1/character/edit`
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_character`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `user_uu_hash` | string | ✅ | 所属用户的 `uu_hash` |
+| `character_uu_hash` | string | ✅ | 目标角色的 `uu_hash` |
+| `character_name` | string | ❌ | 新名称(不能冲突) |
+| `password` | string | ❌ | 新密码 |
+| `meta_data` | string | ❌ | 新元数据 |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Edit Character Success" }
+```
+
+> 同名角色必须通过用户相关端点进行修改。
+
+#### `POST` `/api/v1/character/get`
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_character`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `user_uu_hash` | string | ✅ | 所属用户的 `uu_hash` |
+| `character_uu_hash` | string | ✅ | 目标角色的 `uu_hash` |
+
+**响应**
+
+```json
+{ "code": 200, "character": { "ID": 5, "CharacterName": "bob_alt", "UserUUHash": "" } }
+```
+
+#### `POST` `/api/v1/character/range`
+
+分页 / 过滤的角色列表。
+
+- 🍪 **是否需要 Cookie:** 是 —— `admin_token`
+- 🔑 **所需权限:** `can_operate_character`
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `begin_table_id` | int | ✅ | 页码,从 `1` 开始 |
+| `length` | int | ✅ | 每页数量;`-1` 表示返回全部 |
+| `search_condition` | object | ❌ | 过滤条件(见下) |
+
+**`search_condition`**
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| `character_name` | string | 模糊匹配(`LIKE %value%`) |
+| `user_uu_hash` | string | 模糊匹配(`LIKE %value%`) |
+| `uu_hash` | string | 模糊匹配(`LIKE %value%`) |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Success", "data": [ { "ID": 5, "CharacterName": "bob_alt" } ] }
+```
+
+---
+
+## 📱 应用 API
+
+位于 `/api/v1/app` 下的第三方应用 API。所有端点都要求 **`app_key`** Cookie —— 两个 `metadata` 端点除外,它们使用请求体中的登录 `token` 进行鉴权。
+
+> ⚠️ 由于分组中间件继承机制,**所有** `/api/v1/app/*` 端点也会受 **`allow_multi_character`** 开关限制(包括用户相关端点)。
+
+### 🔐 鉴权
+
+#### `POST` `/api/v1/app/user/login`
+
+以指定角色登录用户。返回 `token`(用于应用用户自身的操作以及 metadata 端点)。
+
+- 🍪 **是否需要 Cookie:** 是 —— `app_key`
+- 🔑 **所需权限:** —
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `username` | string | ✅ | 用户名 |
+| `character_name` | string | ✅ | 属于该用户的角色 |
+| `password` | string | ✅ | 用户密码(明文) |
+| `is_remember` | bool | ❌ | `true` = 30 天令牌,否则 1 小时 |
+
+```json
+{ "username": "bob", "character_name": "bob", "password": "secret", "is_remember": true }
+```
+
+**响应**
+
+```json
+{ "code": 200, "message": "Login success", "token": "" }
+```
+
+> 用户不存在 / 密码错误 / 角色名不存在 → `400`。
+
+#### `DELETE` `/api/v1/app/user/logout`
+
+使用户 `token` 失效。
+
+- 🍪 **是否需要 Cookie:** 是 —— `app_key`
+- 🔑 **所需权限:** —
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `token` | string | ✅ | 应用登录返回的令牌 |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Logout success" }
+```
+
+### 🧑 用户端点(应用)
+
+与对应的管理员端点使用相同的处理函数,但改用 `app_key` 校验,而非 `admin_token` + 权限。
+
+| 方法 | 路径 | 🍪 Cookie | 请求体 |
+| --- | --- | --- | --- |
+| `POST` | `/api/v1/app/user/add` | `app_key` | `username`、`password`、`meta_data` |
+| `DELETE` | `/api/v1/app/user/delete` | `app_key` | `uu_hash` |
+| `PUT` | `/api/v1/app/user/edit` | `app_key` | `uu_hash`、`username?`、`password?`、`meta_data?` |
+| `POST` | `/api/v1/app/user/get` | `app_key` | `uu_hash` |
+| `POST` | `/api/v1/app/user/range` | `app_key` | `begin_table_id`、`length`、`search_condition{ username, uu_hash }` |
+
+### 🎭 角色端点(应用)
+
+| 方法 | 路径 | 🍪 Cookie | 请求体 |
+| --- | --- | --- | --- |
+| `POST` | `/api/v1/app/character/add` | `app_key` | `character_name`、`password`、`user_uu_hash`、`meta_data` |
+| `DELETE` | `/api/v1/app/character/delete` | `app_key` | `user_uu_hash`、`character_uu_hash` |
+| `PUT` | `/api/v1/app/character/edit` | `app_key` | `user_uu_hash`、`character_uu_hash`、`character_name?`、`password?`、`meta_data?` |
+| `POST` | `/api/v1/app/character/get` | `app_key` | `user_uu_hash`、`character_uu_hash` |
+| `POST` | `/api/v1/app/character/range` | `app_key` | `begin_table_id`、`length`、`search_condition{ character_name, user_uu_hash, uu_hash }` |
+
+### 📦 Metadata 端点
+
+> 🍪 这两个端点**不**要求 `app_key`;它们使用请求体中的应用用户 `token` 进行鉴权。
+
+#### `POST` `/api/v1/app/user/get/metadata`
+
+返回 token 所属用户的元数据。
+
+- 🍪 **是否需要 Cookie:** 否
+- 🔑 **所需权限:** —
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `token` | string | ✅ | 应用用户令牌 |
+
+**响应**
+
+```json
+{ "code": 200, "message": "Success", "meta_data": "" }
+```
+
+#### `POST` `/api/v1/app/character/get/metadata`
+
+返回 token 所属角色的元数据。
+
+- 🍪 **是否需要 Cookie:** 否
+- 🔑 **所需权限:** —
+
+**请求体**
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `token` | string | ✅ | 应用用户令牌 |
+
+**响应**
+
+```json
+{ "code": 200, "message": "success", "meta_data": "" }
+```
diff --git a/docs/assets/GAH.png b/docs/assets/GAH.png
new file mode 100644
index 0000000..4803ff7
Binary files /dev/null and b/docs/assets/GAH.png differ
diff --git a/router/router.go b/router/router.go
index 1e43e9c..552a187 100644
--- a/router/router.go
+++ b/router/router.go
@@ -37,34 +37,40 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) {
v1.DELETE("/admin/logout", adminControllor.AdminLogout)
//Dashboard Info (any logged-in admin can view)
- v1.GET("/info", middleware.AdminCheckMiddleware(), adminControllor.Info)
+ v1.POST("/info", middleware.AdminCheckMiddleware(), adminControllor.Info)
//Check Admin Token
- v1.GET("/admin/check_token", middleware.AdminCheckMiddleware(), checkControllor.CheckAdminToken)
+ v1.POST("/admin/check_token", middleware.AdminCheckMiddleware(), checkControllor.CheckAdminToken)
//Admin Info
- v1.GET("/admin/info", middleware.AdminCheckMiddleware(), adminControllor.AdminInfo)
+ v1.POST("/admin/info", middleware.AdminCheckMiddleware(), adminControllor.AdminInfo)
+ //Admin Count
+ v1.POST("/admin/count", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin"), adminControllor.AdminCount)
//Admin Operation
v1.POST("/admin/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_add_admin"), adminControllor.AdminAdd)
v1.DELETE("/admin/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_delete_admin"), adminControllor.AdminDelete)
v1.PUT("/admin/edit", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_edit_admin"), adminControllor.AdminEdit)
- v1.GET("/admin/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin"), adminControllor.AdminGet)
- v1.GET("/admin/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin_range"), adminControllor.AdminRange)
+ v1.POST("/admin/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin"), adminControllor.AdminGet)
+ v1.POST("/admin/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin"), adminControllor.AdminRange)
+ //User Count
+ v1.POST("/user/count", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserCount)
//User Api
v1.POST("/user/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserAdd)
v1.DELETE("/user/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserDelete)
v1.PUT("/user/edit", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserEdit)
- v1.GET("/user/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserGet)
- v1.GET("/user/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user_range"), adminControllor.UserRange)
+ v1.POST("/user/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserGet)
+ v1.POST("/user/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserRange)
//Character Api
//Check is Allowed to Use Multi Character Functions
v1.Use(middleware.ConfigBlocker("allow_multi_character"))
+ //Character Count
+ v1.POST("/character/count", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterCount)
//Character Operation
v1.POST("/character/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterAdd)
v1.DELETE("/character/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterDelete)
v1.PUT("/character/edit", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterEdit)
- v1.GET("/character/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterGet)
- v1.GET("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character_range"), adminControllor.CharacterRange)
+ v1.POST("/character/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterGet)
+ v1.POST("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterRange)
}
{
@@ -77,17 +83,17 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) {
app.POST("/user/add", middleware.ApplicationKeyCheckMiddleware(), appControllor.UserAdd)
app.DELETE("/user/delete", middleware.ApplicationKeyCheckMiddleware(), appControllor.UserDelete)
app.PUT("/user/edit", middleware.ApplicationKeyCheckMiddleware(), appControllor.UserEdit)
- app.GET("/user/get", middleware.ApplicationKeyCheckMiddleware(), appControllor.UserGet)
- app.GET("/user/range", middleware.ApplicationKeyCheckMiddleware(), appControllor.UserRange)
+ app.POST("/user/get", middleware.ApplicationKeyCheckMiddleware(), appControllor.UserGet)
+ app.POST("/user/range", middleware.ApplicationKeyCheckMiddleware(), appControllor.UserRange)
//Character Operation
app.POST("/character/add", middleware.ApplicationKeyCheckMiddleware(), appControllor.CharacterAdd)
app.DELETE("/character/delete", middleware.ApplicationKeyCheckMiddleware(), appControllor.CharacterDelete)
app.PUT("/character/edit", middleware.ApplicationKeyCheckMiddleware(), appControllor.CharacterEdit)
- app.GET("/character/get", middleware.ApplicationKeyCheckMiddleware(), appControllor.CharacterGet)
- app.GET("/character/range", middleware.ApplicationKeyCheckMiddleware(), appControllor.CharacterRange)
+ app.POST("/character/get", middleware.ApplicationKeyCheckMiddleware(), appControllor.CharacterGet)
+ app.POST("/character/range", middleware.ApplicationKeyCheckMiddleware(), appControllor.CharacterRange)
//Get Metadata
- app.GET("/user/get/metadata", appControllor.GetUserMetaData)
- app.GET("/character/get/metadata", appControllor.GetCharacterMetaData)
+ app.POST("/user/get/metadata", appControllor.GetUserMetaData)
+ app.POST("/character/get/metadata", appControllor.GetCharacterMetaData)
}
return router, db
}
diff --git a/todo/target.md b/todo/target.md
deleted file mode 100644
index 860c6b8..0000000
--- a/todo/target.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Frontend
-- [ ] Login Page
-- [ ] Signup Page
-- [ ] Character List Page
-- [ ] Character Detail Page
-- [ ] User List Page
-- [ ] User Detail Page
-- [ ] Admin Page
\ No newline at end of file