Merge remote-tracking branch 'origin/main' into LPM-115

This commit is contained in:
Robin Olsen
2026-03-23 20:18:47 +01:00
105 changed files with 6303 additions and 3758 deletions

View File

@@ -2,8 +2,10 @@ package handler
import (
"context"
"fmt"
"html"
"net/http"
"net/url"
"strings"
"github.com/LazyBachelor/LazyPM/internal/models"
@@ -18,7 +20,7 @@ const commentsKey = "comments"
type IssueForm struct {
Title string `form:"title" validate:"required,max=255"`
Description string `form:"description" validate:"max=2000"`
Status models.Status `form:"status" validate:"required,oneof=open in_progress blocked ready_to_sprint closed"`
Status models.Status `form:"status" validate:"required,oneof=open in_progress blocked closed"`
IssueType models.IssueType `form:"issue_type" validate:"required,oneof=task bug feature chore"`
Priority int `form:"priority" validate:"gte=0,lte=4"`
}
@@ -26,10 +28,11 @@ type IssueForm struct {
type UpdateIssueForm struct {
Title *string `form:"title" validate:"omitempty,max=255"`
Description *string `form:"description" validate:"omitempty,max=2000"`
Status *models.Status `form:"status" validate:"omitempty,oneof=open in_progress blocked ready_to_sprint closed"`
Status *models.Status `form:"status" validate:"omitempty,oneof=open in_progress blocked closed"`
CloseReason *string `form:"close_reason" validate:"omitempty,max=2000"`
IssueType *models.IssueType `form:"issue_type" validate:"omitempty,oneof=task bug feature chore"`
Priority *int `form:"priority" validate:"omitempty,gte=0,lte=4"`
Assignee *string `form:"assignee" validate:"omitempty,max=100"`
}
func CreateIssue(w http.ResponseWriter, r *http.Request) {
@@ -63,7 +66,7 @@ func CreateIssue(w http.ResponseWriter, r *http.Request) {
from := r.URL.Query().Get("from")
referer := r.Header.Get("Referer")
boardView := from == "board" || strings.Contains(referer, "board=true")
if boardView {
w.Header().Set("HX-Redirect", "/?board=true")
} else {
@@ -123,20 +126,8 @@ func GetIssue(w http.ResponseWriter, r *http.Request) {
comments := r.Context().Value(commentsKey).([]*models.Comment)
hx := HTMX(r)
from := r.URL.Query().Get("from")
detailProps := routes.IssueDetailProps{
Issue: issue,
Comments: comments,
From: from,
}
if !hx.IsHxRequest() && strings.Contains(r.Header.Get("Accept"), "text/html") {
routes.IssueDetail(detailProps).Render(r.Context(), w)
return
}
if hx.IsHxRequest() {
routes.IssueDetailContent(detailProps).Render(r.Context(), w)
if strings.Contains(r.Header.Get("Accept"), "text/html") && !hx.IsHxRequest() {
routes.IssueDetailPage(issue, comments).Render(r.Context(), w)
return
}
@@ -171,6 +162,24 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) {
return
}
ctx := r.Context()
addToSprint := r.FormValue("add_to_sprint")
removeFromSprint := r.FormValue("remove_from_sprint")
if addToSprint != "" {
sprintNum := 0
fmt.Sscanf(addToSprint, "%d", &sprintNum)
if sprintNum > 0 {
app.Issues.AddIssueToSprint(ctx, issue.ID, sprintNum)
}
} else if removeFromSprint != "" {
sprintNum := 0
fmt.Sscanf(removeFromSprint, "%d", &sprintNum)
if sprintNum > 0 {
app.Issues.RemoveIssueFromSprint(ctx, issue.ID, sprintNum)
}
}
issue, err = app.Issues.GetIssue(r.Context(), issue.ID)
if err != nil {
http.Error(w, "Failed to retrieve updated issue", http.StatusInternalServerError)
@@ -178,13 +187,20 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) {
}
if hx.IsHxRequest() {
// Check if 'from' parameter says board view
from := r.URL.Query().Get("from")
referer := r.Header.Get("Referer")
boardView := from == "board" || strings.Contains(referer, "board=true")
if boardView {
w.Header().Set("HX-Redirect", "/?board=true")
redirectURL := "/?board=true"
if strings.Contains(referer, "sprint=") {
if parsedURL, err := url.Parse(referer); err == nil {
if sprintParam := parsedURL.Query().Get("sprint"); sprintParam != "" {
redirectURL = redirectURL + "&sprint=" + sprintParam
}
}
}
w.Header().Set("HX-Redirect", redirectURL)
} else {
w.Header().Set("HX-Redirect", "/?selected-issue="+issue.ID)
}
@@ -197,7 +213,12 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) {
func UpdateAssignee(w http.ResponseWriter, r *http.Request) {
issue := r.Context().Value(issueKey).(*models.Issue)
assignee := r.FormValue("assignee")
assignMe := r.FormValue("assign_me")
assignee := ""
if assignMe != "" {
assignee = "Me"
}
if err := App(r).Issues.UpdateIssue(r.Context(), issue.ID, map[string]any{"assignee": assignee}, ""); err != nil {
http.Error(w, "Failed to update assignee", http.StatusInternalServerError)
@@ -214,7 +235,7 @@ func UpdateAssignee(w http.ResponseWriter, r *http.Request) {
// Check if we're in board view
referer := r.Header.Get("Referer")
boardView := strings.Contains(referer, "board=true")
if boardView {
w.Header().Set("HX-Redirect", "/?board=true&selected-issue="+issue.ID)
} else {
@@ -387,7 +408,7 @@ func DeleteIssue(w http.ResponseWriter, r *http.Request) {
from := r.URL.Query().Get("from")
referer := r.Header.Get("Referer")
boardView := from == "board" || strings.Contains(referer, "board=true")
if boardView {
w.Header().Set("HX-Redirect", "/?board=true")
} else {
@@ -472,5 +493,8 @@ func (f *UpdateIssueForm) toChanges() map[string]any {
if f.Priority != nil {
changes["priority"] = *f.Priority
}
if f.Assignee != nil {
changes["assignee"] = *f.Assignee
}
return changes
}