功能开发完成
This commit is contained in:
@@ -5,9 +5,9 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/logger"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/service"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// RepoHandler 仓库API处理器
|
||||
@@ -23,6 +23,15 @@ func NewRepoHandler(repoService *service.RepoService) *RepoHandler {
|
||||
}
|
||||
|
||||
// AddBatch 批量添加仓库
|
||||
// @Summary 批量添加仓库
|
||||
// @Description 批量添加多个Git仓库,异步克隆到本地
|
||||
// @Tags 仓库管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.AddReposRequest true "仓库URL列表"
|
||||
// @Success 200 {object} Response{data=service.AddReposResponse}
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /repos/batch [post]
|
||||
func (h *RepoHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.AddReposRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -30,8 +39,8 @@ func (h *RepoHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.URLs) == 0 {
|
||||
respondError(w, http.StatusBadRequest, 40001, "urls cannot be empty")
|
||||
if len(req.Repos) == 0 {
|
||||
respondError(w, http.StatusBadRequest, 40001, "repos cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,6 +55,16 @@ func (h *RepoHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// List 获取仓库列表
|
||||
// @Summary 获取仓库列表
|
||||
// @Description 分页查询仓库列表,支持按状态筛选
|
||||
// @Tags 仓库管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param status query string false "状态筛选(pending/cloning/ready/failed)"
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(20)
|
||||
// @Success 200 {object} Response
|
||||
// @Router /repos [get]
|
||||
func (h *RepoHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
status := r.URL.Query().Get("status")
|
||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
@@ -76,6 +95,15 @@ func (h *RepoHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Get 获取仓库详情
|
||||
// @Summary 获取仓库详情
|
||||
// @Description 根据ID获取仓库详细信息
|
||||
// @Tags 仓库管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "仓库ID"
|
||||
// @Success 200 {object} Response{data=models.Repository}
|
||||
// @Failure 404 {object} Response
|
||||
// @Router /repos/{id} [get]
|
||||
func (h *RepoHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -93,6 +121,16 @@ func (h *RepoHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// SwitchBranch 切换分支
|
||||
// @Summary 切换仓库分支
|
||||
// @Description 异步切换仓库到指定分支
|
||||
// @Tags 仓库管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "仓库ID"
|
||||
// @Param request body object{branch=string} true "分支名称"
|
||||
// @Success 200 {object} Response{data=models.Task}
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /repos/{id}/switch-branch [post]
|
||||
func (h *RepoHandler) SwitchBranch(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -124,6 +162,15 @@ func (h *RepoHandler) SwitchBranch(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Update 更新仓库
|
||||
// @Summary 更新仓库
|
||||
// @Description 异步拉取仓库最新代码(git pull)
|
||||
// @Tags 仓库管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "仓库ID"
|
||||
// @Success 200 {object} Response{data=models.Task}
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /repos/{id}/update [post]
|
||||
func (h *RepoHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -142,6 +189,14 @@ func (h *RepoHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Reset 重置仓库
|
||||
// @Summary 重置仓库
|
||||
// @Description 异步重置仓库到最新状态
|
||||
// @Tags 仓库管理
|
||||
// @Produce json
|
||||
// @Param id path int true "仓库ID"
|
||||
// @Success 200 {object} Response{data=models.Task}
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /repos/{id}/reset [post]
|
||||
func (h *RepoHandler) Reset(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -160,6 +215,14 @@ func (h *RepoHandler) Reset(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Delete 删除仓库
|
||||
// @Summary 删除仓库
|
||||
// @Description 删除指定仓库
|
||||
// @Tags 仓库管理
|
||||
// @Produce json
|
||||
// @Param id path int true "仓库ID"
|
||||
// @Success 200 {object} Response
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /repos/{id} [delete]
|
||||
func (h *RepoHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -175,3 +238,35 @@ func (h *RepoHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "repository deleted successfully", nil)
|
||||
}
|
||||
|
||||
// GetBranches 获取仓库分支列表
|
||||
// @Summary 获取仓库分支列表
|
||||
// @Description 获取指定仓库的所有分支
|
||||
// @Tags 仓库管理
|
||||
// @Produce json
|
||||
// @Param id path int true "仓库ID"
|
||||
// @Success 200 {object} Response{data=object}
|
||||
// @Failure 400 {object} Response
|
||||
// @Failure 404 {object} Response
|
||||
// @Router /repos/{id}/branches [get]
|
||||
func (h *RepoHandler) GetBranches(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, 40001, "invalid repository id")
|
||||
return
|
||||
}
|
||||
|
||||
branches, err := h.repoService.GetBranches(r.Context(), id)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Int64("repo_id", id).Msg("failed to get branches")
|
||||
respondError(w, http.StatusInternalServerError, 50000, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"branches": branches,
|
||||
"count": len(branches),
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", data)
|
||||
}
|
||||
|
||||
@@ -7,21 +7,33 @@ import (
|
||||
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/logger"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/service"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/storage"
|
||||
)
|
||||
|
||||
// StatsHandler 统计API处理器
|
||||
type StatsHandler struct {
|
||||
statsService *service.StatsService
|
||||
store storage.Store
|
||||
}
|
||||
|
||||
// NewStatsHandler 创建统计处理器
|
||||
func NewStatsHandler(statsService *service.StatsService) *StatsHandler {
|
||||
func NewStatsHandler(statsService *service.StatsService, store storage.Store) *StatsHandler {
|
||||
return &StatsHandler{
|
||||
statsService: statsService,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate 触发统计计算
|
||||
// @Summary 触发统计任务
|
||||
// @Description 异步触发统计计算任务
|
||||
// @Tags 统计管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.CalculateRequest true "统计请求"
|
||||
// @Success 200 {object} Response{data=models.Task}
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /stats/calculate [post]
|
||||
func (h *StatsHandler) Calculate(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.CalculateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -56,6 +68,19 @@ func (h *StatsHandler) Calculate(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// QueryResult 查询统计结果
|
||||
// @Summary 查询统计结果
|
||||
// @Description 查询统计计算结果
|
||||
// @Tags 统计管理
|
||||
// @Produce json
|
||||
// @Param repo_id query int true "仓库ID"
|
||||
// @Param branch query string true "分支名称"
|
||||
// @Param constraint_type query string false "约束类型"
|
||||
// @Param from query string false "开始日期"
|
||||
// @Param to query string false "结束日期"
|
||||
// @Param limit query int false "提交数限制"
|
||||
// @Success 200 {object} Response{data=models.StatsResult}
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /stats/query [get]
|
||||
func (h *StatsHandler) QueryResult(w http.ResponseWriter, r *http.Request) {
|
||||
repoID, _ := strconv.ParseInt(r.URL.Query().Get("repo_id"), 10, 64)
|
||||
branch := r.URL.Query().Get("branch")
|
||||
@@ -98,6 +123,16 @@ func (h *StatsHandler) QueryResult(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// CountCommits 统计提交次数
|
||||
// @Summary 统计提交次数
|
||||
// @Description 统计指定条件下的提交次数
|
||||
// @Tags 统计管理
|
||||
// @Produce json
|
||||
// @Param repo_id query int true "仓库ID"
|
||||
// @Param branch query string true "分支名称"
|
||||
// @Param from query string false "开始日期"
|
||||
// @Success 200 {object} Response{data=service.CountCommitsResponse}
|
||||
// @Failure 400 {object} Response
|
||||
// @Router /stats/commits/count [get]
|
||||
func (h *StatsHandler) CountCommits(w http.ResponseWriter, r *http.Request) {
|
||||
repoID, _ := strconv.ParseInt(r.URL.Query().Get("repo_id"), 10, 64)
|
||||
branch := r.URL.Query().Get("branch")
|
||||
@@ -128,3 +163,62 @@ func (h *StatsHandler) CountCommits(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", result)
|
||||
}
|
||||
|
||||
// ListCaches 获取统计缓存列表
|
||||
// @Summary 获取统计缓存列表
|
||||
// @Description 获取已计算的统计缓存列表
|
||||
// @Tags 统计管理
|
||||
// @Produce json
|
||||
// @Param repo_id query int false "仓库ID(可选,不传则返回所有)"
|
||||
// @Param limit query int false "返回数量限制" default(50)
|
||||
// @Success 200 {object} Response{data=object}
|
||||
// @Failure 500 {object} Response
|
||||
// @Router /stats/caches [get]
|
||||
func (h *StatsHandler) ListCaches(w http.ResponseWriter, r *http.Request) {
|
||||
repoID, _ := strconv.ParseInt(r.URL.Query().Get("repo_id"), 10, 64)
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
|
||||
limit := 50
|
||||
if limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||
limit = l
|
||||
if limit > 200 {
|
||||
limit = 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
caches, total, err := h.store.StatsCache().List(r.Context(), repoID, limit)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to list stats caches")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to list stats caches")
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"caches": caches,
|
||||
"total": total,
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", data)
|
||||
}
|
||||
|
||||
// ClearAllCaches 清除所有统计缓存
|
||||
// @Summary 清除所有统计缓存
|
||||
// @Description 删除所有统计缓存记录和文件
|
||||
// @Tags 统计管理
|
||||
// @Produce json
|
||||
// @Success 200 {object} Response
|
||||
// @Failure 500 {object} Response
|
||||
// @Router /stats/caches/clear [delete]
|
||||
func (h *StatsHandler) ClearAllCaches(w http.ResponseWriter, r *http.Request) {
|
||||
// 删除数据库中的缓存记录
|
||||
if err := h.store.StatsCache().DeleteAll(r.Context()); err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to clear all caches")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to clear caches")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.Info().Msg("all stats caches cleared")
|
||||
respondJSON(w, http.StatusOK, 0, "所有统计缓存已清除", nil)
|
||||
}
|
||||
|
||||
99
internal/api/handlers/task.go
Normal file
99
internal/api/handlers/task.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/logger"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/storage"
|
||||
)
|
||||
|
||||
// TaskHandler 任务API处理器
|
||||
type TaskHandler struct {
|
||||
store storage.Store
|
||||
}
|
||||
|
||||
// NewTaskHandler 创建任务处理器
|
||||
func NewTaskHandler(store storage.Store) *TaskHandler {
|
||||
return &TaskHandler{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// List 查询任务列表
|
||||
// @Summary 查询任务列表
|
||||
// @Description 查询任务列表,可按状态过滤
|
||||
// @Tags 任务管理
|
||||
// @Produce json
|
||||
// @Param status query string false "任务状态"
|
||||
// @Param limit query int false "返回数量限制" default(50)
|
||||
// @Success 200 {object} Response{data=object}
|
||||
// @Failure 500 {object} Response
|
||||
// @Router /tasks [get]
|
||||
func (h *TaskHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
status := r.URL.Query().Get("status")
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
|
||||
limit := 50
|
||||
if limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||
limit = l
|
||||
if limit > 200 {
|
||||
limit = 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 List 方法,repoID=0 表示不过滤仓库
|
||||
tasks, total, err := h.store.Tasks().List(r.Context(), 0, status, 1, limit)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to list tasks")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to list tasks")
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"tasks": tasks,
|
||||
"total": total,
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", data)
|
||||
}
|
||||
|
||||
// ClearAllTasks 清除所有任务记录
|
||||
// @Summary 清除所有任务记录
|
||||
// @Description 删除所有任务记录(包括进行中的)
|
||||
// @Tags 任务管理
|
||||
// @Produce json
|
||||
// @Success 200 {object} Response
|
||||
// @Failure 500 {object} Response
|
||||
// @Router /tasks/clear [delete]
|
||||
func (h *TaskHandler) ClearAllTasks(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h.store.Tasks().DeleteAll(r.Context()); err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to clear all tasks")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to clear tasks")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.Info().Msg("all tasks cleared")
|
||||
respondJSON(w, http.StatusOK, 0, "所有任务记录已清除", nil)
|
||||
}
|
||||
|
||||
// ClearCompletedTasks 清除已完成的任务记录
|
||||
// @Summary 清除已完成的任务记录
|
||||
// @Description 删除已完成、失败或取消的任务记录
|
||||
// @Tags 任务管理
|
||||
// @Produce json
|
||||
// @Success 200 {object} Response
|
||||
// @Failure 500 {object} Response
|
||||
// @Router /tasks/clear-completed [delete]
|
||||
func (h *TaskHandler) ClearCompletedTasks(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h.store.Tasks().DeleteCompleted(r.Context()); err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to clear completed tasks")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to clear completed tasks")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.Info().Msg("completed tasks cleared")
|
||||
respondJSON(w, http.StatusOK, 0, "已完成的任务记录已清除", nil)
|
||||
}
|
||||
@@ -3,23 +3,32 @@ package api
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
_ "github.com/gitcodestatic/gitcodestatic/docs"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/api/handlers"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/service"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/storage"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
httpSwagger "github.com/swaggo/http-swagger"
|
||||
)
|
||||
|
||||
// Router 路由配置
|
||||
type Router struct {
|
||||
repoHandler *handlers.RepoHandler
|
||||
statsHandler *handlers.StatsHandler
|
||||
taskHandler *handlers.TaskHandler
|
||||
webDir string
|
||||
webEnabled bool
|
||||
}
|
||||
|
||||
// NewRouter 创建路由
|
||||
func NewRouter(repoService *service.RepoService, statsService *service.StatsService) *Router {
|
||||
func NewRouter(repoService *service.RepoService, statsService *service.StatsService, store storage.Store, webDir string, webEnabled bool) *Router {
|
||||
return &Router{
|
||||
repoHandler: handlers.NewRepoHandler(repoService),
|
||||
statsHandler: handlers.NewStatsHandler(statsService),
|
||||
statsHandler: handlers.NewStatsHandler(statsService, store),
|
||||
taskHandler: handlers.NewTaskHandler(store),
|
||||
webDir: webDir,
|
||||
webEnabled: webEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +49,19 @@ func (rt *Router) Setup() http.Handler {
|
||||
w.Write([]byte(`{"status":"healthy"}`))
|
||||
})
|
||||
|
||||
// Swagger documentation
|
||||
r.Get("/swagger/*", httpSwagger.Handler(
|
||||
httpSwagger.URL("/swagger/doc.json"),
|
||||
))
|
||||
|
||||
// Web UI static files
|
||||
if rt.webEnabled {
|
||||
fileServer := http.FileServer(http.Dir(rt.webDir))
|
||||
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
fileServer.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// API routes
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
// 仓库管理
|
||||
@@ -47,6 +69,7 @@ func (rt *Router) Setup() http.Handler {
|
||||
r.Post("/batch", rt.repoHandler.AddBatch)
|
||||
r.Get("/", rt.repoHandler.List)
|
||||
r.Get("/{id}", rt.repoHandler.Get)
|
||||
r.Get("/{id}/branches", rt.repoHandler.GetBranches)
|
||||
r.Post("/{id}/switch-branch", rt.repoHandler.SwitchBranch)
|
||||
r.Post("/{id}/update", rt.repoHandler.Update)
|
||||
r.Post("/{id}/reset", rt.repoHandler.Reset)
|
||||
@@ -58,6 +81,15 @@ func (rt *Router) Setup() http.Handler {
|
||||
r.Post("/calculate", rt.statsHandler.Calculate)
|
||||
r.Get("/result", rt.statsHandler.QueryResult)
|
||||
r.Get("/commit-count", rt.statsHandler.CountCommits)
|
||||
r.Get("/caches", rt.statsHandler.ListCaches)
|
||||
r.Delete("/caches/clear", rt.statsHandler.ClearAllCaches)
|
||||
})
|
||||
|
||||
// 任务
|
||||
r.Route("/tasks", func(r chi.Router) {
|
||||
r.Get("/", rt.taskHandler.List)
|
||||
r.Delete("/clear", rt.taskHandler.ClearAllTasks)
|
||||
r.Delete("/clear-completed", rt.taskHandler.ClearCompletedTasks)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user