inou/api/api_entries.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)
}