mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-05 16:24:11 +08:00
@@ -74,13 +74,13 @@ func newServer() (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
userRepo := repo5.NewUserRepo(client, ipdbIPDB, redisClient)
|
||||
userUsecase := usecase4.NewUserUsecase(configConfig, redisClient, userRepo, slogLogger)
|
||||
userHandler := v1_3.NewUserHandler(web, userUsecase, extensionUsecase, authMiddleware, activeMiddleware, sessionSession, slogLogger, configConfig)
|
||||
userUsecase := usecase4.NewUserUsecase(configConfig, redisClient, userRepo, slogLogger, sessionSession)
|
||||
dashboardRepo := repo6.NewDashboardRepo(client)
|
||||
dashboardUsecase := usecase5.NewDashboardUsecase(dashboardRepo)
|
||||
dashboardHandler := v1_4.NewDashboardHandler(web, dashboardUsecase, authMiddleware, activeMiddleware)
|
||||
billingRepo := repo7.NewBillingRepo(client)
|
||||
billingUsecase := usecase6.NewBillingUsecase(billingRepo)
|
||||
userHandler := v1_3.NewUserHandler(web, userUsecase, extensionUsecase, dashboardUsecase, billingUsecase, authMiddleware, activeMiddleware, sessionSession, slogLogger, configConfig)
|
||||
dashboardHandler := v1_4.NewDashboardHandler(web, dashboardUsecase, authMiddleware, activeMiddleware)
|
||||
billingHandler := v1_5.NewBillingHandler(web, billingUsecase, authMiddleware, activeMiddleware)
|
||||
server := &Server{
|
||||
config: configConfig,
|
||||
|
||||
@@ -14,7 +14,8 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
SessionName = "monkeycode_session"
|
||||
SessionName = "monkeycode_session"
|
||||
UserSessionName = "monkeycode_user_session"
|
||||
)
|
||||
|
||||
type UserPlatform string
|
||||
@@ -38,3 +39,10 @@ const (
|
||||
InviteCodeStatusPending InviteCodeStatus = "pending"
|
||||
InviteCodeStatusUsed InviteCodeStatus = "used"
|
||||
)
|
||||
|
||||
type LoginSource string
|
||||
|
||||
const (
|
||||
LoginSourcePlugin LoginSource = "plugin"
|
||||
LoginSourceBrowser LoginSource = "browser"
|
||||
)
|
||||
|
||||
@@ -1446,6 +1446,467 @@
|
||||
"responses": {}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/chat/info": {
|
||||
"get": {
|
||||
"description": "获取对话内容",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Record"
|
||||
],
|
||||
"summary": "获取对话内容",
|
||||
"operationId": "user-chat-info",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "对话记录ID",
|
||||
"name": "id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.ChatInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/chat/record": {
|
||||
"get": {
|
||||
"description": "获取用户对话记录",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Record"
|
||||
],
|
||||
"summary": "获取用户对话记录",
|
||||
"operationId": "user-list-chat-record",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "作者",
|
||||
"name": "author",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "是否接受筛选",
|
||||
"name": "is_accept",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "语言",
|
||||
"name": "language",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "下一页标识",
|
||||
"name": "next_token",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "分页",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "每页多少条记录",
|
||||
"name": "size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "工作模式",
|
||||
"name": "work_mode",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.ListChatRecordResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/completion/info": {
|
||||
"get": {
|
||||
"description": "获取补全内容",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Record"
|
||||
],
|
||||
"summary": "获取补全内容",
|
||||
"operationId": "user-completion-info",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "补全记录ID",
|
||||
"name": "id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.CompletionInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/completion/record": {
|
||||
"get": {
|
||||
"description": "获取补全记录",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Record"
|
||||
],
|
||||
"summary": "获取补全记录",
|
||||
"operationId": "user-list-completion-record",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "作者",
|
||||
"name": "author",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "是否接受筛选",
|
||||
"name": "is_accept",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "语言",
|
||||
"name": "language",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "下一页标识",
|
||||
"name": "next_token",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "分页",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "每页多少条记录",
|
||||
"name": "size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "工作模式",
|
||||
"name": "work_mode",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.ListCompletionRecordResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/dashboard/events": {
|
||||
"get": {
|
||||
"description": "获取用户事件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Dashboard"
|
||||
],
|
||||
"summary": "获取用户事件",
|
||||
"operationId": "user-dashboard-events",
|
||||
"parameters": [
|
||||
{
|
||||
"maximum": 90,
|
||||
"minimum": 24,
|
||||
"type": "integer",
|
||||
"default": 90,
|
||||
"description": "持续时间 (小时或天数)`",
|
||||
"name": "duration",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"hour",
|
||||
"day"
|
||||
],
|
||||
"type": "string",
|
||||
"default": "day",
|
||||
"description": "精度: \"hour\", \"day\"",
|
||||
"name": "precision",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "用户ID,可选参数",
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.UserEvent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/dashboard/heatmap": {
|
||||
"get": {
|
||||
"description": "用户热力图",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Dashboard"
|
||||
],
|
||||
"summary": "用户热力图",
|
||||
"operationId": "user-dashboard-heatmap",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.UserHeatmapResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/dashboard/stat": {
|
||||
"get": {
|
||||
"description": "获取用户统计信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Dashboard"
|
||||
],
|
||||
"summary": "获取用户统计信息",
|
||||
"operationId": "user-dashboard-stat",
|
||||
"parameters": [
|
||||
{
|
||||
"maximum": 90,
|
||||
"minimum": 24,
|
||||
"type": "integer",
|
||||
"default": 90,
|
||||
"description": "持续时间 (小时或天数)`",
|
||||
"name": "duration",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"hour",
|
||||
"day"
|
||||
],
|
||||
"type": "string",
|
||||
"default": "day",
|
||||
"description": "精度: \"hour\", \"day\"",
|
||||
"name": "precision",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "用户ID,可选参数",
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.UserStat"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/delete": {
|
||||
"delete": {
|
||||
"description": "删除用户",
|
||||
@@ -1785,6 +2246,22 @@
|
||||
"description": "会话ID",
|
||||
"name": "session_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"plugin",
|
||||
"browser"
|
||||
],
|
||||
"type": "string",
|
||||
"default": "plugin",
|
||||
"x-enum-varnames": [
|
||||
"LoginSourcePlugin",
|
||||
"LoginSourceBrowser"
|
||||
],
|
||||
"description": "登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin",
|
||||
"name": "source",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1809,6 +2286,99 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/profile": {
|
||||
"get": {
|
||||
"description": "获取用户信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Manage"
|
||||
],
|
||||
"summary": "获取用户信息",
|
||||
"operationId": "user-profile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.User"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "更新用户信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User Manage"
|
||||
],
|
||||
"summary": "更新用户信息",
|
||||
"operationId": "user-update-profile",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "param",
|
||||
"name": "req",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ProfileUpdateReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.User"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.Resp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/register": {
|
||||
"post": {
|
||||
"description": "注册用户",
|
||||
@@ -2107,6 +2677,17 @@
|
||||
"ChatRoleSystem"
|
||||
]
|
||||
},
|
||||
"consts.LoginSource": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"plugin",
|
||||
"browser"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"LoginSourcePlugin",
|
||||
"LoginSourceBrowser"
|
||||
]
|
||||
},
|
||||
"consts.ModelProvider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -2888,15 +3469,27 @@
|
||||
},
|
||||
"domain.LoginReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"source"
|
||||
],
|
||||
"properties": {
|
||||
"password": {
|
||||
"description": "密码",
|
||||
"type": "string"
|
||||
},
|
||||
"session_id": {
|
||||
"description": "会话Id",
|
||||
"description": "会话Id插件登录时必填",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin",
|
||||
"default": "plugin",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.LoginSource"
|
||||
}
|
||||
]
|
||||
},
|
||||
"username": {
|
||||
"description": "用户名",
|
||||
"type": "string"
|
||||
@@ -2909,6 +3502,14 @@
|
||||
"redirect_url": {
|
||||
"description": "重定向URL",
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "用户信息",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.User"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3124,6 +3725,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ProfileUpdateReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"description": "头像",
|
||||
"type": "string"
|
||||
},
|
||||
"old_password": {
|
||||
"description": "旧密码",
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"description": "密码",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "用户名",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ProviderModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -3728,19 +4350,19 @@
|
||||
}
|
||||
},
|
||||
"total_accepted_per": {
|
||||
"description": "近90天总接受率",
|
||||
"description": "总接受率",
|
||||
"type": "number"
|
||||
},
|
||||
"total_chats": {
|
||||
"description": "近90天总对话任务数",
|
||||
"description": "总对话任务数",
|
||||
"type": "integer"
|
||||
},
|
||||
"total_completions": {
|
||||
"description": "近90天总补全任务数",
|
||||
"description": "总补全任务数",
|
||||
"type": "integer"
|
||||
},
|
||||
"total_lines_of_code": {
|
||||
"description": "近90天总代码行数",
|
||||
"description": "总代码行数",
|
||||
"type": "integer"
|
||||
},
|
||||
"work_mode": {
|
||||
|
||||
@@ -13,19 +13,20 @@ import (
|
||||
type BillingUsecase interface {
|
||||
ListChatRecord(ctx context.Context, req ListRecordReq) (*ListChatRecordResp, error)
|
||||
ListCompletionRecord(ctx context.Context, req ListRecordReq) (*ListCompletionRecordResp, error)
|
||||
CompletionInfo(ctx context.Context, id string) (*CompletionInfo, error)
|
||||
ChatInfo(ctx context.Context, id string) (*ChatInfo, error)
|
||||
CompletionInfo(ctx context.Context, id, userID string) (*CompletionInfo, error)
|
||||
ChatInfo(ctx context.Context, id, userID string) (*ChatInfo, error)
|
||||
}
|
||||
|
||||
type BillingRepo interface {
|
||||
ListChatRecord(ctx context.Context, req ListRecordReq) (*ListChatRecordResp, error)
|
||||
ListCompletionRecord(ctx context.Context, req ListRecordReq) (*ListCompletionRecordResp, error)
|
||||
CompletionInfo(ctx context.Context, id string) (*CompletionInfo, error)
|
||||
ChatInfo(ctx context.Context, id string) (*ChatInfo, error)
|
||||
CompletionInfo(ctx context.Context, id, userID string) (*CompletionInfo, error)
|
||||
ChatInfo(ctx context.Context, id, userID string) (*ChatInfo, error)
|
||||
}
|
||||
|
||||
type ListRecordReq struct {
|
||||
*web.Pagination
|
||||
UserID string `json:"-"`
|
||||
Author string `json:"author" query:"author"` // 作者
|
||||
Language string `json:"language" query:"language"` // 语言
|
||||
WorkMode string `json:"work_mode" query:"work_mode"` // 工作模式
|
||||
|
||||
@@ -77,10 +77,10 @@ func (u *UserCodeRank) From(d *db.Task) *UserCodeRank {
|
||||
}
|
||||
|
||||
type UserStat struct {
|
||||
TotalChats int64 `json:"total_chats"` // 近90天总对话任务数
|
||||
TotalCompletions int64 `json:"total_completions"` // 近90天总补全任务数
|
||||
TotalLinesOfCode int64 `json:"total_lines_of_code"` // 近90天总代码行数
|
||||
TotalAcceptedPer float64 `json:"total_accepted_per"` // 近90天总接受率
|
||||
TotalChats int64 `json:"total_chats"` // 总对话任务数
|
||||
TotalCompletions int64 `json:"total_completions"` // 总补全任务数
|
||||
TotalLinesOfCode int64 `json:"total_lines_of_code"` // 总代码行数
|
||||
TotalAcceptedPer float64 `json:"total_accepted_per"` // 总接受率
|
||||
Chats []TimePoint[int64] `json:"chats"` // 对话任务数统计
|
||||
Completions []TimePoint[int64] `json:"code_completions"` // 补全任务数统计
|
||||
LinesOfCode []TimePoint[int64] `json:"lines_of_code"` // 代码行数统计
|
||||
|
||||
@@ -32,10 +32,11 @@ type OAuthUserInfo struct {
|
||||
}
|
||||
|
||||
type OAuthSignUpOrInReq struct {
|
||||
Platform consts.UserPlatform `json:"platform" query:"platform" validate:"required"` // 第三方平台 dingtalk
|
||||
SessionID string `json:"session_id" query:"session_id"` // 会话ID
|
||||
RedirectURL string `json:"redirect_url" query:"redirect_url"` // 登录成功后跳转的 URL
|
||||
InviteCode string `json:"inviate_code" query:"inviate_code"` // 邀请码
|
||||
Source consts.LoginSource `json:"source" query:"source" validate:"required" default:"plugin"` // 登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin
|
||||
Platform consts.UserPlatform `json:"platform" query:"platform" validate:"required"` // 第三方平台 dingtalk
|
||||
SessionID string `json:"session_id" query:"session_id"` // 会话ID
|
||||
RedirectURL string `json:"redirect_url" query:"redirect_url"` // 登录成功后跳转的 URL
|
||||
InviteCode string `json:"inviate_code" query:"inviate_code"` // 邀请码
|
||||
}
|
||||
|
||||
func (o OAuthSignUpOrInReq) OAuthKind() consts.OAuthKind {
|
||||
@@ -56,7 +57,8 @@ type OAuthURLResp struct {
|
||||
}
|
||||
|
||||
type OAuthState struct {
|
||||
Kind consts.OAuthKind `json:"kind" query:"kind" validate:"required"` // 注册或登录
|
||||
Source consts.LoginSource `json:"source"` // 登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin
|
||||
Kind consts.OAuthKind `json:"kind" query:"kind" validate:"required"` // invite: 邀请登录 login: 登录
|
||||
SessionID string `json:"session_id"` // 会话ID
|
||||
Platform consts.UserPlatform `json:"platform" query:"platform" validate:"required"` // 第三方平台 dingtalk
|
||||
RedirectURL string `json:"redirect_url" query:"redirect_url"` // 登录成功后跳转的 URL
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type UserUsecase interface {
|
||||
Login(ctx context.Context, req *LoginReq) (*LoginResp, error)
|
||||
Update(ctx context.Context, req *UpdateUserReq) (*User, error)
|
||||
ProfileUpdate(ctx context.Context, req *ProfileUpdateReq) (*User, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
InitAdmin(ctx context.Context) error
|
||||
AdminLogin(ctx context.Context, req *LoginReq) (*AdminUser, error)
|
||||
@@ -29,12 +30,12 @@ type UserUsecase interface {
|
||||
GetSetting(ctx context.Context) (*Setting, error)
|
||||
UpdateSetting(ctx context.Context, req *UpdateSettingReq) (*Setting, error)
|
||||
OAuthSignUpOrIn(ctx context.Context, req *OAuthSignUpOrInReq) (*OAuthURLResp, error)
|
||||
OAuthCallback(ctx context.Context, req *OAuthCallbackReq) (string, error)
|
||||
OAuthCallback(ctx *web.Context, req *OAuthCallbackReq) error
|
||||
}
|
||||
|
||||
type UserRepo interface {
|
||||
List(ctx context.Context, page *web.Pagination) ([]*db.User, *db.PageInfo, error)
|
||||
Update(ctx context.Context, id string, fn func(*db.UserUpdateOne) error) (*db.User, error)
|
||||
Update(ctx context.Context, id string, fn func(*db.User, *db.UserUpdateOne) error) (*db.User, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
InitAdmin(ctx context.Context, username, password string) error
|
||||
CreateUser(ctx context.Context, user *db.User) (*db.User, error)
|
||||
@@ -57,6 +58,14 @@ type UserRepo interface {
|
||||
SaveUserLoginHistory(ctx context.Context, userID, ip string, session *VSCodeSession) error
|
||||
}
|
||||
|
||||
type ProfileUpdateReq struct {
|
||||
UID string `json:"-"`
|
||||
Username *string `json:"username"` // 用户名
|
||||
Password *string `json:"password"` // 密码
|
||||
OldPassword *string `json:"old_password"` // 旧密码
|
||||
Avatar *string `json:"avatar"` // 头像
|
||||
}
|
||||
|
||||
type UpdateUserReq struct {
|
||||
ID string `json:"id" validate:"required"` // 用户ID
|
||||
Status *consts.UserStatus `json:"status"` // 用户状态 active: 正常 locked: 锁定 inactive: 禁用
|
||||
@@ -83,10 +92,11 @@ type VSCodeAuthInitResp struct {
|
||||
}
|
||||
|
||||
type LoginReq struct {
|
||||
SessionID string `json:"session_id"` // 会话Id
|
||||
Username string `json:"username"` // 用户名
|
||||
Password string `json:"password"` // 密码
|
||||
IP string `json:"-"` // IP地址
|
||||
Source consts.LoginSource `json:"source" validate:"required" default:"plugin"` // 登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin
|
||||
SessionID string `json:"session_id"` // 会话Id插件登录时必填
|
||||
Username string `json:"username"` // 用户名
|
||||
Password string `json:"password"` // 密码
|
||||
IP string `json:"-"` // IP地址
|
||||
}
|
||||
|
||||
type AdminLoginReq struct {
|
||||
@@ -95,7 +105,8 @@ type AdminLoginReq struct {
|
||||
}
|
||||
|
||||
type LoginResp struct {
|
||||
RedirectURL string `json:"redirect_url"` // 重定向URL
|
||||
RedirectURL string `json:"redirect_url"` // 重定向URL
|
||||
User *User `json:"user,omitempty"` // 用户信息
|
||||
}
|
||||
|
||||
type ListReq struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.23.7
|
||||
|
||||
require (
|
||||
entgo.io/ent v0.14.4
|
||||
github.com/GoYoko/web v1.0.0
|
||||
github.com/GoYoko/web v1.1.0
|
||||
github.com/cloudwego/eino v0.3.51
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250710065240-482d48888f25
|
||||
github.com/golang-migrate/migrate/v4 v4.18.3
|
||||
|
||||
@@ -8,8 +8,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/GoYoko/web v1.0.0 h1:kcNxz8BvpKavE0/iqatOmUeCXVghaoD5xYDiHDulVaE=
|
||||
github.com/GoYoko/web v1.0.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo=
|
||||
github.com/GoYoko/web v1.1.0 h1:nIbtol5z0Y03d0nHsvGjv+W0fgmFRGUL8fzPN3kmrOY=
|
||||
github.com/GoYoko/web v1.1.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
|
||||
@@ -84,7 +84,7 @@ func (h *BillingHandler) ListCompletionRecord(c *web.Context, req domain.ListRec
|
||||
// @Success 200 {object} web.Resp{data=domain.CompletionInfo}
|
||||
// @Router /api/v1/billing/completion/info [get]
|
||||
func (h *BillingHandler) CompletionInfo(c *web.Context) error {
|
||||
info, err := h.usecase.CompletionInfo(c.Request().Context(), c.QueryParam("id"))
|
||||
info, err := h.usecase.CompletionInfo(c.Request().Context(), c.QueryParam("id"), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (h *BillingHandler) CompletionInfo(c *web.Context) error {
|
||||
// @Success 200 {object} web.Resp{data=domain.ChatInfo}
|
||||
// @Router /api/v1/billing/chat/info [get]
|
||||
func (h *BillingHandler) ChatInfo(c *web.Context) error {
|
||||
info, err := h.usecase.ChatInfo(c.Request().Context(), c.QueryParam("id"))
|
||||
info, err := h.usecase.ChatInfo(c.Request().Context(), c.QueryParam("id"), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/chaitin/MonkeyCode/backend/domain"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/entx"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type BillingRepo struct {
|
||||
@@ -24,14 +25,21 @@ func NewBillingRepo(db *db.Client) domain.BillingRepo {
|
||||
}
|
||||
|
||||
// ChatInfo implements domain.BillingRepo.
|
||||
func (b *BillingRepo) ChatInfo(ctx context.Context, id string) (*domain.ChatInfo, error) {
|
||||
record, err := b.db.Task.Query().
|
||||
func (b *BillingRepo) ChatInfo(ctx context.Context, id, userID string) (*domain.ChatInfo, error) {
|
||||
q := b.db.Task.Query().
|
||||
WithTaskRecords(func(trq *db.TaskRecordQuery) {
|
||||
trq.Order(taskrecord.ByCreatedAt(sql.OrderAsc()))
|
||||
trq.Where(taskrecord.RoleNEQ(consts.ChatRoleSystem))
|
||||
}).
|
||||
Where(task.TaskID(id)).
|
||||
First(ctx)
|
||||
Where(task.TaskID(id))
|
||||
if userID != "" {
|
||||
uid, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Where(task.UserID(uid))
|
||||
}
|
||||
record, err := q.First(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -40,13 +48,20 @@ func (b *BillingRepo) ChatInfo(ctx context.Context, id string) (*domain.ChatInfo
|
||||
}
|
||||
|
||||
// CompletionInfo implements domain.BillingRepo.
|
||||
func (b *BillingRepo) CompletionInfo(ctx context.Context, id string) (*domain.CompletionInfo, error) {
|
||||
record, err := b.db.Task.Query().
|
||||
func (b *BillingRepo) CompletionInfo(ctx context.Context, id, userID string) (*domain.CompletionInfo, error) {
|
||||
q := b.db.Task.Query().
|
||||
WithTaskRecords(func(trq *db.TaskRecordQuery) {
|
||||
trq.Order(taskrecord.ByCreatedAt(sql.OrderAsc()))
|
||||
}).
|
||||
Where(task.TaskID(id)).
|
||||
First(ctx)
|
||||
Where(task.TaskID(id))
|
||||
if userID != "" {
|
||||
uid, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Where(task.UserID(uid))
|
||||
}
|
||||
record, err := q.First(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -86,6 +101,12 @@ func filterTask(q *db.TaskQuery, req domain.ListRecordReq) {
|
||||
q.Where(task.IsAccept(*req.IsAccept))
|
||||
}
|
||||
|
||||
if req.UserID != "" {
|
||||
if uid, err := uuid.Parse(req.UserID); err == nil {
|
||||
q.Where(task.UserID(uid))
|
||||
}
|
||||
}
|
||||
|
||||
if req.Author != "" {
|
||||
q.Where(task.HasUserWith(func(s *sql.Selector) {
|
||||
s.Where(sql.Like(s.C(user.FieldUsername), "%"+req.Author+"%"))
|
||||
|
||||
@@ -25,11 +25,11 @@ func (b *BillingUsecase) ListCompletionRecord(ctx context.Context, req domain.Li
|
||||
}
|
||||
|
||||
// CompletionInfo implements domain.BillingUsecase.
|
||||
func (b *BillingUsecase) CompletionInfo(ctx context.Context, id string) (*domain.CompletionInfo, error) {
|
||||
return b.repo.CompletionInfo(ctx, id)
|
||||
func (b *BillingUsecase) CompletionInfo(ctx context.Context, id, userID string) (*domain.CompletionInfo, error) {
|
||||
return b.repo.CompletionInfo(ctx, id, userID)
|
||||
}
|
||||
|
||||
// ChatInfo implements domain.BillingUsecase.
|
||||
func (b *BillingUsecase) ChatInfo(ctx context.Context, id string) (*domain.ChatInfo, error) {
|
||||
return b.repo.ChatInfo(ctx, id)
|
||||
func (b *BillingUsecase) ChatInfo(ctx context.Context, id, userID string) (*domain.ChatInfo, error) {
|
||||
return b.repo.ChatInfo(ctx, id, userID)
|
||||
}
|
||||
|
||||
@@ -29,12 +29,18 @@ func (a *ActiveMiddleware) Active(scope string) echo.MiddlewareFunc {
|
||||
return func(c echo.Context) error {
|
||||
switch scope {
|
||||
case "admin":
|
||||
if user := GetUser(c); user != nil {
|
||||
if user := GetAdmin(c); user != nil {
|
||||
if err := a.redis.Set(context.Background(), fmt.Sprintf(consts.AdminActiveKeyFmt, user.ID), time.Now().Unix(), 0).Err(); err != nil {
|
||||
a.logger.With("error", err).ErrorContext(c.Request().Context(), "failed to set admin active status in Redis")
|
||||
}
|
||||
}
|
||||
case "user":
|
||||
if user := GetUser((c)); user != nil {
|
||||
if err := a.redis.Set(context.Background(), fmt.Sprintf(consts.UserActiveKeyFmt, user.ID), time.Now().Unix(), 0).Err(); err != nil {
|
||||
a.logger.With("error", err).ErrorContext(c.Request().Context(), "failed to set user active status in Redis")
|
||||
}
|
||||
}
|
||||
case "apikey":
|
||||
if apikey := GetApiKey(c); apikey != nil {
|
||||
if err := a.redis.Set(context.Background(), fmt.Sprintf(consts.UserActiveKeyFmt, apikey.UserID), time.Now().Unix(), 0).Err(); err != nil {
|
||||
a.logger.With("error", err).ErrorContext(c.Request().Context(), "failed to set user active status in Redis")
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
userKey = "session:user"
|
||||
adminKey = "session:admin"
|
||||
userKey = "session:user"
|
||||
)
|
||||
|
||||
type AuthMiddleware struct {
|
||||
@@ -27,10 +28,10 @@ func NewAuthMiddleware(session *session.Session, logger *slog.Logger) *AuthMiddl
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) Auth() echo.MiddlewareFunc {
|
||||
func (m *AuthMiddleware) UserAuth() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
user, err := session.Get[domain.AdminUser](m.session, c, consts.SessionName)
|
||||
user, err := session.Get[domain.User](m.session, c, consts.UserSessionName)
|
||||
if err != nil {
|
||||
m.logger.Error("auth failed", "error", err)
|
||||
return c.String(http.StatusUnauthorized, "Unauthorized")
|
||||
@@ -41,6 +42,32 @@ func (m *AuthMiddleware) Auth() echo.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func GetUser(c echo.Context) *domain.AdminUser {
|
||||
return c.Get(userKey).(*domain.AdminUser)
|
||||
func (m *AuthMiddleware) Auth() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
user, err := session.Get[domain.AdminUser](m.session, c, consts.SessionName)
|
||||
if err != nil {
|
||||
m.logger.Error("auth failed", "error", err)
|
||||
return c.String(http.StatusUnauthorized, "Unauthorized")
|
||||
}
|
||||
c.Set(adminKey, &user)
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetAdmin(c echo.Context) *domain.AdminUser {
|
||||
i := c.Get(adminKey)
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
return i.(*domain.AdminUser)
|
||||
}
|
||||
|
||||
func GetUser(c echo.Context) *domain.User {
|
||||
i := c.Get(userKey)
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
return i.(*domain.User)
|
||||
}
|
||||
|
||||
@@ -53,5 +53,9 @@ func (p *ProxyMiddleware) Auth() echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
func GetApiKey(c echo.Context) *domain.ApiKey {
|
||||
return c.Get(ApiContextKey).(*domain.ApiKey)
|
||||
i := c.Get(ApiContextKey)
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
return i.(*domain.ApiKey)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (h *ModelHandler) List(c *web.Context) error {
|
||||
// @Success 200 {object} web.Resp{data=[]domain.Model}
|
||||
// @Router /api/v1/model/my [get]
|
||||
func (h *ModelHandler) MyModelList(c *web.Context, req domain.MyModelListReq) error {
|
||||
user := middleware.GetUser(c)
|
||||
user := middleware.GetAdmin(c)
|
||||
req.UserID = user.ID
|
||||
models, err := h.usecase.MyModelList(c.Request().Context(), &req)
|
||||
if err != nil {
|
||||
@@ -109,7 +109,7 @@ func (h *ModelHandler) MyModelList(c *web.Context, req domain.MyModelListReq) er
|
||||
// @Success 200 {object} web.Resp{data=domain.Model}
|
||||
// @Router /api/v1/model [post]
|
||||
func (h *ModelHandler) Create(c *web.Context, req domain.CreateModelReq) error {
|
||||
user := middleware.GetUser(c)
|
||||
user := middleware.GetAdmin(c)
|
||||
req.UserID = user.ID
|
||||
m, err := h.usecase.Create(c.Request().Context(), &req)
|
||||
if err != nil {
|
||||
|
||||
@@ -49,11 +49,11 @@ func NewV1Handler(
|
||||
|
||||
g := w.Group("/v1", middleware.Auth())
|
||||
g.GET("/models", web.BaseHandler(h.ModelList))
|
||||
g.POST("/completion/accept", web.BindHandler(h.AcceptCompletion), active.Active("user"))
|
||||
g.POST("/report", web.BindHandler(h.Report), active.Active("user"))
|
||||
g.POST("/chat/completions", web.BaseHandler(h.ChatCompletion), active.Active("user"))
|
||||
g.POST("/completions", web.BaseHandler(h.Completions), active.Active("user"))
|
||||
g.POST("/embeddings", web.BaseHandler(h.Embeddings), active.Active("user"))
|
||||
g.POST("/completion/accept", web.BindHandler(h.AcceptCompletion), active.Active("apikey"))
|
||||
g.POST("/report", web.BindHandler(h.Report), active.Active("apikey"))
|
||||
g.POST("/chat/completions", web.BaseHandler(h.ChatCompletion), active.Active("apikey"))
|
||||
g.POST("/completions", web.BaseHandler(h.Completions), active.Active("apikey"))
|
||||
g.POST("/embeddings", web.BaseHandler(h.Embeddings), active.Active("apikey"))
|
||||
return h
|
||||
}
|
||||
|
||||
|
||||
73
backend/internal/user/handler/v1/dashboard.go
Normal file
73
backend/internal/user/handler/v1/dashboard.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/GoYoko/web"
|
||||
|
||||
"github.com/chaitin/MonkeyCode/backend/domain"
|
||||
"github.com/chaitin/MonkeyCode/backend/internal/middleware"
|
||||
)
|
||||
|
||||
// UserStat 获取用户统计信息
|
||||
//
|
||||
// @Tags User Dashboard
|
||||
// @Summary 获取用户统计信息
|
||||
// @Description 获取用户统计信息
|
||||
// @ID user-dashboard-stat
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param filter query domain.StatisticsFilter true "筛选参数"
|
||||
// @Success 200 {object} web.Resp{data=domain.UserStat}
|
||||
// @Failure 401 {object} string
|
||||
// @Router /api/v1/user/dashboard/stat [get]
|
||||
func (h *UserHandler) UserStat(c *web.Context, req domain.StatisticsFilter) error {
|
||||
req.UserID = middleware.GetUser(c).ID
|
||||
userStat, err := h.duse.UserStat(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Success(userStat)
|
||||
}
|
||||
|
||||
// UserEvents 获取用户事件
|
||||
//
|
||||
// @Tags User Dashboard
|
||||
// @Summary 获取用户事件
|
||||
// @Description 获取用户事件
|
||||
// @ID user-dashboard-events
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param filter query domain.StatisticsFilter true "筛选参数"
|
||||
// @Success 200 {object} web.Resp{data=[]domain.UserEvent}
|
||||
// @Failure 401 {object} string
|
||||
// @Router /api/v1/user/dashboard/events [get]
|
||||
func (h *UserHandler) UserEvents(c *web.Context) error {
|
||||
userEvents, err := h.duse.UserEvents(c.Request().Context(), domain.StatisticsFilter{
|
||||
Precision: "day",
|
||||
Duration: 90,
|
||||
UserID: middleware.GetUser(c).ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Success(userEvents)
|
||||
}
|
||||
|
||||
// UserHeatmap 用户热力图
|
||||
//
|
||||
// @Tags User Dashboard
|
||||
// @Summary 用户热力图
|
||||
// @Description 用户热力图
|
||||
// @ID user-dashboard-heatmap
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} web.Resp{data=domain.UserHeatmapResp}
|
||||
// @Failure 401 {object} string
|
||||
// @Router /api/v1/user/dashboard/heatmap [get]
|
||||
func (h *UserHandler) UserHeatmap(c *web.Context) error {
|
||||
userID := middleware.GetUser(c).ID
|
||||
userHeatmap, err := h.duse.UserHeatmap(c.Request().Context(), userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Success(userHeatmap)
|
||||
}
|
||||
91
backend/internal/user/handler/v1/record.go
Normal file
91
backend/internal/user/handler/v1/record.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/GoYoko/web"
|
||||
"github.com/chaitin/MonkeyCode/backend/domain"
|
||||
"github.com/chaitin/MonkeyCode/backend/internal/middleware"
|
||||
)
|
||||
|
||||
// ListChatRecord 获取用户对话记录
|
||||
//
|
||||
// @Tags User Record
|
||||
// @Summary 获取用户对话记录
|
||||
// @Description 获取用户对话记录
|
||||
// @ID user-list-chat-record
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query domain.ListRecordReq true "参数"
|
||||
// @Success 200 {object} web.Resp{data=domain.ListChatRecordResp}
|
||||
// @Failure 401 {object} string
|
||||
// @Router /api/v1/user/chat/record [get]
|
||||
func (h *UserHandler) ListChatRecord(c *web.Context, req domain.ListRecordReq) error {
|
||||
req.Pagination = c.Page()
|
||||
req.UserID = middleware.GetUser(c).ID
|
||||
records, err := h.buse.ListChatRecord(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Success(records)
|
||||
}
|
||||
|
||||
// ListCompletionRecord 获取补全记录
|
||||
//
|
||||
// @Tags User Record
|
||||
// @Summary 获取补全记录
|
||||
// @Description 获取补全记录
|
||||
// @ID user-list-completion-record
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query domain.ListRecordReq true "参数"
|
||||
// @Success 200 {object} web.Resp{data=domain.ListCompletionRecordResp}
|
||||
// @Failure 401 {object} string
|
||||
// @Router /api/v1/user/completion/record [get]
|
||||
func (h *UserHandler) ListCompletionRecord(c *web.Context, req domain.ListRecordReq) error {
|
||||
req.Pagination = c.Page()
|
||||
req.UserID = middleware.GetUser(c).ID
|
||||
records, err := h.buse.ListCompletionRecord(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Success(records)
|
||||
}
|
||||
|
||||
// CompletionInfo 获取补全内容
|
||||
//
|
||||
// @Tags User Record
|
||||
// @Summary 获取补全内容
|
||||
// @Description 获取补全内容
|
||||
// @ID user-completion-info
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id query string true "补全记录ID"
|
||||
// @Success 200 {object} web.Resp{data=domain.CompletionInfo}
|
||||
// @Failure 401 {object} string
|
||||
// @Router /api/v1/user/completion/info [get]
|
||||
func (h *UserHandler) CompletionInfo(c *web.Context) error {
|
||||
info, err := h.buse.CompletionInfo(c.Request().Context(), c.QueryParam("id"), middleware.GetUser(c).ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Success(info)
|
||||
}
|
||||
|
||||
// ChatInfo 获取对话内容
|
||||
//
|
||||
// @Tags User Record
|
||||
// @Summary 获取对话内容
|
||||
// @Description 获取对话内容
|
||||
// @ID user-chat-info
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id query string true "对话记录ID"
|
||||
// @Success 200 {object} web.Resp{data=domain.ChatInfo}
|
||||
// @Failure 401 {object} string
|
||||
// @Router /api/v1/user/chat/info [get]
|
||||
func (h *UserHandler) ChatInfo(c *web.Context) error {
|
||||
info, err := h.buse.ChatInfo(c.Request().Context(), c.QueryParam("id"), middleware.GetUser(c).ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Success(info)
|
||||
}
|
||||
@@ -31,6 +31,8 @@ type CacheEntry struct {
|
||||
type UserHandler struct {
|
||||
usecase domain.UserUsecase
|
||||
euse domain.ExtensionUsecase
|
||||
duse domain.DashboardUsecase
|
||||
buse domain.BillingUsecase
|
||||
session *session.Session
|
||||
logger *slog.Logger
|
||||
cfg *config.Config
|
||||
@@ -43,6 +45,8 @@ func NewUserHandler(
|
||||
w *web.Web,
|
||||
usecase domain.UserUsecase,
|
||||
euse domain.ExtensionUsecase,
|
||||
duse domain.DashboardUsecase,
|
||||
buse domain.BillingUsecase,
|
||||
auth *middleware.AuthMiddleware,
|
||||
active *middleware.ActiveMiddleware,
|
||||
session *session.Session,
|
||||
@@ -51,10 +55,12 @@ func NewUserHandler(
|
||||
) *UserHandler {
|
||||
u := &UserHandler{
|
||||
usecase: usecase,
|
||||
euse: euse,
|
||||
duse: duse,
|
||||
buse: buse,
|
||||
session: session,
|
||||
logger: logger,
|
||||
cfg: cfg,
|
||||
euse: euse,
|
||||
vsixCache: make(map[string]*CacheEntry),
|
||||
limiter: rate.NewLimiter(rate.Every(time.Duration(cfg.Extension.LimitSecond)*time.Second), cfg.Extension.Limit),
|
||||
}
|
||||
@@ -82,6 +88,9 @@ func NewUserHandler(
|
||||
g.POST("/register", web.BindHandler(u.Register))
|
||||
g.POST("/login", web.BindHandler(u.Login))
|
||||
|
||||
g.GET("/profile", web.BaseHandler(u.Profile), auth.UserAuth())
|
||||
g.PUT("/profile", web.BindHandler(u.UpdateProfile), auth.UserAuth())
|
||||
|
||||
g.Use(auth.Auth(), active.Active("admin"))
|
||||
|
||||
g.PUT("/update", web.BindHandler(u.Update))
|
||||
@@ -90,6 +99,24 @@ func NewUserHandler(
|
||||
g.GET("/list", web.BindHandler(u.List, web.WithPage()))
|
||||
g.GET("/login-history", web.BaseHandler(u.LoginHistory, web.WithPage()))
|
||||
|
||||
// user dashboard
|
||||
d := w.Group("/api/v1/user/dashboard")
|
||||
d.Use(auth.UserAuth(), active.Active("user"))
|
||||
d.GET("/stat", web.BindHandler(u.UserStat))
|
||||
d.GET("/events", web.BaseHandler(u.UserEvents))
|
||||
d.GET("/heatmap", web.BaseHandler(u.UserHeatmap))
|
||||
|
||||
// user record
|
||||
uc := w.Group("/api/v1/user/chat")
|
||||
uc.Use(auth.UserAuth(), active.Active("user"))
|
||||
uc.GET("/record", web.BindHandler(u.ListChatRecord, web.WithPage()))
|
||||
uc.GET("/info", web.BaseHandler(u.ChatInfo))
|
||||
|
||||
cplt := w.Group("/api/v1/user/completion")
|
||||
cplt.Use(auth.UserAuth(), active.Active("user"))
|
||||
cplt.GET("/record", web.BindHandler(u.ListCompletionRecord, web.WithPage()))
|
||||
cplt.GET("/info", web.BaseHandler(u.CompletionInfo))
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -199,6 +226,11 @@ func (h *UserHandler) Login(c *web.Context, req domain.LoginReq) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Source == consts.LoginSourceBrowser {
|
||||
if _, err := h.session.Save(c, consts.UserSessionName, c.Request().Host, resp.User); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.Success(resp)
|
||||
}
|
||||
|
||||
@@ -333,7 +365,7 @@ func (h *UserHandler) LoginHistory(c *web.Context) error {
|
||||
// @Success 200 {object} web.Resp{data=domain.InviteResp}
|
||||
// @Router /api/v1/user/invite [get]
|
||||
func (h *UserHandler) Invite(c *web.Context) error {
|
||||
user := middleware.GetUser(c)
|
||||
user := middleware.GetAdmin(c)
|
||||
resp, err := h.usecase.Invite(c.Request().Context(), user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -373,7 +405,7 @@ func (h *UserHandler) Register(c *web.Context, req domain.RegisterReq) error {
|
||||
// @Success 200 {object} web.Resp{data=domain.AdminUser}
|
||||
// @Router /api/v1/admin/create [post]
|
||||
func (h *UserHandler) CreateAdmin(c *web.Context, req domain.CreateAdminReq) error {
|
||||
user := middleware.GetUser(c)
|
||||
user := middleware.GetAdmin(c)
|
||||
if user.Username != "admin" {
|
||||
return errcode.ErrPermission
|
||||
}
|
||||
@@ -471,6 +503,7 @@ func (h *UserHandler) UpdateSetting(c *web.Context, req domain.UpdateSettingReq)
|
||||
// @Success 200 {object} web.Resp{data=domain.OAuthURLResp}
|
||||
// @Router /api/v1/user/oauth/signup-or-in [get]
|
||||
func (h *UserHandler) OAuthSignUpOrIn(ctx *web.Context, req domain.OAuthSignUpOrInReq) error {
|
||||
h.logger.With("req", req).DebugContext(ctx.Request().Context(), "OAuthSignUpOrIn")
|
||||
resp, err := h.usecase.OAuthSignUpOrIn(ctx.Request().Context(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -491,12 +524,43 @@ func (h *UserHandler) OAuthSignUpOrIn(ctx *web.Context, req domain.OAuthSignUpOr
|
||||
// @Router /api/v1/user/oauth/callback [get]
|
||||
func (h *UserHandler) OAuthCallback(ctx *web.Context, req domain.OAuthCallbackReq) error {
|
||||
req.IP = ctx.RealIP()
|
||||
resp, err := h.usecase.OAuthCallback(ctx.Request().Context(), &req)
|
||||
return h.usecase.OAuthCallback(ctx, &req)
|
||||
}
|
||||
|
||||
// Profile 获取用户信息
|
||||
//
|
||||
// @Tags User Manage
|
||||
// @Summary 获取用户信息
|
||||
// @Description 获取用户信息
|
||||
// @ID user-profile
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} web.Resp{data=domain.User}
|
||||
// @Failure 401 {object} web.Resp{}
|
||||
// @Router /api/v1/user/profile [get]
|
||||
func (h *UserHandler) Profile(ctx *web.Context) error {
|
||||
return ctx.Success(middleware.GetUser(ctx))
|
||||
}
|
||||
|
||||
// UpdateProfile 更新用户信息
|
||||
//
|
||||
// @Tags User Manage
|
||||
// @Summary 更新用户信息
|
||||
// @Description 更新用户信息
|
||||
// @ID user-update-profile
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param req body domain.ProfileUpdateReq true "param"
|
||||
// @Success 200 {object} web.Resp{data=domain.User}
|
||||
// @Failure 401 {object} web.Resp{}
|
||||
// @Router /api/v1/user/profile [put]
|
||||
func (h *UserHandler) UpdateProfile(ctx *web.Context, req domain.ProfileUpdateReq) error {
|
||||
req.UID = middleware.GetUser(ctx).ID
|
||||
user, err := h.usecase.ProfileUpdate(ctx.Request().Context(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Redirect(http.StatusFound, resp)
|
||||
return nil
|
||||
return ctx.Success(user)
|
||||
}
|
||||
|
||||
func (h *UserHandler) InitAdmin() error {
|
||||
|
||||
@@ -221,7 +221,7 @@ func (r *UserRepo) UpdateSetting(ctx context.Context, fn func(*db.Setting, *db.S
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (r *UserRepo) Update(ctx context.Context, id string, fn func(*db.UserUpdateOne) error) (*db.User, error) {
|
||||
func (r *UserRepo) Update(ctx context.Context, id string, fn func(*db.User, *db.UserUpdateOne) error) (*db.User, error) {
|
||||
uid, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -233,7 +233,7 @@ func (r *UserRepo) Update(ctx context.Context, id string, fn func(*db.UserUpdate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fn(u.Update()); err != nil {
|
||||
if err := fn(u, u.Update()); err != nil {
|
||||
return err
|
||||
}
|
||||
return u.Update().Exec(ctx)
|
||||
@@ -420,17 +420,20 @@ func (r *UserRepo) SaveUserLoginHistory(ctx context.Context, userID string, ip s
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = r.db.UserLoginHistory.Create().
|
||||
c := r.db.UserLoginHistory.Create().
|
||||
SetUserID(uid).
|
||||
SetIP(ip).
|
||||
SetCity(addr.City).
|
||||
SetCountry(addr.Country).
|
||||
SetProvince(addr.Province).
|
||||
SetClientVersion(session.Version).
|
||||
SetOsType(session.OSType).
|
||||
SetOsRelease(session.OSRelease).
|
||||
SetClientID(session.ClientID).
|
||||
SetHostname(session.Hostname).
|
||||
Save(ctx)
|
||||
return err
|
||||
SetProvince(addr.Province)
|
||||
|
||||
if session != nil {
|
||||
c.SetClientVersion(session.Version).
|
||||
SetOsType(session.OSType).
|
||||
SetOsRelease(session.OSRelease).
|
||||
SetClientID(session.ClientID).
|
||||
SetHostname(session.Hostname)
|
||||
}
|
||||
|
||||
return c.Exec(ctx)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -23,13 +24,15 @@ import (
|
||||
"github.com/chaitin/MonkeyCode/backend/errcode"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/oauth"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/session"
|
||||
)
|
||||
|
||||
type UserUsecase struct {
|
||||
cfg *config.Config
|
||||
redis *redis.Client
|
||||
repo domain.UserRepo
|
||||
logger *slog.Logger
|
||||
cfg *config.Config
|
||||
redis *redis.Client
|
||||
repo domain.UserRepo
|
||||
logger *slog.Logger
|
||||
session *session.Session
|
||||
}
|
||||
|
||||
func NewUserUsecase(
|
||||
@@ -37,12 +40,14 @@ func NewUserUsecase(
|
||||
redis *redis.Client,
|
||||
repo domain.UserRepo,
|
||||
logger *slog.Logger,
|
||||
session *session.Session,
|
||||
) domain.UserUsecase {
|
||||
u := &UserUsecase{
|
||||
cfg: cfg,
|
||||
redis: redis,
|
||||
repo: repo,
|
||||
logger: logger,
|
||||
cfg: cfg,
|
||||
redis: redis,
|
||||
repo: repo,
|
||||
logger: logger,
|
||||
session: session,
|
||||
}
|
||||
return u
|
||||
}
|
||||
@@ -205,22 +210,36 @@ func (u *UserUsecase) Login(ctx context.Context, req *domain.LoginReq) (*domain.
|
||||
return nil, errcode.ErrPassword.Wrap(err)
|
||||
}
|
||||
|
||||
apiKey, err := u.repo.GetOrCreateApiKey(ctx, user.ID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch req.Source {
|
||||
case consts.LoginSourcePlugin:
|
||||
apiKey, err := u.repo.GetOrCreateApiKey(ctx, user.ID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, session, err := u.getVSCodeURL(ctx, req.SessionID, apiKey.Key, user.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := u.repo.SaveUserLoginHistory(ctx, user.ID.String(), req.IP, session); err != nil {
|
||||
u.logger.With("error", err).Error("save user login history")
|
||||
}
|
||||
return &domain.LoginResp{
|
||||
RedirectURL: r,
|
||||
}, nil
|
||||
|
||||
case consts.LoginSourceBrowser:
|
||||
if err := u.repo.SaveUserLoginHistory(ctx, user.ID.String(), req.IP, nil); err != nil {
|
||||
u.logger.With("error", err).Error("save user login history")
|
||||
}
|
||||
return &domain.LoginResp{
|
||||
RedirectURL: "",
|
||||
User: cvt.From(user, &domain.User{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
r, session, err := u.getVSCodeURL(ctx, req.SessionID, apiKey.Key, user.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := u.repo.SaveUserLoginHistory(ctx, user.ID.String(), req.IP, session); err != nil {
|
||||
u.logger.With("error", err).Error("save user login history")
|
||||
}
|
||||
return &domain.LoginResp{
|
||||
RedirectURL: r,
|
||||
}, nil
|
||||
return nil, fmt.Errorf("invalid login source")
|
||||
}
|
||||
|
||||
func (u *UserUsecase) getVSCodeURL(ctx context.Context, sessionID, apiKey, username string) (string, *domain.VSCodeSession, error) {
|
||||
@@ -376,7 +395,7 @@ func (u *UserUsecase) UpdateSetting(ctx context.Context, req *domain.UpdateSetti
|
||||
}
|
||||
|
||||
func (u *UserUsecase) Update(ctx context.Context, req *domain.UpdateUserReq) (*domain.User, error) {
|
||||
user, err := u.repo.Update(ctx, req.ID, func(u *db.UserUpdateOne) error {
|
||||
user, err := u.repo.Update(ctx, req.ID, func(_ *db.User, u *db.UserUpdateOne) error {
|
||||
if req.Status != nil {
|
||||
u.SetStatus(*req.Status)
|
||||
}
|
||||
@@ -455,6 +474,7 @@ func (u *UserUsecase) OAuthSignUpOrIn(ctx context.Context, req *domain.OAuthSign
|
||||
state, url := oauth.GetAuthorizeURL()
|
||||
|
||||
session := &domain.OAuthState{
|
||||
Source: req.Source,
|
||||
SessionID: req.SessionID,
|
||||
Kind: req.OAuthKind(),
|
||||
Platform: req.Platform,
|
||||
@@ -474,35 +494,56 @@ func (u *UserUsecase) OAuthSignUpOrIn(ctx context.Context, req *domain.OAuthSign
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *UserUsecase) OAuthCallback(ctx context.Context, req *domain.OAuthCallbackReq) (string, error) {
|
||||
func (u *UserUsecase) OAuthCallback(c *web.Context, req *domain.OAuthCallbackReq) error {
|
||||
ctx := c.Request().Context()
|
||||
b, err := u.redis.Get(ctx, fmt.Sprintf("oauth:state:%s", req.State)).Result()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
var session domain.OAuthState
|
||||
if err := json.Unmarshal([]byte(b), &session); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
switch session.Kind {
|
||||
case consts.OAuthKindInvite:
|
||||
return u.WithOAuthCallback(ctx, req, &session, func(ctx context.Context, s *domain.OAuthState, oui *domain.OAuthUserInfo) (*db.User, error) {
|
||||
_, redirect, err := u.WithOAuthCallback(ctx, req, &session, func(ctx context.Context, s *domain.OAuthState, oui *domain.OAuthUserInfo) (*db.User, error) {
|
||||
return u.repo.OAuthRegister(ctx, s.Platform, s.InviteCode, oui)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Redirect(http.StatusFound, redirect)
|
||||
return nil
|
||||
|
||||
case consts.OAuthKindLogin:
|
||||
setting, err := u.repo.GetSetting(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
return u.WithOAuthCallback(ctx, req, &session, func(ctx context.Context, s *domain.OAuthState, oui *domain.OAuthUserInfo) (*db.User, error) {
|
||||
user, redirect, err := u.WithOAuthCallback(ctx, req, &session, func(ctx context.Context, s *domain.OAuthState, oui *domain.OAuthUserInfo) (*db.User, error) {
|
||||
if setting.EnableAutoLogin {
|
||||
return u.repo.SignUpOrIn(ctx, s.Platform, oui)
|
||||
}
|
||||
return u.repo.OAuthLogin(ctx, s.Platform, oui)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if session.Source == consts.LoginSourceBrowser {
|
||||
resUser := cvt.From(user, &domain.User{})
|
||||
u.logger.With("user", resUser).With("host", c.Request().Host).DebugContext(ctx, "save user session")
|
||||
if _, err := u.session.Save(c, consts.UserSessionName, c.Request().Host, resUser); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, redirect)
|
||||
return nil
|
||||
|
||||
default:
|
||||
return "", errcode.ErrOAuthStateInvalid
|
||||
return errcode.ErrOAuthStateInvalid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,28 +571,27 @@ func (u *UserUsecase) FetchUserInfo(ctx context.Context, req *domain.OAuthCallba
|
||||
|
||||
type OAuthUserRepoHandle func(context.Context, *domain.OAuthState, *domain.OAuthUserInfo) (*db.User, error)
|
||||
|
||||
func (u *UserUsecase) WithOAuthCallback(ctx context.Context, req *domain.OAuthCallbackReq, session *domain.OAuthState, handle OAuthUserRepoHandle) (string, error) {
|
||||
func (u *UserUsecase) WithOAuthCallback(ctx context.Context, req *domain.OAuthCallbackReq, session *domain.OAuthState, handle OAuthUserRepoHandle) (*db.User, string, error) {
|
||||
info, err := u.FetchUserInfo(ctx, req, session)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
user, err := handle(ctx, session, info)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apiKey, err := u.repo.GetOrCreateApiKey(ctx, user.ID.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
redirect := session.RedirectURL
|
||||
|
||||
if session.SessionID != "" {
|
||||
apiKey, err := u.repo.GetOrCreateApiKey(ctx, user.ID.String())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
r, vsess, err := u.getVSCodeURL(ctx, session.SessionID, apiKey.Key, user.Username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, "", err
|
||||
}
|
||||
redirect = fmt.Sprintf("%s?redirect_url=%s", redirect, url.QueryEscape(r))
|
||||
if err := u.repo.SaveUserLoginHistory(ctx, user.ID.String(), req.IP, vsess); err != nil {
|
||||
@@ -559,6 +599,36 @@ func (u *UserUsecase) WithOAuthCallback(ctx context.Context, req *domain.OAuthCa
|
||||
}
|
||||
}
|
||||
|
||||
u.logger.Debug("oauth callback", "redirect", redirect)
|
||||
return redirect, nil
|
||||
u.logger.With("session", session).Debug("oauth callback", "redirect", redirect)
|
||||
return user, redirect, nil
|
||||
}
|
||||
|
||||
func (u *UserUsecase) ProfileUpdate(ctx context.Context, req *domain.ProfileUpdateReq) (*domain.User, error) {
|
||||
user, err := u.repo.Update(ctx, req.UID, func(old *db.User, uuo *db.UserUpdateOne) error {
|
||||
if req.Avatar != nil {
|
||||
uuo.SetAvatarURL(*req.Avatar)
|
||||
}
|
||||
|
||||
if req.Username != nil {
|
||||
uuo.SetUsername(*req.Username)
|
||||
}
|
||||
|
||||
if req.Password != nil && req.OldPassword != nil {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(old.Password), []byte(*req.OldPassword)); err != nil {
|
||||
return errcode.ErrPassword.Wrap(err)
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return errcode.ErrPassword.Wrap(err)
|
||||
}
|
||||
uuo.SetPassword(string(hash))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cvt.From(user, &domain.User{}), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user