# PingCAP Account API 文档

## 说明

> 所有的 API 调用都有频率限制，未认证用户 30 次/分钟, 认证用户 100 次/分钟，超出调用频率会返回 HTTP-Status-Code 429  
> i18n支持(zh-hans/en-us), 在请求 Header 中声明： Accept-Language: zh-hans  
> 当 cookie 中有 csrftoken 时, put|post|delete|patch 等请求需在 header 中声明: X-CSRFTOKEN  
> 以下 API 文档中均未提及 5xx 错误, 但你在使用 API 的时候应考虑到相关错误(包括网络延时)  
> 登录、注册时部分接口会使用 Google Recaptcha Token 来进行人机检查。 文档：https://developers.google.com/recaptcha/docs/v3
> 后端会在用户在新环境第一次登录或者异常情况登录时，以短信或者邮件的方式通知用户

- [获取当前用户信息](#获取当前用户信息)
- [手机验证码登录-检查手机号](#手机验证码登录-检查手机号)
- [手机验证码登录-发送验证码](#手机验证码登录-发送验证码)
- [手机验证码登录-登录](#手机验证码登录-登录)
- [GitHub 登录](#GitHub-登录)
- [密码登录](#密码登录)
- [忘记密码-发送验证码](#忘记密码-发送验证码)
- [忘记密码-检查验证码](#忘记密码-检查验证码)
- [忘记密码-重置密码](#忘记密码-重置密码)
- [忘记密码-检查用户是否可以重置密码](#忘记密码-检查用户是否可以重置密码)
- [注册-发送验证码](#注册-发送验证码)
- [注册-提交信息](#注册-提交信息)
- [获取个人信息](#获取个人信息)
- [修改个人信息](#修改个人信息)
- [公司认证时向企业邮箱发送验证码](#公司认证时向企业邮箱发送验证码)
- [提交公司认证](#提交公司认证)
- [获取账号设置](#获取账号设置)
- [修改手机号码-发送验证码](#修改手机号码-发送验证码)
- [修改手机号码-提交](#修改手机号码-提交)
- [修改邮箱-发送验证码](#修改邮箱-发送验证码)
- [修改邮箱-提交](#修改邮箱-提交)
- [更改密码](#更改密码)
- [设置密码](#设置密码)
- [解绑第三方关联账号](#解绑第三方关联账号)
- [运营红点-获取提示信息](#运营红点-获取提示信息)
- [运营红点-标记为已读](#运营红点-标记为已读)
- [获取用户公开信息](#获取用户公开信息)
- [刷新图片验证码](#刷新图片验证码)

### 获取当前用户信息

#### Request

- Method: **GET**
- URL: `/api/me`
- Content-Type: application/json
- Permission: Authenticated User

#### Response Example

- Success

  - HTTP/1.1 200

    - 未申请创建团队，也不属于任何团队

      ```json
      {
        "detail": "success",
        "data": {
          "id": 1,
          "username": "zhangsan",
          "avatar_url": "https://asktug.com/user_avatar/asktug.com/wd0517/50/2218_2.png",
          "org": null, // 未加入任何团队
          "org_enroll": null,  // 未申请创建任何团队
          "org_invitations": [], // 未收到任何团队邀请
        }
      }
      ```

    - 已属于某个团队

      ```json
      {
        "detail": "success",
        "data": {
          "id": 1,
          "username": "zhangsan",
          "avatar_url": "https://asktug.com/user_avatar/asktug.com/wd0517/50/2218_2.png",
          "org": {
            "slug": "pwd",           // org_slug，用于后续 ORG 成员管理 API 接口参数拼接使用
            "name": "PingCAP",       // 团队名称
            "role": "admin",         // 角色
            "role_display": "管理员"
          },
          "org_enroll": null,  // 未申请创建任何团队
          "org_invitations": [], // 未收到任何团队邀请
        }
      }
      ```

    - 正在申请创建某个团队

      ```json
      {
        "detail": "success",
        "data": {
          "id": 1,
          "username": "zhangsan",
          "avatar_url": "https://asktug.com/user_avatar/asktug.com/wd0517/50/2218_2.png",
          "org": null,  // 未加入任何团队
          "org_enroll": {
            "audit_status": 0,  // 审核状态: 0 -> pending, 1 -> pass, 2 -> deny
            "audit_status_display": "pending",
            "audit_reason": "reason",  // 审核理由，一般用于在审核未通过时使用
          },
          "org_invitations": [], // 未收到任何团队邀请
        }
      }
      ```

    - 收到团队邀请

      ```json
      {
        "detail": "success",
        "data": {
          "id": 1,
          "username": "zhangsan",
          "avatar_url": "https://asktug.com/user_avatar/asktug.com/wd0517/50/2218_2.png",
          "org": null,  // 未加入任何团队
          "org_enroll": null,  // 未申请创建任何团队
          "org_invitations": [{
            "id": 1,                      // 邀请记录的 ID，用于接受邀请或拒绝邀请
            "org_slug": "PingCAP",        // 被邀请加入的团队 Slug
            "org_name": "PingCAP",        // 被邀请加入的团队名称
            "org_company": "北京平凯星辰科技发展有限公司",  // 被邀请加入的团队所属企业
            "inviter_username": "wangdi", // 邀请人用户名
            "valid": true,                // 是否有效
          }]
        }
      }
      ```

- Failed

  - HTTP/1.1 401

    ```json
    {
      "detail": "Authentication credentials were not provided"
    }
    ```

### 手机验证码登录-检查手机号

#### Request

- Method: **POST**
- URL: `/api/login/phone/check`
- Content-Type: application/json
- Body:

  ```json
  {
    "phone": "+85251234567",  // 手机号，必填；支持大陆、香港和澳门
  }
  ```

- Permission: Anyone
- 说明:
  - 支持中国大陆 11 位手机号、`+852` 开头的香港号码和 `+853` 开头的澳门号码
  - 大陆号码兼容 `136110812xx` 和 `+86136110812xx`，后端统一保存为不带 `+86` 的旧格式
  - 手机号必须已注册

#### Response Example

- Success

  - HTTP/1.1 200  可以用于登录

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400  该手机号不能用于登录

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "phone": ["phone number has not been registered", "invalid phone number"]
      }
    }
    ```


### 手机验证码登录-发送验证码

#### Request

- Method: **POST**
- URL: `/api/login/phone/send-code`
- Content-Type: application/json
- Body:

  ```json
  {
    "phone": "136110812xx",    // 手机号，必填
    "re_token_v3": "ndanjkkjk1k1nk11",   // Google Recaptcha v3，必填
  }
  ```

- Permission: Anyone
- 说明:
  - 目前只支持 11 位的中国手机号
  - 手机号必须已注册
  - 后端限制 57 秒后可重新发送，前端显示 60 秒后可重新发送，用于解决前后端计数器不一致引起不好的用户体验
  - 当某一手机号或 IP 一段时间内发送 2 次及以上短信验证码，且验证码未验证，同时人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请稍后重试”
  - 每个手机号和邮箱每天最多只可发送 20 条验证码信息
  - 验证码 5 分钟内有效, 会在信件中说明，前端可不用额外说明

#### Response Example

- Success

  - HTTP/1.1 200  验证码已发送

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400  该手机号不能用于登录

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "phone": ["phone number has not been registered", "invalid phone number"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 429 发送频率限制

    ```json
    {
      "detail": "rate limit"
    }
    ```


### 手机验证码登录-登录

#### Request

- Method: **POST**
- URL: `/api/login/phone`
- Content-Type: application/json
- Body:

  ```json
  {
    "phone": "136110812xx",   // 登录手机号, 必填
    "code": "123456",    // 手机验证码, 必填
    "re_token_v3": "ndanjkkjk1k1nk11",   // Google Recaptcha v3  必填
    "redirect_to": "https://asktug.com/latest",  // GET 参数（redirect_to）解析出来的, 非必填
  }
  ```

- Permission: Anyone
- 说明:
  - 目前只支持 11 位的中国手机号
  - 手机号必须已注册
  - 当某一手机号或 IP 连续 2 次登录失败，同时人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请刷新页面稍后重试”
  - 每分钟最多调用 20 次

#### Response Example

- Success

  - HTTP/1.1 200  登录成功

    ```json
    {
      "detail": "success",
      "data": {
        "redirect_to": "https://asktug.com/latest",   // 登录成功，前端需要将页面跳转到该地址
        "sso_callbacks": [                            // 在请求 redirect_to 页面前，先请求 callbacks 中的地址，可以设置一个最大时间 500ms，超过最大时间不管请求是否成功，均跳转
          "/_sso_set_cookie?sys=asktug"
        ]
      }
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "phone": ["user does not exist", "invalid phone number"],
        "code": ["invalid code"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 429

    ```json
    {
      "detail": "rate limit"
    }
    ```

### GitHub 登录

#### Request

- Method: **GET**
- URL: `/social/login/github?redirect_to=http%3a%2f%2f127.0.0.1%3a9000%2fsso%3fa%3d1%26b%3d2`
- 说明:
  - 从 URL 中解析出来的 redirect_to，如果 URL 中没有 redirect_to，则这里也可以不用加

#### Response Example

- Success

  - HTTP/1.1 302
  - Location: `http://127.0.0.1:9000/sso?a=1&b=2`

### 密码登录

#### Request

- Method: **POST**
- URL: `/api/login/password`
- Content-Type: application/json
- Body:

  ```json
  {
    "identifier" : "13511112222",  // 邮箱、手机号、用户名
    "password": "*****",           // 密码, 必填
    "re_token_v3": "dnalknjklknwjlbhj123", // recaptcha token, 必填
    "redirect_to": "https://asktug.com/latest",  // GET 参数解析出来的, 非必填
  }
  ```

- 说明:
  - 当某一账号或 IP 在一段时间多次( >= 2 )登录失败，同时人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请刷新页面稍后重试”
  - 每分钟最多调用 20 次

#### Response Example

- Success

  - HTTP/1.1 200  登录成功

    ```json
    {
      "detail": "success",
      "data": {
        "redirect_to": "https://asktug.com/latest",   // 登录成功，前端需要将页面跳转到该地址
        "sso_callbacks": [                            // 在请求 redirect_to 页面前，先请求 callbacks 中的地址，可以设置一个最大时间 500ms，超过最大时间不管请求是否成功，均跳转
          "/_sso_set_cookie?sys=asktug"
        ]
      }
    }
    ```

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "__all__": ["identifier or password is wrong, please try again"],  // 表单全局错误，用户名或密码错误
        "identifier": ["this feild is required"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 429

    ```json
    {
      "detail": "rate limit"
    }
    ```

### 忘记密码-发送验证码

#### Request

- Method: **POST**
- URL: `/api/forget/send-code`
- Content-Type: application/json
- Body:

  ```json
  {
    "identifier": "136110812xx",  // 手机号或邮箱，必填
    "re_token_v3": "ndanjkkjk1k1nk11",   // Google Recaptcha v3，必填
  }
  ```

- Permission: Anyone
- 说明:
  - 目前只支持 11 位的中国手机号和邮箱
  - 账号必须存在
  - 后端限制 57 秒后可重新发送，前端显示 60 秒后可重新发送，用于解决前后端计数器不一致引起不好的用户体验
  - 当某一手机号或 IP 一段时间内发送 2 次及以上短信验证码，且验证码未验证，同时人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请稍后重试”
  - 每个手机号和邮箱每天最多只可发送 20 条验证码信息
  - 验证码 5 分钟内有效, 会在信件中说明，前端可不用额外说明

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "identifier": ["user does not exist"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 429

    ```json
    {
      "detail": "rate limit"
    }
    ```

### 忘记密码-检查验证码

#### Request

- Method: **POST**
- URL: `/api/forget/verify`
- Content-Type: application/json
- Body:

  ```json
  {
    "identifier": "13611081287",    // 手机、邮箱、用户名, 必填
    "code": "123456",   // 验证码, 必填
    "re_token_v3": "dankk1nknk1nk",   // recaptcha token, 必填
  }
  ```

- 说明:
  - 每分钟最多调用 20 次

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success",
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors":{
        "code":["invalid code"]
      }
    }
    ```

### 忘记密码-重置密码

#### Request

- Method: **POST**
- URL: `/api/forget/reset-password`
- Content-Type: application/json
- Body:

  ```json
  {
    "new_password": "@pingcap_newpassword",    // 新密码, 1. 至少8个字符  2. 不能全是数字  3. 不能太常见(由后端来判断)
  }
  ```

- 说明: 需严格按照步骤(发送验证码 -> 检查验证码 -> 重置密码)来, 额外信息保存在 session 中
- 有效期: 用户有 10 分钟时间修改密码, 10 分钟后 session 自动过期, 修改密码成功后自动过期

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success",
    }
    ```

- Failed

  - HTTP/1.1 428

    ```json
    {
      "detail": "precondition required，please verify your identity first",
    }
    ```

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors":{
        "new_password":["password is required"]
      }
    }
    ```

### 忘记密码-检查用户是否可以重置密码

#### Request

- Method: **HEAD**
- URL: `/api/forget/reset-password`
- 说明:
  假设前端在用户重置密码时有多个页面，它们的 URL 都不一样，比如

  - 发送验证码页面 /forget/send-code
  - 密码重置页面 /forget/reset-passeord

  此时当用户直接输入 /forget/reset-password 的链接时，前端可以使用本接口，如果得到 200 的响应，则表示用户可以重置密码，如果得到 428 的响应，则可自动将用户跳转到 /forget/send-code 页面

#### Response Example

- Success

  - HTTP/1.1 200

- Failed

  - HTTP/1.1 428

### 注册-发送验证码

#### Request

- Method: **POST**
- URL: `/api/signup/send-code`
- Content-Type: application/json
- Body:

  ```json
  {
    "phone": "+85251234567",  // 手机号，必填；格式必须与发送验证码时一致
    "re_token_v3": "ndanjkkjk1k1nk11",   // Google Recaptcha v3，必填
  }
  ```

- Permission: Anyone
- 说明:
  - 目前只支持 11 位的中国手机号
  - 之前没有注册过账号
  - 后端限制 57 秒后可重新发送，前端显示 60 秒后可重新发送，用于解决前后端计数器不一致引起不好的用户体验
  - 当某一手机号或 IP 一段时间内发送 2 次及以上短信验证码，且验证码未验证，同时人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请稍后重试”
  - 每个手机号和邮箱每天最多只可发送 20 条验证码信息
  - 验证码 5 分钟内有效, 会在信件中说明，前端可不用额外说明

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "phone": ["user already exist"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 429

    ```json
    {
      "detail": "rate limit"
    }
    ```

### 注册-提交信息

#### Request

- Method: **POST**
- URL: `/api/signup`
- Content-Type: application/json
- Body:

  ```json
  {
    "company": "PingCAP", // 公司，必填, 长度限制：1 ~ 100
    "email": "user@mail.com",  // 邮箱，必填
    "phone": "136110812xx",  // 手机号，必填
    "code": "123456", // 手机验证码
    "re_token_v3": "ndanjkkjk1k1nk11",   // Google Recaptcha v3，必填
    "redirect_to": "https://asktug.com/latest",  // GET 参数解析出来的, 非必填
  }
  ```

- Permission: Anyone
- 说明:
  - 人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请稍后重试”

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success",
      "data": {
        "redirect_to": "https://asktug.com/latest",   // 注册并登录成功，前端需要将页面跳转到该地址
        "sso_callbacks": [                            // 在请求 redirect_to 页面前，先请求 callbacks 中的地址，可以设置一个最大时间 500ms，超过最大时间不管请求是否成功，均跳转
          "/_sso_set_cookie?sys=asktug"
        ]
      }
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "phone": ["user already exist"],
        "code": ["invalid code"],
        "re_token_v3": ["man-machine check failed, please try again later"]
      }
    }
    ```

  - HTTP/1.1 429

    ```json
    {
      "detail": "rate limit"
    }
    ```

### 获取个人信息

#### Request

- Method: **GET**
- URL: `/api/profile`
- Content-Type: application/json
- Permission: IsAuthenticated

#### Response Example

- Success

  - HTTP/1.1 200  都有内容

    ```json
    {
      "detail": "success",
      "data": {
        "avatar_url": "https://asktug.com/user_avatar/asktug.com/gangshen/90/77770_2.png", // 用户头像，系统生成，用户不可修改
        "username": "Hacker_xxx", // 用户名，用户名每 30 天修改一次
        "username_last_modified_at": "2021-05-17 12:00:00", // 用户名上次修改时间, 如果是新创建的用户则为 null
        "bio": "介绍一下你自己吧", // 个人简介
        "name": "真实姓名", // 真实姓名
        "gender": 0,  // 性别，0 -> 男,  1 -> 女
        "date_of_birth": "2021-05-17", // 出生年月日
        "address": "beijing",   // 住址
        "company_name": "pingcap", // 公司名称
        "job_title": "xxx_003", // 职位代码,  来源: https://gist.github.com/wd0517/c5d407f165b6a0485cbc66d6acaa47ae
        "company_verified_status": 0, // 公司认证状态, 0 -> 未认证, 1 -> 审核中, 2 -> 已认证
      }
    }
    ```

  - HTTP/1.1 200 部分无内容

    ```json
    {
      "detail": "success",
      "data": {
        "avatar_url": "https://asktug.com/user_avatar/asktug.com/gangshen/90/77770_2.png", // 用户头像，系统生成，用户不可修改
        "username": "Hacker_xxx", // 用户名
        "username_last_modified_at": null, // 用户名上次修改时间, 如果是新创建的用户则为 null
        "bio": null, // 个人简介
        "name": null, // 真实姓名
        "gender": null,  // 性别，0 -> 男,  1 -> 女
        "date_of_birth": null, // 出生年月日
        "address": null,   // 住址
        "company_name": null, // 公司名称
        "job_title": null, // 职位代码,  来源: https://gist.github.com/wd0517/c5d407f165b6a0485cbc66d6acaa47ae
        "company_verified_status": 0, // 公司认证状态, 0 -> 未认证, 1 -> 审核中, 2 -> 已认证
      }
    }
    ```

- Failed

  - HTTP/1.1 401

  ```json
    {
      "detail": "Authentication credentials were not provided."
    }
  ```


### 修改个人信息

#### Request

- Method: **PATCH**
- URL: `/api/profile`
- Content-Type: application/json
- Body:

  ```json
  {
    "username": "new_username", // 用户名，规则: 1. 4~20个字符  2. 只能包含数字、字母、汉字、下划线或连字符 3. 首字母只能为字母、数字或下划线  4. 还未被使用
    "bio": "个人介绍", // 个人简介
    "name": "真实姓名", // 真实姓名
    "date_of_birth": "1995-01-25",  // 出生年月日
    "job_title": "xxx_003", // 职位代码
  }
  ```

- Permission: IsAuthenticated
- 说明:
  - 修改了哪些字段就只提交哪些字段，无需全部提交
  - 用户名每 30 天只能修改一次
  - 不能修改 `avatar_url` 和 `username_last_modified_at`

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

  ```json
  {
    "detail": "params wrong",
    "errors": {
      "username": ["invalid username"],
      "name": ["invalid name"],
    }
  }
  ```

  - HTTP/1.1 401

  ```json
    {
      "detail": "Authentication credentials were not provided."
    }
  ```

### 公司认证时向企业邮箱发送验证码

#### Request

- Method: **POST**
- URL: `/api/profile/company/send-code`
- Body:

  ```json
  {
    "email" : "enterprise@mail.com",
  }
  ```

- Content-Type: application/json
- Permission: Authenticated User & Unverified User
- 说明: 后端限制 50 秒发送一次，前端限制 60 秒发送一次，差值是为了处理前后端计数器不一致带来的交互问题

#### Response Example

- Success
  - HTTP/1.1 200

    ```json
    {
        "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 429

    ```json
    { "detail": "rate limit" }
    ```

  - HTTP/1.1 400

    ```json
    {
      "detail": "params error",
      "errors": {
        "email": [ "invalid email address" ]
      }
    }
    ```

  - HTTP/1.1 409  公司认证状态不是 “未认证”, 无法操作申请相关接口

    ```json
    {
      "detail": "application has already been submitted",
    }
    ```

### 提交公司认证

#### Request

- Method: **POST**
- URL: `/api/profile/company/verify`
- Content-Type: `multipart/form-data`
- Body:

  |  字段   | 类型  | 含义 | 备注 |
  |  ----  | ----  | --- | --- |
  |  verification_method | int | 认证类型 | 0 -> 邮箱认证  1 -> 在职证明认证 |
  | email  | string | 企业邮箱 |邮箱认证时必填 |
  | email_code  | string | 邮箱验证码 |邮箱认证时必填 |
  | incumbency_cert  | file | 在职证明的文件 | 使用在职证明认证时必填 |

- Permission: Authenticated User & Unverified User
- 备注: 文件只允许上传 .jpg .jpeg .png 格式，文件大小在 5M 以内，这些前端都应该在上传前检查

#### Response Example

- Success
  - HTTP/1.1 200

    ```json
    {
        "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params error",
      "errors": {
        "email": [ "invalid email address" ],
      }
    }
    ```

  - HTTP/1.1 409  公司认证状态不是 “未认证”, 无法操作申请相关接口

    ```json
    {
      "detail": "application has already been submitted",
    }
    ```

### 获取账号设置

#### Request

- Method: **GET**
- URL: `/api/account/settings`
- Content-Type: application/json
- Permission: IsAuthenticated

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success",
      "data": {
        "phone": "130*****1234", // 手机号码，如果没有则为 null
        "email": "example@mail.com", // 邮箱, 如果没有则为 null
        "email_verified": false,  // 邮箱是否已验证
        "has_password": true,  // 是否设置过密码
        "associated_accounts": {
          "github": {   // 如果未绑定，则为 null
            "id": "github-id",
            "login": "github-usernname"
          },
          "wechat": {  // 如果未绑定，则为 null
            "id": "wechat-unionid",
          }
        }
      }
    }
    ```

- Failed

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

### 修改手机号码-发送验证码

#### Request

- Method: **POST**
- URL: `/api/account/set-phone/send-code`
- Content-Type: application/json
- Body:

  ```json
  {
    "phone": "131331788931",  // 新的手机号, 必填
    "re_token_v3": "dakndjkn1knkjkjnkkjnkj",  // google recaptcha, 必填
  }
  ```

- Permission: IsAuthenticated
- 说明:
  - 目前只支持 11 位的中国手机号
  - 没有与其他账号关联过，也不是当前绑定的手机号
  - 当某一手机号或 IP 一段时间内发送 2 次及以上短信验证码，且验证码未验证，同时人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请稍后重试”
  - 每个手机号和邮箱每天最多只可发送 20 条验证码信息
  - 验证码 5 分钟内有效, 会在信件中说明，前端可不用额外说明

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "phone": ["phone number has been registered"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

  - HTTP/1.1 429

    ```json
    {
      "detail": "rate limit"
    }
    ```

### 修改手机号码-提交

#### Request

- Method: **POST**
- URL: `/api/account/set-phone`
- Content-Type: application/json
- Body:

  ```json
  {
    "phone": "131331788931",  // 新的手机号, 必填
    "code": "123345",  // 手机号验证码, 必填
    "re_token_v3": "dnakjnknjknkjnkjndknkjank", // google recaptcha, 必填
  }
  ```

- Permission: IsAuthenticated

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "phone": ["phone number has been registered"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

### 修改邮箱-发送验证码

#### Request

- Method: **POST**
- URL: `/api/account/set-email/send-code`
- Content-Type: application/json
- Body:

  ```json
  {
    "email": "131331788931",  // 新的邮箱, 必填
    "re_token_v3": "dakndjkn1knkjkjnkkjnkj",  // google recaptcha, 必填
  }
  ```

- Permission: IsAuthenticated
- 说明:
  - 没有与其他账号关联过，也不是当前绑定的邮箱
  - 当某一邮箱或 IP 一段时间内发送 2 次及以上验证码，且验证码未验证，同时人机检查失败的时候，本接口会返回 400，其中 re_token 的错误为 “人机检查失败，请稍后重试”
  - 每个手机号和邮箱每天最多只可发送 20 条验证码信息
  - 验证码 5 分钟内有效, 会在信件中说明，前端可不用额外说明

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "email": ["email has been registered"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

  - HTTP/1.1 429

    ```json
    {
      "detail": "rate limit"
    }
    ```

### 修改邮箱-提交

#### Request

- Method: **POST**
- URL: `/api/account/set-email`
- Content-Type: application/json
- Body:

  ```json
  {
    "email": "131331788931",  // 新的邮箱, 必填
    "code": "123345",  // 邮箱验证码, 必填
    "re_token_v3": "dnakjnknjknkjnkjndknkjank", // google recaptcha, 必填
  }
  ```

- Permission: IsAuthenticated

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "email": ["email has been registered"],
        "re_token_v3": ["man-machine check failed, please refresh page and try again later"]
      }
    }
    ```

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

### 更改密码

#### Request

- Method: **POST**
- URL: `/api/account/reset-password`
- Content-Type: application/json
- Body:

  ```json
  {
    "old_password": "dankdnakjnknk",  // 旧密码, 必填
    "new_password": "dmalmdlkamdlamda",  // 新密码, 必填
    "re_token_v3": "dnakjnknjknkjnkjndknkjank", // google recaptcha, 必填
  }
  ```

- Permission: IsAuthenticated
- 说明: 修改成功后，用户会被自动登出，需要重新登录

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "old_password": ["old password is wrong"],
        "new_password": ["the password is too similar to the username", "this password is too common"]
      }
    }
    ```

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

### 设置密码

#### Request

- Method: **POST**
- URL: `/api/account/set-password`
- Content-Type: application/json
- Body:

  ```json
  {
    "new_password": "dmalmdlkamdlamda",  // 新密码, 必填
  }
  ```

- Permission: IsAuthenticated
- 说明:
  - 只有当用户没有密码的时候才能调用该接口，否则接口会返回 403
  - 修改成功后，用户会被自动登出，需要重新登录

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "new_password": ["the password is too similar to the username", "this password is too common"]
      }
    }
    ```

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

  - HTTP/1.1 403

    ```json
      {
        "detail": "You have no permission for preform this action"
      }
    ```


### 解绑第三方关联账号

#### Request

- Method: **POST**
- URL: `/api/social/disconnect/<provider>`
- Content-Type: application/json
- Permission: IsAuthenticated
- 说明:
  - GitHub 的 provider 是 `github`, 请注意大小写
  - WeChat 的 provider 是 `wechat`, 请注意大小写

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

  - HTTP/1.1 404

    ```json
      {
        "detail": "provider not found"
      }
    ```

  - HTTP/1.1 409

    ```json
      {
        "detail": "you need to set a phone or email for login purpose before unbinding GitHub"
      }
    ```

### 运营红点-获取提示信息

#### Request

- Method: **GET**
- URL: `/api/operation/red-dots`
- Content-Type: application/json
- Permission: IsAuthenticated
- 说明:
  - 在前端渲染页面的时候, 请求该接口来查询红点的状态

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success",
      "data": [
        {
          "name": "org-enroll",   // 红点的代号
          "visible": true,       // 是否显示
        },
        {
          "name": "company-info",
          "visible": true,
        },
        {
          "name": "join-org",   // 标识用户是否加入过团队，由后端来标记
          "visible": true,
        }
      ]
    }
    ```

- Failed

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

### 运营红点-标记为已读

#### Request

- Method: **POST**
- URL: `/api/operation/red-dots/<dot-name>`
- Content-Type: application/json
- Body:

  ```json
  {
    "action": "read"
  }
  ```

- Permission: IsAuthenticated
- 说明:
  - 当用户打开了某个页面，或者点击了某个 button 后，标记红点为已读

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

  - HTTP/1.1 401

    ```json
      {
        "detail": "Authentication credentials were not provided."
      }
    ```

### 获取用户公开信息

#### Request

- Method: **GET**
- URL: `/api/users/<username>`
- Content-Type: application/json
- Permission: Anyone

#### Response Example

- Success

  - HTTP/1.1 200

    ```json
    {
      "detail": "success",
      "data": {
        "username": "username",    // 用户名
        "avatar_url": "https://asktug.com/letter_avatar_proxy/v4/letter/h/ed8c4c/240.png",  // 头像
        "bio": "bio",              // 个人简介
        "joined_at": "2021-01-11", // 注册时间
        "points": 150,             // 积分
        "exps": 150,               // 经验
        "level": 3,                // 等级
        "level_desc": {            // 等级说明
          "min_exps": 100,            // 当前等级要求的最低经验值
          "max_exps": 200,            // 下一等级要求的最低经验值
          "progress": 0.5             // 当前等级的进度
        },
        "can_edit": true           // 是否可编辑该用户, 仅请求用户为当前用户时为 true
      }
    }
    ```

### 刷新图片验证码

#### Request
- Method: **POST**
- URL: `/api/captcha/refresh`
- Content-Type: application/json
- Permission: Anyone
- 说明: 刷新图片验证码

#### Response Example

- Success
  - HTTP/1.1 200
    ```json
    {
      "detail": "success",
      "data": {
        "key": "5abf340505f84e5b9af959481d73c915",
        "image_url": "https://accounts.pingcap.cn/captcha/image/5abf340505f84e5b9af959481d73c915"
      }
    }
    ```
