inou/api/api_entries.go

299 lines
7.4 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"`
}
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.EntryDeleteByCategory(ctx, dossierID, 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 != "" {
entryID := req.ID
if req.DeleteChildren {
if err := lib.EntryDeleteTree(ctx, req.Dossier, entryID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} else {
if err := lib.EntryDelete(entryID); 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.EntryModify(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,
ProductID: req.Product,
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.EntryAddBatch(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: e.ProductID,
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,
}
}
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
}
// RBAC: Check read access to dossier
if !requireDossierAccess(w, ctx, dossierID) {
return
}
lang := r.URL.Query().Get("lang")
if lang == "" {
lang = "en"
}
// Get date filter (default to today)
dateStr := r.URL.Query().Get("date")
var fromTs, toTs int64
if dateStr == "today" || dateStr == "" {
now := time.Now()
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
fromTs = start.Unix()
toTs = start.Add(24 * time.Hour).Unix()
}
categoryStr := r.URL.Query().Get("category")
typ := r.URL.Query().Get("type")
catInt := -1 // -1 means no category filter
if categoryStr != "" {
if c, ok := lib.CategoryFromString[categoryStr]; ok {
catInt = c
}
}
entries, err := lib.EntryQuery(dossierID, catInt, typ)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Filter by date if specified
var filtered []*lib.Entry
for _, e := range entries {
if fromTs > 0 && (e.Timestamp < fromTs || e.Timestamp >= toTs) {
continue
}
filtered = append(filtered, e)
}
var responses []EntryResponse
for _, e := range filtered {
responses = append(responses, entryToResponse(e, lang))
}
if responses == nil {
responses = []EntryResponse{}
}
json.NewEncoder(w).Encode(responses)
}