Files
2025-12-05 12:55:26 +08:00

786 lines
22 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package v1
import (
"bytes"
"context"
"crypto/md5"
"fmt"
"log/slog"
"net/http"
"strings"
"sync"
"time"
"github.com/GoYoko/web"
"golang.org/x/time/rate"
"github.com/chaitin/MonkeyCode/backend/config"
"github.com/chaitin/MonkeyCode/backend/consts"
"github.com/chaitin/MonkeyCode/backend/domain"
"github.com/chaitin/MonkeyCode/backend/errcode"
"github.com/chaitin/MonkeyCode/backend/internal/middleware"
"github.com/chaitin/MonkeyCode/backend/pkg/session"
"github.com/chaitin/MonkeyCode/backend/pkg/version"
"github.com/chaitin/MonkeyCode/backend/pkg/vsix"
)
// CacheEntry 缓存条目
type CacheEntry struct {
data []byte
createdAt time.Time
}
type UserHandler struct {
usecase domain.UserUsecase
euse domain.ExtensionUsecase
duse domain.DashboardUsecase
buse domain.BillingUsecase
scuse domain.SecurityScanningUsecase
session *session.Session
logger *slog.Logger
cfg *config.Config
vsixCache map[string]*CacheEntry
zipCache map[string]*CacheEntry
cacheMu sync.RWMutex
limiter *rate.Limiter
}
func NewUserHandler(
w *web.Web,
usecase domain.UserUsecase,
euse domain.ExtensionUsecase,
scuse domain.SecurityScanningUsecase,
duse domain.DashboardUsecase,
buse domain.BillingUsecase,
auth *middleware.AuthMiddleware,
active *middleware.ActiveMiddleware,
readonly *middleware.ReadOnlyMiddleware,
session *session.Session,
logger *slog.Logger,
cfg *config.Config,
) *UserHandler {
u := &UserHandler{
usecase: usecase,
euse: euse,
duse: duse,
buse: buse,
scuse: scuse,
session: session,
logger: logger,
cfg: cfg,
vsixCache: make(map[string]*CacheEntry),
zipCache: make(map[string]*CacheEntry),
limiter: rate.NewLimiter(rate.Every(time.Duration(cfg.Extension.LimitSecond)*time.Second), cfg.Extension.Limit),
}
w.GET("/api/v1/static/vsix/:version", web.BaseHandler(u.VSIXDownload))
w.GET("/api/v1/static/vsix", web.BaseHandler(u.VSIXDownload))
w.GET("/api/v1/static/jetbrains/:version", web.BaseHandler(u.ZipDownload))
w.GET("/api/v1/static/jetbrains", web.BaseHandler(u.ZipDownload))
w.POST("/api/v1/vscode/init-auth", web.BindHandler(u.VSCodeAuthInit))
// admin
admin := w.Group("/api/v1/admin")
admin.POST("/login", web.BindHandler(u.AdminLogin))
admin.GET("/setting", web.BaseHandler(u.GetSetting))
admin.GET("/role", web.BaseHandler(u.ListRole))
admin.Use(auth.Auth(), active.Active("admin"), readonly.Guard())
admin.GET("/profile", web.BaseHandler(u.AdminProfile))
admin.GET("/list", web.BaseHandler(u.AdminList, web.WithPage()))
admin.GET("/login-history", web.BaseHandler(u.AdminLoginHistory, web.WithPage()))
admin.PUT("/setting", web.BindHandler(u.UpdateSetting))
admin.POST("/create", web.BindHandler(u.CreateAdmin))
admin.POST("/logout", web.BaseHandler(u.AdminLogout))
admin.DELETE("/delete", web.BaseHandler(u.DeleteAdmin))
admin.GET("/export-completion-data", web.BaseHandler(u.ExportCompletionData))
admin.POST("/role", web.BindHandler(u.GrantRole))
// user
g := w.Group("/api/v1/user")
g.GET("/oauth/signup-or-in", web.BindHandler(u.OAuthSignUpOrIn))
g.GET("/oauth/callback", web.BindHandler(u.OAuthCallback))
g.POST("/register", web.BindHandler(u.Register))
g.POST("/login", web.BindHandler(u.Login))
g.Use(readonly.Guard())
g.GET("/profile", web.BaseHandler(u.Profile), auth.UserAuth())
g.PUT("/profile", web.BindHandler(u.UpdateProfile), auth.UserAuth())
g.POST("/logout", web.BaseHandler(u.Logout), auth.UserAuth())
g.Use(auth.Auth(), active.Active("admin"))
g.PUT("/update", web.BindHandler(u.Update))
g.DELETE("/delete", web.BaseHandler(u.Delete))
g.GET("/invite", web.BaseHandler(u.Invite))
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))
// user security
sc := w.Group("/api/v1/user/security")
sc.Use(auth.UserAuth(), active.Active("user"))
sc.GET("/scanning", web.BindHandler(u.SecurityList, web.WithPage()))
sc.GET("/scanning/detail", web.BaseHandler(u.SecurityDetail))
return u
}
func (h *UserHandler) VSCodeAuthInit(c *web.Context, req domain.VSCodeAuthInitReq) error {
s, err := h.usecase.GetSetting(c.Request().Context())
if err != nil {
return err
}
req.BaseURL = h.cfg.GetBaseURL(c.Request(), s)
resp, err := h.usecase.VSCodeAuthInit(c.Request().Context(), &req)
if err != nil {
return err
}
return c.JSON(http.StatusOK, resp)
}
// generateCacheKey 生成缓存键
func (h *UserHandler) generateCacheKey(version, baseUrl string) string {
hash := md5.Sum([]byte(version + ":" + baseUrl))
return fmt.Sprintf("%x", hash)
}
// cleanExpiredCache 清理过期缓存
func (h *UserHandler) cleanExpiredCache() {
h.cacheMu.Lock()
defer h.cacheMu.Unlock()
now := time.Now()
for key, entry := range h.vsixCache {
// 缓存1小时后过期
if now.Sub(entry.createdAt) > time.Hour {
delete(h.vsixCache, key)
}
}
if h.zipCache != nil {
for key, entry := range h.zipCache {
if now.Sub(entry.createdAt) > time.Hour {
delete(h.zipCache, key)
}
}
}
}
// VSIXDownload 下载VSCode插件
//
// @Tags User
// @Summary 下载VSCode插件
// @Description 下载VSCode插件
// @ID vsix-download
// @Accept json
// @Produce octet-stream
// @Router /api/v1/static/vsix [get]
func (h *UserHandler) VSIXDownload(c *web.Context) error {
if !h.limiter.Allow() {
return c.String(http.StatusTooManyRequests, "Too Many Requests")
}
s, err := h.usecase.GetSetting(c.Request().Context())
if err != nil {
return err
}
host := c.Request().Host
h.logger.With("url", c.Request().URL).With("header", c.Request().Header).With("host", host).DebugContext(c.Request().Context(), "vsix download")
cacheKey := h.generateCacheKey(version.Version, h.cfg.GetBaseURL(c.Request(), s))
version := strings.Trim(version.Version, "v")
h.cacheMu.RLock()
if entry, exists := h.vsixCache[cacheKey]; exists {
if time.Since(entry.createdAt) < time.Hour {
h.cacheMu.RUnlock()
disposition := fmt.Sprintf("attachment; filename=monkeycode-%s.vsix", version)
c.Response().Header().Set("Content-Type", "application/octet-stream")
c.Response().Header().Set("Content-Disposition", disposition)
c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(entry.data)))
_, err := c.Response().Writer.Write(entry.data)
return err
}
}
h.cacheMu.RUnlock()
var buf bytes.Buffer
if err := vsix.ChangeVsixEndpoint(fmt.Sprintf("/app/assets/vsix/monkeycode-%s.vsix", version), "extension/package.json", h.cfg.GetBaseURL(c.Request(), s), &buf); err != nil {
return err
}
data := buf.Bytes()
h.cacheMu.Lock()
h.vsixCache[cacheKey] = &CacheEntry{
data: data,
createdAt: time.Now(),
}
h.cacheMu.Unlock()
go h.cleanExpiredCache()
disposition := fmt.Sprintf("attachment; filename=monkeycode-%s.vsix", version)
c.Response().Header().Set("Content-Type", "application/octet-stream")
c.Response().Header().Set("Content-Disposition", disposition)
c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
_, err = c.Response().Writer.Write(data)
return err
}
// ZipDownload 下载ZIP包Jetbrains插件包
//
// @Tags User
// @Summary 下载ZIP包
// @Description 下载ZIP安装包缓存1小时
// @ID zip-download
// @Accept json
// @Produce octet-stream
// @Router /api/v1/static/jetbrains [get]
func (h *UserHandler) ZipDownload(c *web.Context) error {
if !h.limiter.Allow() {
return c.String(http.StatusTooManyRequests, "Too Many Requests")
}
s, err := h.usecase.GetSetting(c.Request().Context())
if err != nil {
return err
}
host := c.Request().Host
h.logger.With("url", c.Request().URL).With("header", c.Request().Header).With("host", host).DebugContext(c.Request().Context(), "zip download")
cacheKey := h.generateCacheKey(version.Version, h.cfg.GetBaseURL(c.Request(), s))
ver := strings.Trim(version.Version, "v")
// 缓存命中
h.cacheMu.RLock()
if entry, exists := h.zipCache[cacheKey]; exists {
if time.Since(entry.createdAt) < time.Hour {
h.cacheMu.RUnlock()
disposition := fmt.Sprintf("attachment; filename=monkeycode-%s.zip", ver)
c.Response().Header().Set("Content-Type", "application/octet-stream")
c.Response().Header().Set("Content-Disposition", disposition)
c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(entry.data)))
fmt.Println(c.Response().Header())
_, werr := c.Response().Writer.Write(entry.data)
return werr
}
}
h.cacheMu.RUnlock()
var buf bytes.Buffer
if err := vsix.ChangeVsixEndpoint(fmt.Sprintf("/app/assets/jetbrains/monkeycode-%s.zip", ver), "roo-code/package.json", h.cfg.GetBaseURL(c.Request(), s), &buf); err != nil {
return err
}
data := buf.Bytes()
h.cacheMu.Lock()
h.zipCache[cacheKey] = &CacheEntry{
data: data,
createdAt: time.Now(),
}
h.cacheMu.Unlock()
// 异步清理
go h.cleanExpiredCache()
// 响应输出
disposition := fmt.Sprintf("attachment; filename=monkeycode-%s.zip", ver)
c.Response().Header().Set("Content-Type", "application/octet-stream")
c.Response().Header().Set("Content-Disposition", disposition)
c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
_, err = c.Response().Writer.Write(data)
return err
}
// Login 用户登录
//
// @Tags User
// @Summary 用户登录
// @Description 用户登录
// @ID login
// @Accept json
// @Produce json
// @Param param body domain.LoginReq true "登录参数"
// @Success 200 {object} web.Resp{data=domain.LoginResp}
// @Router /api/v1/user/login [post]
func (h *UserHandler) Login(c *web.Context, req domain.LoginReq) error {
req.IP = c.RealIP()
resp, err := h.usecase.Login(c.Request().Context(), &req)
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)
}
// Logout 用户登出
//
// @Tags User
// @Summary 用户登出
// @Description 用户登出
// @ID logout
// @Accept json
// @Produce json
// @Success 200 {object} web.Resp{}
// @Router /api/v1/user/logout [post]
func (h *UserHandler) Logout(c *web.Context) error {
if err := h.session.Del(c, consts.UserSessionName); err != nil {
return err
}
return c.Success(nil)
}
// Update 更新用户
//
// @Tags User
// @Summary 更新用户
// @Description 更新用户
// @ID update-user
// @Accept json
// @Produce json
// @Param param body domain.UpdateUserReq true "更新用户参数"
// @Success 200 {object} web.Resp{data=domain.User}
// @Router /api/v1/user/update [put]
func (h *UserHandler) Update(c *web.Context, req domain.UpdateUserReq) error {
resp, err := h.usecase.Update(c.Request().Context(), &req)
if err != nil {
return err
}
return c.Success(resp)
}
// Delete 删除用户
//
// @Tags User
// @Summary 删除用户
// @Description 删除用户
// @ID delete-user
// @Accept json
// @Produce json
// @Param id query string true "用户ID"
// @Success 200 {object} web.Resp{data=nil}
// @Router /api/v1/user/delete [delete]
func (h *UserHandler) Delete(c *web.Context) error {
err := h.usecase.Delete(c.Request().Context(), c.QueryParam("id"))
if err != nil {
return err
}
return c.Success(nil)
}
// DeleteAdmin 删除管理员
//
// @Tags Admin
// @Summary 删除管理员
// @Description 删除管理员
// @ID delete-admin
// @Accept json
// @Produce json
// @Param id query string true "管理员ID"
// @Success 200 {object} web.Resp{data=nil}
// @Router /api/v1/admin/delete [delete]
func (h *UserHandler) DeleteAdmin(c *web.Context) error {
err := h.usecase.DeleteAdmin(c.Request().Context(), c.QueryParam("id"))
if err != nil {
return err
}
return c.Success(nil)
}
// AdminLogin 管理员登录
//
// @Tags Admin
// @Summary 管理员登录
// @Description 管理员登录
// @ID admin-login
// @Accept json
// @Produce json
// @Param param body domain.LoginReq true "登录参数"
// @Success 200 {object} web.Resp{data=domain.AdminUser}
// @Router /api/v1/admin/login [post]
func (h *UserHandler) AdminLogin(c *web.Context, req domain.LoginReq) error {
req.IP = c.RealIP()
resp, err := h.usecase.AdminLogin(c.Request().Context(), &req)
if err != nil {
return err
}
h.logger.With("header", c.Request().Header).With("host", c.Request().Host).Info("admin login", "username", resp.Username)
if _, err := h.session.Save(c, consts.SessionName, c.Request().Host, resp); err != nil {
return err
}
return c.Success(resp)
}
// AdminLogout 管理员登出
//
// @Tags Admin
// @Summary 管理员登出
// @Description 管理员登出
// @ID admin-logout
// @Accept json
// @Produce json
// @Success 200 {object} web.Resp{}
// @Router /api/v1/admin/logout [post]
func (h *UserHandler) AdminLogout(c *web.Context) error {
if err := h.session.Del(c, consts.SessionName); err != nil {
return err
}
return c.Success(nil)
}
// AdminProfile 管理员信息
//
// @Tags Admin
// @Summary 管理员信息
// @Description 管理员信息
// @ID admin-profile
// @Accept json
// @Produce json
// @Success 200 {object} web.Resp{data=domain.AdminUser}
// @Router /api/v1/admin/profile [get]
func (h *UserHandler) AdminProfile(c *web.Context) error {
user := middleware.GetAdmin(c)
return c.Success(user)
}
// List 获取用户列表
//
// @Tags User
// @Summary 获取用户列表
// @Description 获取用户列表
// @ID list-user
// @Accept json
// @Produce json
// @Param page query web.Pagination true "分页"
// @Success 200 {object} web.Resp{data=domain.ListUserResp}
// @Router /api/v1/user/list [get]
func (h *UserHandler) List(c *web.Context, req domain.ListReq) error {
resp, err := h.usecase.List(c.Request().Context(), req)
if err != nil {
return err
}
return c.Success(resp)
}
// LoginHistory 获取用户登录历史
//
// @Tags User
// @Summary 获取用户登录历史
// @Description 获取用户登录历史
// @ID login-history
// @Accept json
// @Produce json
// @Param page query web.Pagination true "分页"
// @Success 200 {object} web.Resp{data=domain.ListLoginHistoryResp}
// @Router /api/v1/user/login-history [get]
func (h *UserHandler) LoginHistory(c *web.Context) error {
resp, err := h.usecase.LoginHistory(c.Request().Context(), c.Page())
if err != nil {
return err
}
return c.Success(resp)
}
// Invite 获取用户邀请码
//
// @Tags User
// @Summary 获取用户邀请码
// @Description 获取用户邀请码
// @ID invite
// @Accept json
// @Produce json
// @Success 200 {object} web.Resp{data=domain.InviteResp}
// @Router /api/v1/user/invite [get]
func (h *UserHandler) Invite(c *web.Context) error {
admin := middleware.GetAdmin(c)
resp, err := h.usecase.Invite(c.Request().Context(), admin.ID.String())
if err != nil {
return err
}
return c.Success(resp)
}
// Register 注册用户
//
// @Tags User
// @Summary 注册用户
// @Description 注册用户
// @ID register
// @Accept json
// @Produce json
// @Param param body domain.RegisterReq true "注册参数"
// @Success 200 {object} web.Resp{data=domain.User}
// @Router /api/v1/user/register [post]
func (h *UserHandler) Register(c *web.Context, req domain.RegisterReq) error {
resp, err := h.usecase.Register(c.Request().Context(), &req)
if err != nil {
return err
}
return c.Success(resp)
}
// CreateAdmin 创建管理员
//
// @Tags Admin
// @Summary 创建管理员
// @Description 创建管理员
// @ID create-admin
// @Accept json
// @Produce json
// @Param param body domain.CreateAdminReq true "创建管理员参数"
// @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.GetAdmin(c)
if user.Username != "admin" {
return errcode.ErrPermission
}
resp, err := h.usecase.CreateAdmin(c.Request().Context(), &req)
if err != nil {
return err
}
return c.Success(resp)
}
// AdminList 获取管理员用户列表
//
// @Tags Admin
// @Summary 获取管理员用户列表
// @Description 获取管理员用户列表
// @ID list-admin-user
// @Accept json
// @Produce json
// @Param page query web.Pagination true "分页"
// @Success 200 {object} web.Resp{data=domain.ListAdminUserResp}
// @Router /api/v1/admin/list [get]
func (h *UserHandler) AdminList(c *web.Context) error {
resp, err := h.usecase.AdminList(c.Request().Context(), c.Page())
if err != nil {
return err
}
return c.Success(resp)
}
// AdminLoginHistory 获取管理员登录历史
//
// @Tags Admin
// @Summary 获取管理员登录历史
// @Description 获取管理员登录历史
// @ID admin-login-history
// @Accept json
// @Produce json
// @Param page query web.Pagination true "分页"
// @Success 200 {object} web.Resp{data=domain.ListAdminLoginHistoryResp}
// @Router /api/v1/admin/login-history [get]
func (h *UserHandler) AdminLoginHistory(c *web.Context) error {
resp, err := h.usecase.AdminLoginHistory(c.Request().Context(), c.Page())
if err != nil {
return err
}
return c.Success(resp)
}
// ListRole 获取系统角色列表
//
// @Tags Admin
// @Summary 获取角色列表
// @Description 获取角色列表
// @ID list-role
// @Accept json
// @Produce json
// @Success 200 {object} web.Resp{data=[]domain.Role}
// @Router /api/v1/admin/role [get]
func (h *UserHandler) ListRole(c *web.Context) error {
roles, err := h.usecase.ListRole(c.Request().Context())
if err != nil {
return err
}
return c.Success(roles)
}
// GrantRole 授权角色
//
// @Tags Admin
// @Summary 授权角色
// @Description 授权角色
// @ID grant-role
// @Accept json
// @Produce json
// @Param param body domain.GrantRoleReq true "授权角色参数"
// @Success 200 {object} web.Resp
// @Router /api/v1/admin/role [post]
func (h *UserHandler) GrantRole(c *web.Context, req domain.GrantRoleReq) error {
if err := h.usecase.GrantRole(c.Request().Context(), &req); err != nil {
return err
}
return c.Success(nil)
}
// GetSetting 获取系统设置
//
// @Tags Admin
// @Summary 获取系统设置
// @Description 获取系统设置
// @ID get-setting
// @Accept json
// @Produce json
// @Success 200 {object} web.Resp{data=domain.Setting}
// @Router /api/v1/admin/setting [get]
func (h *UserHandler) GetSetting(c *web.Context) error {
resp, err := h.usecase.GetSetting(c.Request().Context())
if err != nil {
return err
}
return c.Success(resp)
}
// UpdateSetting 更新系统设置
//
// @Tags Admin
// @Summary 更新系统设置
// @Description 更新为增量更新,只传需要更新的字段
// @ID update-setting
// @Accept json
// @Produce json
// @Param param body domain.UpdateSettingReq true "更新系统设置参数"
// @Success 200 {object} web.Resp{data=domain.Setting}
// @Router /api/v1/admin/setting [put]
func (h *UserHandler) UpdateSetting(c *web.Context, req domain.UpdateSettingReq) error {
resp, err := h.usecase.UpdateSetting(c.Request().Context(), &req)
if err != nil {
return err
}
return c.Success(resp)
}
// OAuthSignUpOrIn 用户 OAuth 登录或注册
//
// @Tags User
// @Summary 用户 OAuth 登录或注册
// @Description 用户 OAuth 登录或注册
// @ID user-oauth-signup-or-in
// @Accept json
// @Produce json
// @Param req query domain.OAuthSignUpOrInReq true "param"
// @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")
s, err := h.usecase.GetSetting(ctx.Request().Context())
if err != nil {
return err
}
req.BaseURL = h.cfg.GetBaseURL(ctx.Request(), s)
resp, err := h.usecase.OAuthSignUpOrIn(ctx.Request().Context(), &req)
if err != nil {
return err
}
return ctx.Success(resp)
}
// OAuthCallback 用户 OAuth 回调
//
// @Tags User
// @Summary 用户 OAuth 回调
// @Description 用户 OAuth 回调
// @ID user-oauth-callback
// @Accept json
// @Produce json
// @Param req query domain.OAuthCallbackReq true "param"
// @Success 200 {object} web.Resp{data=string}
// @Router /api/v1/user/oauth/callback [get]
func (h *UserHandler) OAuthCallback(ctx *web.Context, req domain.OAuthCallbackReq) error {
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
}
return ctx.Success(user)
}
func (h *UserHandler) InitAdmin() error {
return h.usecase.InitAdmin(context.Background())
}
// ExportCompletionData godoc
//
// @Summary 导出补全数据
// @Description 管理员导出所有补全相关数据
// @Tags admin
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Success 200 {object} domain.ExportCompletionDataResp
// @Failure 401 {object} web.Resp{}
// @Failure 500 {object} web.Resp{}
// @Router /api/v1/admin/export-completion-data [get]
func (h *UserHandler) ExportCompletionData(c *web.Context) error {
data, err := h.usecase.ExportCompletionData(c.Request().Context())
if err != nil {
return err
}
return c.JSON(http.StatusOK, data)
}