Files
dela 0335d572de
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
First Commit
2026-10-09 08:38:16 +08:00

116 lines
3.3 KiB
Go

package server
import (
"errors"
"net/http"
"strings"
"github.com/Autumn-27/artex/db"
)
const maxTaskAssetRequestBytes = 512 << 10
// 작업 자산 API 가 사용자에게 돌려주는 오류 응답 문구. 필드명(scope·asset_ids)은
// 요청 본문 키라 원문 그대로 두고, 사람이 읽는 메시지만 한국어로 둔다.
const (
errTaskAssetRequestTooLarge = "요청 본문이 너무 큽니다"
errTaskAssetScopeConflict = "scope 와 asset_ids 는 동시에 제출할 수 없습니다"
)
func writeTaskAssetError(w http.ResponseWriter, err error) {
switch {
case errors.Is(err, db.ErrTaskAssetInvalid):
writeErr(w, http.StatusBadRequest, err.Error())
case errors.Is(err, db.ErrTaskAssetTaskNotFound), errors.Is(err, db.ErrTaskAssetAssetNotFound):
writeErr(w, http.StatusNotFound, err.Error())
default:
writeErr(w, http.StatusInternalServerError, err.Error())
}
}
func (s *Server) attachTaskAssets(w http.ResponseWriter, r *http.Request) {
task, ok := s.m.Task(r.PathValue("id"))
if !ok {
writeErr(w, http.StatusNotFound, "task not found")
return
}
r.Body = http.MaxBytesReader(w, r.Body, maxTaskAssetRequestBytes)
var request struct {
AssetIDs []int64 `json:"asset_ids"`
SourceSummary string `json:"source_summary"`
Scope companyScopeInputs `json:"scope"`
}
if err := decode(r, &request); err != nil {
var tooLarge *http.MaxBytesError
if errors.As(err, &tooLarge) {
writeErr(w, http.StatusRequestEntityTooLarge, errTaskAssetRequestTooLarge)
} else {
writeErr(w, http.StatusBadRequest, err.Error())
}
return
}
request.SourceSummary = strings.TrimSpace(request.SourceSummary)
taskID, _ := parseTaskID(task.ID)
if request.Scope != nil && len(request.AssetIDs) > 0 {
writeErr(w, http.StatusBadRequest, errTaskAssetScopeConflict)
return
}
if request.Scope != nil {
mutation, err := s.m.Assets().RegisterTaskAssetScopes(taskID, request.Scope)
if err != nil {
writeTaskAssetError(w, err)
return
}
task.Notify()
writeJSON(w, http.StatusOK, mutation)
return
}
mutation, err := s.m.Assets().AttachAssetsToTask(taskID, request.AssetIDs, request.SourceSummary)
if err != nil {
writeTaskAssetError(w, err)
return
}
task.Notify()
writeJSON(w, http.StatusOK, mutation)
}
func (s *Server) detachTaskAsset(w http.ResponseWriter, r *http.Request) {
task, ok := s.m.Task(r.PathValue("id"))
if !ok {
writeErr(w, http.StatusNotFound, "task not found")
return
}
assetID, ok := pathInt(r, "assetID")
if !ok {
writeErr(w, http.StatusBadRequest, "bad asset id")
return
}
taskID, _ := parseTaskID(task.ID)
detached, err := s.m.Assets().DetachAssetFromTask(taskID, assetID)
if err != nil {
writeTaskAssetError(w, err)
return
}
if !detached {
writeErr(w, http.StatusNotFound, "asset is not associated with this task")
return
}
task.Notify()
writeJSON(w, http.StatusOK, map[string]any{"detached": assetID})
}
func (s *Server) taskIntentAssets(w http.ResponseWriter, r *http.Request) {
task, ok := s.m.Task(r.PathValue("id"))
if !ok {
writeErr(w, http.StatusNotFound, "task not found")
return
}
taskID, _ := parseTaskID(task.ID)
assets, err := s.m.Assets().IntentAssets(taskID)
if err != nil {
writeTaskAssetError(w, err)
return
}
writeJSON(w, http.StatusOK, map[string]any{"assets": assets})
}