Files
ForgeBucket/internal/api/handlers/issues.go
T
2026-05-07 02:06:54 +02:00

168 lines
4.2 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"xorm.io/xorm"
"github.com/forgeo/forgebucket/internal/api/middleware"
"github.com/forgeo/forgebucket/internal/models"
)
type IssueHandler struct {
db *xorm.Engine
}
func NewIssueHandler(db *xorm.Engine) *IssueHandler {
return &IssueHandler{db: db}
}
type issueResponse struct {
models.Issue
AuthorName string `json:"authorName"`
}
func (h *IssueHandler) enrichIssue(issue *models.Issue) issueResponse {
var author models.User
h.db.ID(issue.AuthorID).Get(&author)
return issueResponse{Issue: *issue, AuthorName: author.Username}
}
func (h *IssueHandler) List(w http.ResponseWriter, r *http.Request) {
repoID, ok := h.repoID(w, r)
if !ok {
return
}
state := r.URL.Query().Get("state")
if state == "" {
state = "open"
}
var issues []models.Issue
if err := h.db.Where("repo_id = ? AND state = ?", repoID, state).
OrderBy("id DESC").Find(&issues); err != nil {
jsonError(w, "could not list issues", http.StatusInternalServerError)
return
}
result := make([]issueResponse, len(issues))
for i := range issues {
result[i] = h.enrichIssue(&issues[i])
}
if result == nil {
result = []issueResponse{}
}
jsonOK(w, result)
}
func (h *IssueHandler) Create(w http.ResponseWriter, r *http.Request) {
repoID, ok := h.repoID(w, r)
if !ok {
return
}
authorID, _ := middleware.UserIDFromContext(r.Context())
var body struct {
Title string `json:"title"`
Body string `json:"body"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Title == "" {
jsonError(w, "title is required", http.StatusBadRequest)
return
}
// Auto-increment issue number per repo
count, _ := h.db.Where("repo_id = ?", repoID).Count(&models.Issue{})
number := int(count) + 1
issue := &models.Issue{
RepoID: repoID,
AuthorID: authorID,
Number: number,
Title: body.Title,
Body: body.Body,
State: models.IssueStateOpen,
}
if _, err := h.db.Insert(issue); err != nil {
jsonError(w, "could not create issue", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(h.enrichIssue(issue))
}
func (h *IssueHandler) Get(w http.ResponseWriter, r *http.Request) {
issue, ok := h.lookupIssue(w, r)
if !ok {
return
}
jsonOK(w, h.enrichIssue(issue))
}
func (h *IssueHandler) Close(w http.ResponseWriter, r *http.Request) {
issue, ok := h.lookupIssue(w, r)
if !ok {
return
}
issue.State = models.IssueStateClosed
if _, err := h.db.ID(issue.ID).Cols("state").Update(issue); err != nil {
jsonError(w, "could not close issue", http.StatusInternalServerError)
return
}
jsonOK(w, h.enrichIssue(issue))
}
func (h *IssueHandler) Reopen(w http.ResponseWriter, r *http.Request) {
issue, ok := h.lookupIssue(w, r)
if !ok {
return
}
issue.State = models.IssueStateOpen
if _, err := h.db.ID(issue.ID).Cols("state").Update(issue); err != nil {
jsonError(w, "could not reopen issue", http.StatusInternalServerError)
return
}
jsonOK(w, h.enrichIssue(issue))
}
func (h *IssueHandler) repoID(w http.ResponseWriter, r *http.Request) (int64, bool) {
ownerName := chi.URLParam(r, "owner")
repoName := chi.URLParam(r, "repo")
var owner models.User
if found, _ := h.db.Where("username = ?", ownerName).Get(&owner); !found {
jsonError(w, "repository not found", http.StatusNotFound)
return 0, false
}
var repo models.Repository
if found, _ := h.db.Where("owner_id = ? AND name = ?", owner.ID, repoName).Get(&repo); !found {
jsonError(w, "repository not found", http.StatusNotFound)
return 0, false
}
return repo.ID, true
}
func (h *IssueHandler) lookupIssue(w http.ResponseWriter, r *http.Request) (*models.Issue, bool) {
repoID, ok := h.repoID(w, r)
if !ok {
return nil, false
}
issueNum, err := strconv.Atoi(chi.URLParam(r, "issueNum"))
if err != nil {
jsonError(w, "invalid issue number", http.StatusBadRequest)
return nil, false
}
var issue models.Issue
if found, _ := h.db.Where("repo_id = ? AND number = ?", repoID, issueNum).Get(&issue); !found {
jsonError(w, "issue not found", http.StatusNotFound)
return nil, false
}
return &issue, true
}