feat: 添加用户注册登录功能,实现数据和配置的用户隔离

- 新增 users 和 user_settings 表,repositories/analyses/review_notes 添加 user_id 列
- 实现基于邮箱+密码的注册登录,密码使用 bcrypt 哈希
- 使用 gin-contrib/sessions cookie-based session 管理
- 所有仓库、分析记录、review notes 按用户隔离
- 用户设置(LLM 配置、review 参数、缓存配置)独立存储
- 新增登录/注册页面,导航栏显示用户邮箱和退出按钮
- 前端 fetch 请求统一添加 credentials: 'same-origin'
- 支持 SESSION_SECRET 环境变量配置会话密钥
This commit is contained in:
2026-06-20 21:57:46 +08:00
parent 953c83d9aa
commit 9c843b79ba
28 changed files with 837 additions and 176 deletions
+13 -7
View File
@@ -20,11 +20,17 @@ func NewGenerateHandler(db *sql.DB) *GenerateHandler {
// Generate handles POST /api/repos/:id/generate — SSE streaming PR description generation.
func (h *GenerateHandler) Generate(c *gin.Context) {
user := GetCurrentUser(c)
if user == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthenticated"})
return
}
id := c.Param("id")
// Get repo info
// Get repo info (scoped to user)
var localPath string
err := h.db.QueryRow(`SELECT local_path FROM repositories WHERE id = ?`, id).Scan(&localPath)
err := h.db.QueryRow(`SELECT local_path FROM repositories WHERE id = ? AND user_id = ?`, id, user.ID).Scan(&localPath)
if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "repository not found"})
return
@@ -69,21 +75,21 @@ func (h *GenerateHandler) Generate(c *gin.Context) {
// Update last_used
h.db.Exec(`UPDATE repositories SET last_used = datetime('now') WHERE id = ?`, id)
// Generate PR description
pr, err := services.GeneratePR(h.db, localPath, req.Base, req.Head, sendEvent)
// Generate PR description (pass user ID for per-user LLM config)
pr, err := services.GeneratePR(h.db, localPath, req.Base, req.Head, user.ID, sendEvent)
if err != nil {
sendEvent("error", map[string]interface{}{"message": err.Error()})
return
}
// Save analysis to DB
// Save analysis to DB with user_id
resultJSON, err := json.Marshal(pr)
if err != nil {
sendEvent("error", map[string]interface{}{"message": "marshal result: " + err.Error()})
return
}
if _, err := h.db.Exec(`INSERT INTO analyses (repo_id, type, base_ref, head_ref, result) VALUES (?, 'pr_description', ?, ?, ?)`,
id, req.Base, req.Head, string(resultJSON)); err != nil {
if _, err := h.db.Exec(`INSERT INTO analyses (user_id, repo_id, type, base_ref, head_ref, result) VALUES (?, ?, 'pr_description', ?, ?, ?)`,
user.ID, id, req.Base, req.Head, string(resultJSON)); err != nil {
sendEvent("error", map[string]interface{}{"message": "save analysis: " + err.Error()})
return
}