spin-backend/internal/httpapi/handlers_calendar.go
theorose49 b196f85bb3
All checks were successful
build-and-push / build (push) Successful in 32s
fix(project): 프로젝트 삭제 캐스케이드 + 생성 검증 공백 보강 (심화 QA 발견)
- handleDeleteProject: 작업·댓글·작업자·담당자·계약·첨부·분할입금·메일메모/메시지/상태까지
  함께 삭제(고아 레코드 방지)
- 서버측 필수값 검증 추가(프론트뿐 아니라 API도):
  · 작업 제목, 담당자 이름, 구성원 이메일 필수 → 400
  · 작업자 기여도 0~100 범위 → 400
  · 일정 제목·시작일 필수, 종료<시작 거부 → 400
  · 휴가 종류·시작일 필수, 종료<시작 거부 → 400

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:54:23 +09:00

84 lines
2.5 KiB
Go

package httpapi
import (
"encoding/json"
"net/http"
"strings"
"spin/internal/models"
"github.com/go-chi/chi/v5"
"gorm.io/datatypes"
)
// 개인 캘린더 — 각자 본인 이벤트만 보고 관리(분류: 프로젝트/기타/개인 + 색).
// handleListEvents returns ALL members' events (회사 공유 캘린더) so the UI can
// filter by member checkboxes. Editing/deleting stays owner-only.
func (s *Server) handleListEvents(w http.ResponseWriter, r *http.Request) {
var out []models.CalendarEvent
s.db.Order("start asc").Find(&out)
writeJSON(w, http.StatusOK, out)
}
func (s *Server) handleCreateEvent(w http.ResponseWriter, r *http.Request) {
var e models.CalendarEvent
if err := decodeJSON(r, &e); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
if strings.TrimSpace(e.Title) == "" || strings.TrimSpace(e.Start) == "" {
writeError(w, http.StatusBadRequest, "제목과 시작일이 필요합니다")
return
}
if e.End != "" && e.End < e.Start {
writeError(w, http.StatusBadRequest, "종료일이 시작일보다 빠릅니다")
return
}
e.ID = ""
e.OwnerEmail = s.email(r)
s.db.Create(&e)
writeJSON(w, http.StatusCreated, e)
}
func (s *Server) handlePatchEvent(w http.ResponseWriter, r *http.Request) {
var e models.CalendarEvent
if err := s.db.First(&e, "id = ?", chi.URLParam(r, "eId")).Error; err != nil {
writeError(w, http.StatusNotFound, "일정을 찾을 수 없습니다")
return
}
if !s.owns(r, e.OwnerEmail) {
writeError(w, http.StatusForbidden, "본인 일정만 수정할 수 있습니다")
return
}
var patch map[string]interface{}
if err := decodeJSON(r, &patch); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
delete(patch, "id")
delete(patch, "ownerEmail")
if v, ok := patch["participants"]; ok {
if b, err := json.Marshal(v); err == nil {
patch["participants"] = datatypes.JSON(b)
}
}
s.db.Model(&e).Updates(snakeKeys(patch))
s.db.First(&e, "id = ?", e.ID)
writeJSON(w, http.StatusOK, e)
}
func (s *Server) handleDeleteEvent(w http.ResponseWriter, r *http.Request) {
var e models.CalendarEvent
if err := s.db.First(&e, "id = ?", chi.URLParam(r, "eId")).Error; err != nil {
writeError(w, http.StatusNotFound, "일정을 찾을 수 없습니다")
return
}
if !s.owns(r, e.OwnerEmail) {
writeError(w, http.StatusForbidden, "본인 일정만 삭제할 수 있습니다")
return
}
s.db.Delete(&models.CalendarEvent{}, "id = ?", e.ID)
writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
}