基本能力编写完成
This commit is contained in:
177
internal/api/handlers/repo.go
Normal file
177
internal/api/handlers/repo.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/logger"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/service"
|
||||
)
|
||||
|
||||
// RepoHandler 仓库API处理器
|
||||
type RepoHandler struct {
|
||||
repoService *service.RepoService
|
||||
}
|
||||
|
||||
// NewRepoHandler 创建仓库处理器
|
||||
func NewRepoHandler(repoService *service.RepoService) *RepoHandler {
|
||||
return &RepoHandler{
|
||||
repoService: repoService,
|
||||
}
|
||||
}
|
||||
|
||||
// AddBatch 批量添加仓库
|
||||
func (h *RepoHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.AddReposRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, 40001, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.URLs) == 0 {
|
||||
respondError(w, http.StatusBadRequest, 40001, "urls cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.repoService.AddRepos(r.Context(), &req)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to add repositories")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to add repositories")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", resp)
|
||||
}
|
||||
|
||||
// List 获取仓库列表
|
||||
func (h *RepoHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
status := r.URL.Query().Get("status")
|
||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
repos, total, err := h.repoService.ListRepos(r.Context(), status, page, pageSize)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to list repositories")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to list repositories")
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"repositories": repos,
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", data)
|
||||
}
|
||||
|
||||
// Get 获取仓库详情
|
||||
func (h *RepoHandler) Get(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
|
||||
}
|
||||
|
||||
repo, err := h.repoService.GetRepo(r.Context(), id)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, 40400, "repository not found")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", repo)
|
||||
}
|
||||
|
||||
// SwitchBranch 切换分支
|
||||
func (h *RepoHandler) SwitchBranch(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
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, 40001, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Branch == "" {
|
||||
respondError(w, http.StatusBadRequest, 40001, "branch cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
task, err := h.repoService.SwitchBranch(r.Context(), id, req.Branch)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Int64("repo_id", id).Msg("failed to switch branch")
|
||||
respondError(w, http.StatusInternalServerError, 50000, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "branch switch task submitted", task)
|
||||
}
|
||||
|
||||
// Update 更新仓库
|
||||
func (h *RepoHandler) Update(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
|
||||
}
|
||||
|
||||
task, err := h.repoService.UpdateRepo(r.Context(), id)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Int64("repo_id", id).Msg("failed to update repository")
|
||||
respondError(w, http.StatusInternalServerError, 50000, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "update task submitted", task)
|
||||
}
|
||||
|
||||
// Reset 重置仓库
|
||||
func (h *RepoHandler) Reset(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
|
||||
}
|
||||
|
||||
task, err := h.repoService.ResetRepo(r.Context(), id)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Int64("repo_id", id).Msg("failed to reset repository")
|
||||
respondError(w, http.StatusInternalServerError, 50000, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "reset task submitted", task)
|
||||
}
|
||||
|
||||
// Delete 删除仓库
|
||||
func (h *RepoHandler) Delete(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
|
||||
}
|
||||
|
||||
if err := h.repoService.DeleteRepo(r.Context(), id); err != nil {
|
||||
logger.Logger.Error().Err(err).Int64("repo_id", id).Msg("failed to delete repository")
|
||||
respondError(w, http.StatusInternalServerError, 50000, "failed to delete repository")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "repository deleted successfully", nil)
|
||||
}
|
||||
32
internal/api/handlers/response.go
Normal file
32
internal/api/handlers/response.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Response 统一响应结构
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// respondJSON 返回JSON响应
|
||||
func respondJSON(w http.ResponseWriter, statusCode, code int, message string, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
resp := Response{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// respondError 返回错误响应
|
||||
func respondError(w http.ResponseWriter, statusCode, code int, message string) {
|
||||
respondJSON(w, statusCode, code, message, nil)
|
||||
}
|
||||
130
internal/api/handlers/stats.go
Normal file
130
internal/api/handlers/stats.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/logger"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/service"
|
||||
)
|
||||
|
||||
// StatsHandler 统计API处理器
|
||||
type StatsHandler struct {
|
||||
statsService *service.StatsService
|
||||
}
|
||||
|
||||
// NewStatsHandler 创建统计处理器
|
||||
func NewStatsHandler(statsService *service.StatsService) *StatsHandler {
|
||||
return &StatsHandler{
|
||||
statsService: statsService,
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate 触发统计计算
|
||||
func (h *StatsHandler) Calculate(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.CalculateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, 40001, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.RepoID == 0 {
|
||||
respondError(w, http.StatusBadRequest, 40001, "repo_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Branch == "" {
|
||||
respondError(w, http.StatusBadRequest, 40001, "branch is required")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验约束参数
|
||||
if err := service.ValidateStatsConstraint(req.Constraint); err != nil {
|
||||
respondError(w, http.StatusBadRequest, 40001, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
task, err := h.statsService.Calculate(r.Context(), &req)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to submit stats task")
|
||||
respondError(w, http.StatusInternalServerError, 50000, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "statistics task submitted", task)
|
||||
}
|
||||
|
||||
// QueryResult 查询统计结果
|
||||
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")
|
||||
constraintType := r.URL.Query().Get("constraint_type")
|
||||
from := r.URL.Query().Get("from")
|
||||
to := r.URL.Query().Get("to")
|
||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
|
||||
if repoID == 0 {
|
||||
respondError(w, http.StatusBadRequest, 40001, "repo_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
if branch == "" {
|
||||
respondError(w, http.StatusBadRequest, 40001, "branch is required")
|
||||
return
|
||||
}
|
||||
|
||||
req := &service.QueryResultRequest{
|
||||
RepoID: repoID,
|
||||
Branch: branch,
|
||||
ConstraintType: constraintType,
|
||||
From: from,
|
||||
To: to,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
result, err := h.statsService.QueryResult(r.Context(), req)
|
||||
if err != nil {
|
||||
if err.Error() == "statistics not found, please submit calculation task first" {
|
||||
respondError(w, http.StatusNotFound, 40400, err.Error())
|
||||
return
|
||||
}
|
||||
logger.Logger.Error().Err(err).Msg("failed to query stats result")
|
||||
respondError(w, http.StatusInternalServerError, 50000, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", result)
|
||||
}
|
||||
|
||||
// CountCommits 统计提交次数
|
||||
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")
|
||||
from := r.URL.Query().Get("from")
|
||||
|
||||
if repoID == 0 {
|
||||
respondError(w, http.StatusBadRequest, 40001, "repo_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
if branch == "" {
|
||||
respondError(w, http.StatusBadRequest, 40001, "branch is required")
|
||||
return
|
||||
}
|
||||
|
||||
req := &service.CountCommitsRequest{
|
||||
RepoID: repoID,
|
||||
Branch: branch,
|
||||
From: from,
|
||||
}
|
||||
|
||||
result, err := h.statsService.CountCommits(r.Context(), req)
|
||||
if err != nil {
|
||||
logger.Logger.Error().Err(err).Msg("failed to count commits")
|
||||
respondError(w, http.StatusInternalServerError, 50000, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, 0, "success", result)
|
||||
}
|
||||
65
internal/api/router.go
Normal file
65
internal/api/router.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/api/handlers"
|
||||
"github.com/gitcodestatic/gitcodestatic/internal/service"
|
||||
)
|
||||
|
||||
// Router 路由配置
|
||||
type Router struct {
|
||||
repoHandler *handlers.RepoHandler
|
||||
statsHandler *handlers.StatsHandler
|
||||
}
|
||||
|
||||
// NewRouter 创建路由
|
||||
func NewRouter(repoService *service.RepoService, statsService *service.StatsService) *Router {
|
||||
return &Router{
|
||||
repoHandler: handlers.NewRepoHandler(repoService),
|
||||
statsHandler: handlers.NewStatsHandler(statsService),
|
||||
}
|
||||
}
|
||||
|
||||
// Setup 设置路由
|
||||
func (rt *Router) Setup() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// 中间件
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// Health check
|
||||
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status":"healthy"}`))
|
||||
})
|
||||
|
||||
// API routes
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
// 仓库管理
|
||||
r.Route("/repos", func(r chi.Router) {
|
||||
r.Post("/batch", rt.repoHandler.AddBatch)
|
||||
r.Get("/", rt.repoHandler.List)
|
||||
r.Get("/{id}", rt.repoHandler.Get)
|
||||
r.Post("/{id}/switch-branch", rt.repoHandler.SwitchBranch)
|
||||
r.Post("/{id}/update", rt.repoHandler.Update)
|
||||
r.Post("/{id}/reset", rt.repoHandler.Reset)
|
||||
r.Delete("/{id}", rt.repoHandler.Delete)
|
||||
})
|
||||
|
||||
// 统计
|
||||
r.Route("/stats", func(r chi.Router) {
|
||||
r.Post("/calculate", rt.statsHandler.Calculate)
|
||||
r.Get("/result", rt.statsHandler.QueryResult)
|
||||
r.Get("/commit-count", rt.statsHandler.CountCommits)
|
||||
})
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user