299 lines
7.4 KiB
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)
|
|
}
|