v2.0.0: claims model - signed MMUR2 votes in a truth DB, merged view in a rebuildable cache DB; POST /api/claims, claims and merge routes, unsigned writes answer 410
Build and Push Docker Image / build (push) Successful in 5m1s
Build and Push Docker Image / build (push) Successful in 5m1s
This commit is contained in:
+8
-200
@@ -38,6 +38,9 @@ type MetadataSearchRequest struct {
|
||||
Property string `json:"property,omitempty"`
|
||||
PropertyValue string `json:"propertyValue,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
// As is a viewer uid: the search sees that signer's own claims overlaid
|
||||
// (and their records as candidates). Also accepted as ?as=.
|
||||
As string `json:"as,omitempty"`
|
||||
|
||||
// Filters is the structured form: AND across entries, OR within an
|
||||
// entry's Values. Narrowed in SQL through the field index alongside the
|
||||
@@ -222,10 +225,10 @@ func (s *Server) handleGetMetadataByHashId(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// Same resolution as the PUT below: callers hold a CID, not the root.
|
||||
// Callers may hold a CID, not the root.
|
||||
hashID = s.storage.ResolveRoot(hashID)
|
||||
|
||||
metadata, err := s.storage.GetMetadataFlat(hashID)
|
||||
metadata, err := s.storage.GetMetadataFlatAs(hashID, r.URL.Query().Get("as"))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -244,87 +247,6 @@ func (s *Server) handleGetMetadataByHashId(w http.ResponseWriter, r *http.Reques
|
||||
writeJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// handleUpdateMetadataByHashId handles PUT /api/metadata/{hashId}
|
||||
func (s *Server) handleUpdateMetadataByHashId(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
hashID := vars["hashId"]
|
||||
|
||||
if hashID == "" {
|
||||
writeError(w, http.StatusBadRequest, "hash ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
if !s.storage.IsConnected() {
|
||||
writeError(w, http.StatusServiceUnavailable, "storage not connected")
|
||||
return
|
||||
}
|
||||
|
||||
// The {hashId} in the URL is whatever CID the caller knows the file by —
|
||||
// the gateway's auto-store keys this PUT by the hash it just computed
|
||||
// (dispatch.rs → store_metadata_only), which for a usenet→IPFS fetch is
|
||||
// the dag-pb root, not the nzb-release cid the record was minted under.
|
||||
// Without resolving, the write lands on file:<thatCid>/* and forks a
|
||||
// second root for a file that already has one: a full duplicate record,
|
||||
// cross-linked and separately indexed, which every downstream ingest then
|
||||
// sees as two files. Unknown CIDs pass through unchanged, so a genuinely
|
||||
// new gateway record still mints its root at the hash.
|
||||
hashID = s.storage.ResolveRoot(hashID)
|
||||
|
||||
var metadata map[string]string
|
||||
if err := json.NewDecoder(r.Body).Decode(&metadata); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
|
||||
// Exclude internal processing status field
|
||||
delete(metadata, "processingStatus")
|
||||
|
||||
if err := s.storage.SetMetadataFlat(hashID, metadata); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"hashId": hashID,
|
||||
"message": "Metadata updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// handleDeleteMetadataByHashId handles DELETE /api/metadata/{hashId}
|
||||
func (s *Server) handleDeleteMetadataByHashId(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
hashID := vars["hashId"]
|
||||
|
||||
if hashID == "" {
|
||||
writeError(w, http.StatusBadRequest, "hash ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
if !s.storage.IsConnected() {
|
||||
writeError(w, http.StatusServiceUnavailable, "storage not connected")
|
||||
return
|
||||
}
|
||||
|
||||
// meta-gateway retracts a dropped hit by whichever CID it holds
|
||||
// (meta_core.rs `delete`, which documents this resolution as a
|
||||
// precondition). Without it, deleting by a non-root CID is a silent
|
||||
// no-op and the retracted record lingers.
|
||||
hashID = s.storage.ResolveRoot(hashID)
|
||||
|
||||
deletedCount, err := s.storage.DeleteMetadata(hashID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"hashId": hashID,
|
||||
"deletedKeys": deletedCount,
|
||||
})
|
||||
}
|
||||
|
||||
// handleMetadataGetProperty handles GET /api/metadata/{hashId}/property?property=X
|
||||
func (s *Server) handleMetadataGetProperty(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
@@ -364,46 +286,6 @@ func (s *Server) handleMetadataGetProperty(w http.ResponseWriter, r *http.Reques
|
||||
})
|
||||
}
|
||||
|
||||
// handleMetadataUpdateProperty handles PUT /api/metadata/{hashId}/property
|
||||
func (s *Server) handleMetadataUpdateProperty(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
hashID := vars["hashId"]
|
||||
|
||||
if hashID == "" {
|
||||
writeError(w, http.StatusBadRequest, "hash ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
if !s.storage.IsConnected() {
|
||||
writeError(w, http.StatusServiceUnavailable, "storage not connected")
|
||||
return
|
||||
}
|
||||
|
||||
var req PropertyUpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Property == "" {
|
||||
writeError(w, http.StatusBadRequest, "property is required")
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.storage.SetProperty(hashID, req.Property, req.Value); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"hashId": hashID,
|
||||
"property": req.Property,
|
||||
"value": req.Value,
|
||||
"message": "Property updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// handleSearchMetadata handles POST /api/metadata/search
|
||||
func (s *Server) handleSearchMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.storage.IsConnected() {
|
||||
@@ -420,6 +302,9 @@ func (s *Server) handleSearchMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 100
|
||||
}
|
||||
if req.As == "" {
|
||||
req.As = r.URL.Query().Get("as")
|
||||
}
|
||||
|
||||
// If hashId is provided, do exact match
|
||||
if req.HashID != "" {
|
||||
@@ -457,83 +342,6 @@ func (s *Server) handleSearchMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
s.serveIndexedSearch(w, req)
|
||||
}
|
||||
|
||||
// handleBatchUpdate handles POST /api/metadata/batch
|
||||
func (s *Server) handleBatchUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.storage.IsConnected() {
|
||||
writeError(w, http.StatusServiceUnavailable, "storage not connected")
|
||||
return
|
||||
}
|
||||
|
||||
var req MetadataBatchUpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Updates) == 0 {
|
||||
writeError(w, http.StatusBadRequest, "updates array is required")
|
||||
return
|
||||
}
|
||||
|
||||
var results []MetadataBatchResult
|
||||
successCount := 0
|
||||
errorCount := 0
|
||||
|
||||
for _, update := range req.Updates {
|
||||
if update.HashID == "" {
|
||||
results = append(results, MetadataBatchResult{
|
||||
HashID: "unknown",
|
||||
Status: "error",
|
||||
Error: "Missing hash ID",
|
||||
})
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
|
||||
var updateErr error
|
||||
|
||||
// Update complete metadata
|
||||
if update.Metadata != nil {
|
||||
// Exclude internal processing status
|
||||
delete(update.Metadata, "processingStatus")
|
||||
updateErr = s.storage.SetMetadataFlat(update.HashID, update.Metadata)
|
||||
}
|
||||
|
||||
// Update individual properties
|
||||
if updateErr == nil && update.Properties != nil {
|
||||
for property, value := range update.Properties {
|
||||
if err := s.storage.SetProperty(update.HashID, property, value); err != nil {
|
||||
updateErr = err
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if updateErr != nil {
|
||||
results = append(results, MetadataBatchResult{
|
||||
HashID: update.HashID,
|
||||
Status: "error",
|
||||
Error: updateErr.Error(),
|
||||
})
|
||||
errorCount++
|
||||
} else {
|
||||
results = append(results, MetadataBatchResult{
|
||||
HashID: update.HashID,
|
||||
Status: "ok",
|
||||
})
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, MetadataBatchResponse{
|
||||
Status: "completed",
|
||||
Total: len(results),
|
||||
Success: successCount,
|
||||
Errors: errorCount,
|
||||
Results: results,
|
||||
})
|
||||
}
|
||||
|
||||
// handleClearMetadata handles POST /api/metadata/clear
|
||||
func (s *Server) handleClearMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.storage.IsConnected() {
|
||||
|
||||
Reference in New Issue
Block a user