Initial commit: Gitea Project Management System

Features:
- Complete project management system with Epic/Story/Task hierarchy
- Vue.js 3 + Element Plus frontend with kanban board
- Go backend with Gin framework and GORM
- OAuth2 integration with Gitea
- Docker containerization with MySQL
- RESTful API for project, task, and user management
- JWT authentication and authorization
- Responsive web interface with dashboard
This commit is contained in:
huxunan
2025-09-22 14:53:53 +08:00
commit 885fad6c64
33 changed files with 4128 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
package handlers
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"giteapm/internal/middleware"
"giteapm/internal/models"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/oauth2"
)
type GiteaUser struct {
ID int `json:"id"`
Login string `json:"login"`
FullName string `json:"full_name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
}
func (h *Handlers) GiteaLogin(c *gin.Context) {
config := &oauth2.Config{
ClientID: h.Cfg.Gitea.ClientID,
ClientSecret: h.Cfg.Gitea.ClientSecret,
RedirectURL: h.Cfg.Gitea.RedirectURL,
Scopes: []string{"read:user"},
Endpoint: oauth2.Endpoint{
AuthURL: h.Cfg.Gitea.BaseURL + "/login/oauth/authorize",
TokenURL: h.Cfg.Gitea.BaseURL + "/login/oauth/access_token",
},
}
state := "random-state-string"
url := config.AuthCodeURL(state, oauth2.AccessTypeOffline)
c.JSON(http.StatusOK, SuccessResponse(gin.H{
"auth_url": url,
}))
}
func (h *Handlers) GiteaCallback(c *gin.Context) {
// 记录所有查询参数用于调试
fmt.Printf("OAuth回调收到的所有参数: %v\n", c.Request.URL.Query())
code := c.Query("code")
state := c.Query("state")
errorParam := c.Query("error")
fmt.Printf("OAuth回调 - code: %s, state: %s, error: %s\n", code, state, errorParam)
if errorParam != "" {
c.JSON(http.StatusBadRequest, ErrorResponse(400, fmt.Sprintf("OAuth授权失败: %s", errorParam)))
return
}
if code == "" {
c.JSON(http.StatusBadRequest, ErrorResponse(400, "缺少授权码"))
return
}
config := &oauth2.Config{
ClientID: h.Cfg.Gitea.ClientID,
ClientSecret: h.Cfg.Gitea.ClientSecret,
RedirectURL: h.Cfg.Gitea.RedirectURL,
Scopes: []string{"read:user"},
Endpoint: oauth2.Endpoint{
AuthURL: h.Cfg.Gitea.BaseURL + "/login/oauth/authorize",
TokenURL: h.Cfg.Gitea.BaseURL + "/login/oauth/access_token",
},
}
token, err := config.Exchange(c.Request.Context(), code)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse(400, "授权码交换失败"))
return
}
client := config.Client(c.Request.Context(), token)
resp, err := client.Get(h.Cfg.Gitea.BaseURL + "/api/v1/user")
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "获取用户信息失败"))
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "读取用户信息失败"))
return
}
var giteaUser GiteaUser
if err := json.Unmarshal(body, &giteaUser); err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "解析用户信息失败"))
return
}
user, err := models.GetUserByGiteaID(uint(giteaUser.ID))
if err != nil {
user = &models.User{
GiteaUserID: func() *uint { id := uint(giteaUser.ID); return &id }(),
Username: giteaUser.Login,
Email: giteaUser.Email,
FullName: giteaUser.FullName,
AvatarURL: giteaUser.AvatarURL,
Role: "developer",
}
if err := models.CreateUser(user); err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "创建用户失败"))
return
}
} else {
user.Username = giteaUser.Login
user.Email = giteaUser.Email
user.FullName = giteaUser.FullName
user.AvatarURL = giteaUser.AvatarURL
if err := models.UpdateUser(user); err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "更新用户失败"))
return
}
}
jwtToken, err := h.generateJWT(user)
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "生成JWT失败"))
return
}
// 重定向到前端页面并通过URL参数传递token
redirectURL := fmt.Sprintf("/?token=%s", jwtToken)
c.Redirect(http.StatusFound, redirectURL)
}
func (h *Handlers) Logout(c *gin.Context) {
c.JSON(http.StatusOK, SuccessResponse(gin.H{
"message": "登出成功",
}))
}
func (h *Handlers) generateJWT(user *models.User) (string, error) {
claims := &middleware.Claims{
UserID: user.ID,
Username: user.Username,
Role: user.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(h.Cfg.JWT.ExpireHour) * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: fmt.Sprintf("%d", user.ID),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(h.Cfg.JWT.Secret))
}