278 lines
7.0 KiB
Go
278 lines
7.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"inou/lib"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type EntryRequest struct {
|
|
ID string `json:"id"`
|
|
Dossier string `json:"dossier"`
|
|
Parent string `json:"parent"`
|
|
Product string `json:"product"`
|
|
Category string `json:"category"`
|
|
Type string `json:"type"`
|
|
Value string `json:"value"`
|
|
Ordinal int `json:"ordinal"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
TimestampEnd int64 `json:"timestamp_end"`
|
|
Status int `json:"status"`
|
|
Tags string `json:"tags"`
|
|
Data string `json:"data"`
|
|
Delete bool `json:"delete"`
|
|
DeleteChildren bool `json:"delete_children"`
|
|
DeleteCategory bool `json:"delete_category"`
|
|
}
|
|
|
|
type EntryResponse struct {
|
|
ID string `json:"id"`
|
|
Dossier string `json:"dossier"`
|
|
Parent string `json:"parent,omitempty"`
|
|
Product string `json:"product,omitempty"`
|
|
Category string `json:"category"`
|
|
Type string `json:"type"`
|
|
Value string `json:"value,omitempty"`
|
|
Ordinal int `json:"ordinal"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
TimestampEnd int64 `json:"timestamp_end,omitempty"`
|
|
Status int `json:"status"`
|
|
Tags string `json:"tags,omitempty"`
|
|
Data string `json:"data,omitempty"`
|
|
SearchKey string `json:"search_key,omitempty"`
|
|
SearchKey2 string `json:"search_key2,omitempty"`
|
|
}
|
|
|
|
func handleEntries(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
if r.Method == "GET" {
|
|
handleEntriesGet(w, r)
|
|
return
|
|
}
|
|
|
|
if r.Method != "POST" {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
// For POST: require localhost OR valid auth with write permission
|
|
ctx := getAccessContextOrSystem(w, r)
|
|
if ctx == nil {
|
|
return
|
|
}
|
|
|
|
lang := r.URL.Query().Get("lang")
|
|
if lang == "" {
|
|
lang = "en"
|
|
}
|
|
|
|
// Try array first, then single object
|
|
var requests []EntryRequest
|
|
decoder := json.NewDecoder(r.Body)
|
|
if err := decoder.Decode(&requests); err != nil {
|
|
// Reset and try single object
|
|
r.Body.Close()
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Handle delete_category first (before any inserts)
|
|
for _, req := range requests {
|
|
if req.DeleteCategory && req.Dossier != "" && req.Category != "" {
|
|
dossierID := req.Dossier
|
|
catInt, ok := lib.CategoryFromString[req.Category]
|
|
if !ok {
|
|
http.Error(w, "unknown category: "+req.Category, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := lib.EntryDelete(ctx.AccessorID, dossierID, &lib.Filter{Category: catInt}); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect entries to insert/update
|
|
var toInsert []*lib.Entry
|
|
var responses []EntryResponse
|
|
now := time.Now().Unix()
|
|
|
|
for i, req := range requests {
|
|
// Skip if this was just a delete_category request
|
|
if req.DeleteCategory && req.Type == "" {
|
|
continue
|
|
}
|
|
|
|
// Handle single delete
|
|
if req.Delete && req.ID != "" {
|
|
if err := lib.EntryDelete(ctx.AccessorID, req.Dossier, &lib.Filter{EntryID: req.ID}); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
responses = append(responses, EntryResponse{ID: req.ID})
|
|
continue
|
|
}
|
|
|
|
// Handle update
|
|
if req.ID != "" {
|
|
entryID := req.ID
|
|
existing, err := lib.EntryGet(ctx, entryID)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
// Update only provided fields
|
|
if req.Category != "" {
|
|
if catInt, ok := lib.CategoryFromString[req.Category]; ok {
|
|
existing.Category = catInt
|
|
}
|
|
}
|
|
if req.Type != "" {
|
|
existing.Type = req.Type
|
|
}
|
|
if req.Value != "" {
|
|
existing.Value = req.Value
|
|
}
|
|
if req.Ordinal != 0 {
|
|
existing.Ordinal = req.Ordinal
|
|
}
|
|
if req.Timestamp != 0 {
|
|
existing.Timestamp = req.Timestamp
|
|
}
|
|
if req.TimestampEnd != 0 {
|
|
existing.TimestampEnd = req.TimestampEnd
|
|
}
|
|
if req.Status != 0 {
|
|
existing.Status = req.Status
|
|
}
|
|
if req.Tags != "" {
|
|
existing.Tags = req.Tags
|
|
}
|
|
if req.Data != "" {
|
|
existing.Data = req.Data
|
|
}
|
|
if err := lib.EntryWrite(ctx.AccessorID, existing); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
responses = append(responses, entryToResponse(existing, lang))
|
|
continue
|
|
}
|
|
|
|
// Insert new entry
|
|
if req.Dossier == "" {
|
|
http.Error(w, "dossier required for insert", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
catInt := lib.CategoryFromString[req.Category] // defaults to 0 (CategoryImaging) if not found
|
|
e := &lib.Entry{
|
|
DossierID: req.Dossier,
|
|
ParentID: req.Parent,
|
|
Category: catInt,
|
|
Type: req.Type,
|
|
Value: req.Value,
|
|
Ordinal: req.Ordinal,
|
|
Timestamp: req.Timestamp,
|
|
TimestampEnd: req.TimestampEnd,
|
|
Status: req.Status,
|
|
Tags: req.Tags,
|
|
Data: req.Data,
|
|
}
|
|
if e.Timestamp == 0 {
|
|
e.Timestamp = now
|
|
}
|
|
if e.Ordinal == 0 {
|
|
e.Ordinal = i
|
|
}
|
|
toInsert = append(toInsert, e)
|
|
}
|
|
|
|
// Batch insert
|
|
if len(toInsert) > 0 {
|
|
if err := lib.EntryWrite(ctx.AccessorID, toInsert...); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
for _, e := range toInsert {
|
|
responses = append(responses, entryToResponse(e, lang))
|
|
}
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(responses)
|
|
}
|
|
|
|
func entryToResponse(e *lib.Entry, lang string) EntryResponse {
|
|
return EntryResponse{
|
|
ID: e.EntryID,
|
|
Dossier: e.DossierID,
|
|
Parent: e.ParentID,
|
|
Product: "",
|
|
Category: lib.CategoryTranslate(e.Category, lang),
|
|
Type: e.Type,
|
|
Value: e.Value,
|
|
Ordinal: e.Ordinal,
|
|
Timestamp: e.Timestamp,
|
|
TimestampEnd: e.TimestampEnd,
|
|
Status: e.Status,
|
|
Tags: e.Tags,
|
|
Data: e.Data,
|
|
SearchKey: e.SearchKey,
|
|
SearchKey2: e.SearchKey2,
|
|
}
|
|
}
|
|
|
|
func handleEntriesGet(w http.ResponseWriter, r *http.Request) {
|
|
ctx := getAccessContextOrFail(w, r)
|
|
if ctx == nil {
|
|
return
|
|
}
|
|
|
|
dossierID := r.URL.Query().Get("dossier")
|
|
if dossierID == "" {
|
|
http.Error(w, "dossier parameter required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if !requireDossierAccess(w, ctx, dossierID) {
|
|
return
|
|
}
|
|
|
|
lang := r.URL.Query().Get("lang")
|
|
if lang == "" {
|
|
lang = "en"
|
|
}
|
|
|
|
f := &lib.Filter{Category: -1}
|
|
if categoryStr := r.URL.Query().Get("category"); categoryStr != "" {
|
|
if c, ok := lib.CategoryFromString[categoryStr]; ok {
|
|
f.Category = c
|
|
}
|
|
}
|
|
if typ := r.URL.Query().Get("type"); typ != "" {
|
|
f.Type = typ
|
|
}
|
|
if sk := r.URL.Query().Get("search_key"); sk != "" {
|
|
f.SearchKey = sk
|
|
}
|
|
if parent := r.URL.Query().Get("parent"); parent != "" {
|
|
f.ParentID = parent
|
|
}
|
|
entries, err := lib.EntryRead(ctx.AccessorID, dossierID, f)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var responses []EntryResponse
|
|
for _, e := range entries {
|
|
responses = append(responses, entryToResponse(e, lang))
|
|
}
|
|
if responses == nil {
|
|
responses = []EntryResponse{}
|
|
}
|
|
json.NewEncoder(w).Encode(responses)
|
|
}
|