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
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
116 lines
3.3 KiB
Go
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})
|
|
}
|