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
feat/vaults
Tomáš Mládek 2022-02-09 20:34:52 +01:00
parent 41293c4e4d
commit 5ad64a6591
No known key found for this signature in database
GPG Key ID: ED21612889E75EC5
8 changed files with 155 additions and 77 deletions

View File

@ -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 {

View File

@ -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(())
}
}

View File

@ -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)

View File

@ -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,19 +241,82 @@ 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();
Ok((
connection
.insert_entry(entry.clone())
.map_err(ErrorInternalServerError)?,
Some(entry),
))
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(invariant.clone())
.map_err(ErrorInternalServerError)?,
),
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? {
@ -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}")]

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}

View File

@ -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