make a PUT to /api/obj also insert bare addresses and prepare metadata; bunch of tidying up
/api/obj returns [entry_addr, entity_addr] ADDED_ATTR is a const impl TryInto<Address > for InAddress
This commit is contained in:
parent
41293c4e4d
commit
5ad64a6591
8 changed files with 155 additions and 77 deletions
|
@ -12,6 +12,7 @@ pub const HIER_TYPE_VAL: &str = "HIER";
|
|||
pub const HIER_HAS_ATTR: &str = "HAS";
|
||||
|
||||
pub const LABEL_ATTR: &str = "LBL";
|
||||
pub const ADDED_ATTR: &str = "ADDED";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref TYPE_INVARIANT: InvariantEntry = InvariantEntry {
|
||||
|
|
|
@ -3,14 +3,14 @@ use crate::database::inner::models;
|
|||
use crate::util::hash::{b58_decode, hash, Hash, Hashable};
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InEntry {
|
||||
pub entity: Option<InAddress>,
|
||||
pub attribute: String,
|
||||
pub value: EntryValue,
|
||||
#[serde(untagged)]
|
||||
pub enum InEntry {
|
||||
Invariant(InvariantEntry),
|
||||
Address { entity: InAddress },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -20,6 +20,25 @@ pub enum InAddress {
|
|||
Components { t: String, c: String },
|
||||
}
|
||||
|
||||
impl TryInto<Address> for InAddress {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> Result<Address, Self::Error> {
|
||||
Ok(match self {
|
||||
InAddress::Address(address) => address.parse()?,
|
||||
InAddress::Components { t, c } => {
|
||||
// I absolutely cannot handle serde magic right now
|
||||
// TODO: make this automatically derive from `Address` definition
|
||||
match t.as_str() {
|
||||
"Attribute" => Address::Attribute(c),
|
||||
"Url" => Address::Url(c),
|
||||
_ => c.parse()?,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Entry {
|
||||
pub entity: Address,
|
||||
|
@ -125,35 +144,6 @@ impl TryFrom<&InvariantEntry> for Entry {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InEntry> for Entry {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(in_entry: InEntry) -> Result<Self, Self::Error> {
|
||||
match in_entry.entity {
|
||||
Some(address) => Ok(Entry {
|
||||
entity: match address {
|
||||
InAddress::Address(address) => address.parse()?,
|
||||
InAddress::Components { t, c } => {
|
||||
// I absolutely cannot handle serde magic right now
|
||||
// TODO: make this automatically derive from `Address` definition
|
||||
match t.as_str() {
|
||||
"Attribute" => Address::Attribute(c),
|
||||
"Url" => Address::Url(c),
|
||||
_ => c.parse()?,
|
||||
}
|
||||
}
|
||||
},
|
||||
attribute: in_entry.attribute,
|
||||
value: in_entry.value,
|
||||
}),
|
||||
None => Ok(Entry::try_from(&InvariantEntry {
|
||||
attribute: in_entry.attribute,
|
||||
value: in_entry.value,
|
||||
})?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InvariantEntry {
|
||||
pub fn entity(&self) -> Result<Address> {
|
||||
let mut entity = Cursor::new(vec![0u8; 0]);
|
||||
|
@ -267,4 +257,19 @@ mod tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_in_address() -> Result<()> {
|
||||
let address = Address::Url("https://upendproject.net".into());
|
||||
let in_address = InAddress::Address(address.to_string());
|
||||
assert_eq!(address, in_address.try_into()?);
|
||||
|
||||
let in_address = InAddress::Components {
|
||||
t: "Url".into(),
|
||||
c: "https://upendproject.net".into(),
|
||||
};
|
||||
assert_eq!(address, in_address.try_into()?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ use std::{fs, iter};
|
|||
|
||||
use crate::addressing::Address;
|
||||
use crate::database::constants::{
|
||||
HIER_HAS_ATTR, IS_OF_TYPE_ATTR, LABEL_ATTR, TYPE_ADDR, TYPE_BASE_ATTR, TYPE_HAS_ATTR,
|
||||
ADDED_ATTR, HIER_HAS_ATTR, IS_OF_TYPE_ATTR, LABEL_ATTR, TYPE_ADDR, TYPE_BASE_ATTR,
|
||||
TYPE_HAS_ATTR,
|
||||
};
|
||||
use crate::database::entry::{Entry, EntryValue, InvariantEntry};
|
||||
use crate::database::hierarchies::{
|
||||
|
@ -414,7 +415,7 @@ fn insert_file_with_metadata(
|
|||
|
||||
let added_entry = Entry {
|
||||
entity: blob_address.clone(),
|
||||
attribute: "ADDED".to_string(),
|
||||
attribute: ADDED_ATTR.to_string(),
|
||||
value: EntryValue::Number(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::addressing::{Address, Addressable};
|
||||
use crate::database::entry::{Entry, EntryValue, InEntry};
|
||||
use crate::database::constants::{ADDED_ATTR, LABEL_ATTR};
|
||||
use crate::database::entry::{Entry, EntryValue, InEntry, InvariantEntry};
|
||||
use crate::database::hierarchies::{list_roots, resolve_path, UHierPath};
|
||||
use crate::database::lang::Query;
|
||||
use crate::database::UpEndDatabase;
|
||||
|
@ -18,11 +19,12 @@ use futures_util::TryStreamExt;
|
|||
use log::{debug, info, trace};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{collections::HashMap, io};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
|
@ -239,20 +241,83 @@ pub async fn put_object(
|
|||
state: web::Data<State>,
|
||||
payload: Either<web::Json<InEntry>, Multipart>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let (result_address, entry) = match payload {
|
||||
let (entry_address, entity_address) = match payload {
|
||||
Either::A(in_entry) => {
|
||||
let in_entry = in_entry.into_inner();
|
||||
let entry = Entry::try_from(in_entry).map_err(ErrorInternalServerError)?;
|
||||
|
||||
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
|
||||
let in_entry = in_entry.into_inner();
|
||||
|
||||
debug!("PUTting {in_entry:?}");
|
||||
|
||||
match in_entry {
|
||||
InEntry::Invariant(in_entry) => {
|
||||
println!("INVARIANT: {in_entry:?}");
|
||||
let invariant = Entry::try_from(&InvariantEntry {
|
||||
attribute: in_entry.attribute,
|
||||
value: in_entry.value,
|
||||
})
|
||||
.map_err(ErrorInternalServerError)?;
|
||||
|
||||
Ok((
|
||||
Some(
|
||||
connection
|
||||
.insert_entry(entry.clone())
|
||||
.insert_entry(invariant.clone())
|
||||
.map_err(ErrorInternalServerError)?,
|
||||
Some(entry),
|
||||
),
|
||||
invariant.entity,
|
||||
))
|
||||
}
|
||||
InEntry::Address { entity: in_address } => {
|
||||
println!("IN_ADDRESS: {in_address:?}");
|
||||
let address = in_address.try_into().map_err(ErrorBadRequest)?;
|
||||
|
||||
let entries_to_add = match &address {
|
||||
Address::Hash(_) => vec![],
|
||||
Address::Uuid(uuid) => vec![Entry {
|
||||
entity: address.clone(),
|
||||
attribute: LABEL_ATTR.to_string(),
|
||||
value: EntryValue::String(uuid.to_string()),
|
||||
}],
|
||||
Address::Attribute(attribute) => vec![Entry {
|
||||
entity: address.clone(),
|
||||
attribute: LABEL_ATTR.to_string(),
|
||||
value: EntryValue::String(format!("ATTRIBUTE: {attribute}")),
|
||||
}],
|
||||
// todo: set off an opengraph query
|
||||
Address::Url(url) => vec![Entry {
|
||||
entity: address.clone(),
|
||||
attribute: LABEL_ATTR.to_string(),
|
||||
value: EntryValue::String(url.to_string()),
|
||||
}],
|
||||
};
|
||||
|
||||
connection
|
||||
.transaction::<_, anyhow::Error, _>(|| {
|
||||
if connection.retrieve_object(&address)?.is_empty() {
|
||||
connection.insert_entry(Entry {
|
||||
entity: address.clone(),
|
||||
attribute: ADDED_ATTR.to_string(),
|
||||
value: EntryValue::Number(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
as f64,
|
||||
),
|
||||
})?;
|
||||
}
|
||||
|
||||
for entry in entries_to_add {
|
||||
connection.insert_entry(entry)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(ErrorInternalServerError)?;
|
||||
|
||||
Ok((None, address))
|
||||
}
|
||||
}
|
||||
}
|
||||
Either::B(mut multipart) => {
|
||||
if let Some(mut field) = multipart.try_next().await? {
|
||||
let content_disposition = field
|
||||
|
@ -300,22 +365,15 @@ pub async fn put_object(
|
|||
let _ = upend_insert_val!(&connection, address, "LBL", filename);
|
||||
}
|
||||
|
||||
Ok((address, None))
|
||||
Ok((None, address))
|
||||
} else {
|
||||
Err(ErrorBadRequest(anyhow!("wat")))
|
||||
Err(anyhow!("Multipart contains no fields."))
|
||||
}
|
||||
}
|
||||
}?;
|
||||
}
|
||||
.map_err(ErrorInternalServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(
|
||||
[(
|
||||
b58_encode(result_address.encode().map_err(ErrorInternalServerError)?),
|
||||
entry,
|
||||
)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashMap<String, Option<Entry>>>(),
|
||||
))
|
||||
Ok(HttpResponse::Ok().json([entry_address, Some(entity_address)]))
|
||||
}
|
||||
|
||||
#[put("/api/obj/{address}/{attribute}")]
|
||||
|
|
|
@ -29,6 +29,9 @@ export interface ListingResult {
|
|||
[key: string]: IEntry;
|
||||
}
|
||||
|
||||
// entry address, entity address address
|
||||
export type PutResult = [string | undefined, string];
|
||||
|
||||
// export type OrderedListing = [Address, IEntry][];
|
||||
|
||||
export interface IFile {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { useNavigate } from "svelte-navigator";
|
||||
import type { ListingResult } from "upend/types";
|
||||
import type { PutResult } from "upend/types";
|
||||
import Icon from "./utils/Icon.svelte";
|
||||
import IconButton from "./utils/IconButton.svelte";
|
||||
const navigate = useNavigate();
|
||||
|
@ -43,11 +43,11 @@
|
|||
throw Error(await response.text());
|
||||
}
|
||||
|
||||
return (await response.json()) as ListingResult;
|
||||
return (await response.json()) as PutResult;
|
||||
})
|
||||
);
|
||||
|
||||
const addresses = responses.map((lr) => Object.keys(lr)).flat();
|
||||
const addresses = responses.map(([_, entry]) => entry);
|
||||
navigate(`/browse/${addresses.join(",")}`);
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { UpEntry } from "upend";
|
||||
import type { ListingResult } from "upend/types";
|
||||
import type { PutResult } from "upend/types";
|
||||
import { query as queryFn, queryOnce } from "../lib/entity";
|
||||
|
||||
export function baseSearch(query: string) {
|
||||
|
@ -25,24 +25,34 @@ export async function getObjects(
|
|||
}
|
||||
|
||||
export async function createLabelled(label: string) {
|
||||
const response = await fetch(`api/obj`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
entity: !label.match("^[\\w]+://[\\w]")
|
||||
? undefined
|
||||
: { t: "Url", c: label },
|
||||
let body: unknown;
|
||||
if (label.match("^[\\w]+://[\\w]")) {
|
||||
body = {
|
||||
entity: {
|
||||
t: "Url",
|
||||
c: label,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
body = {
|
||||
attribute: "LBL",
|
||||
value: {
|
||||
t: "String",
|
||||
c: label,
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch(`api/obj`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to create object: ${await response.text()}`);
|
||||
}
|
||||
const result = (await response.json()) as ListingResult;
|
||||
const address = Object.values(result)[0].entity;
|
||||
return address;
|
||||
|
||||
const [_, entry] = (await response.json()) as PutResult;
|
||||
return entry;
|
||||
}
|
||||
|
|
|
@ -4008,8 +4008,8 @@ __metadata:
|
|||
|
||||
"upend@file:../tools/upend_js::locator=svelte-app%40workspace%3A.":
|
||||
version: 0.0.1
|
||||
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=a864e4&locator=svelte-app%40workspace%3A."
|
||||
checksum: 7fdd22d2098068c8c5467d0cada87425491223a5bc7d79a9cdfb03497365a969ea055d71817f18723b783869cb44cd3a4928ae8f765f37611f65eb900ead74e5
|
||||
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=7db405&locator=svelte-app%40workspace%3A."
|
||||
checksum: 2cf640ebac60141df688cafb62b3a14bc94edc797b701037cac00fc7216fe064067857ae13f6baf1d339afd34ad0666d8dc2fdb2e84aa6ac1e0968c0bd938eea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
Loading…
Reference in a new issue