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 HIER_HAS_ATTR: &str = "HAS";
pub const LABEL_ATTR: &str = "LBL"; pub const LABEL_ATTR: &str = "LBL";
pub const ADDED_ATTR: &str = "ADDED";
lazy_static! { lazy_static! {
pub static ref TYPE_INVARIANT: InvariantEntry = InvariantEntry { 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 crate::util::hash::{b58_decode, hash, Hash, Hashable};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::{TryFrom, TryInto};
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InEntry { #[serde(untagged)]
pub entity: Option<InAddress>, pub enum InEntry {
pub attribute: String, Invariant(InvariantEntry),
pub value: EntryValue, Address { entity: InAddress },
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -20,6 +20,25 @@ pub enum InAddress {
Components { t: String, c: String }, 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)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entry { pub struct Entry {
pub entity: Address, 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 { impl InvariantEntry {
pub fn entity(&self) -> Result<Address> { pub fn entity(&self) -> Result<Address> {
let mut entity = Cursor::new(vec![0u8; 0]); let mut entity = Cursor::new(vec![0u8; 0]);
@ -267,4 +257,19 @@ mod tests {
Ok(()) 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::addressing::Address;
use crate::database::constants::{ 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::entry::{Entry, EntryValue, InvariantEntry};
use crate::database::hierarchies::{ use crate::database::hierarchies::{
@ -414,7 +415,7 @@ fn insert_file_with_metadata(
let added_entry = Entry { let added_entry = Entry {
entity: blob_address.clone(), entity: blob_address.clone(),
attribute: "ADDED".to_string(), attribute: ADDED_ATTR.to_string(),
value: EntryValue::Number( value: EntryValue::Number(
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)

View File

@ -1,5 +1,6 @@
use crate::addressing::{Address, Addressable}; 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::hierarchies::{list_roots, resolve_path, UHierPath};
use crate::database::lang::Query; use crate::database::lang::Query;
use crate::database::UpEndDatabase; use crate::database::UpEndDatabase;
@ -18,11 +19,12 @@ use futures_util::TryStreamExt;
use log::{debug, info, trace}; use log::{debug, info, trace};
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use std::convert::TryFrom; use std::convert::{TryFrom, TryInto};
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};
use std::{collections::HashMap, io}; use std::{collections::HashMap, io};
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
@ -239,19 +241,82 @@ pub async fn put_object(
state: web::Data<State>, state: web::Data<State>,
payload: Either<web::Json<InEntry>, Multipart>, payload: Either<web::Json<InEntry>, Multipart>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let (result_address, entry) = match payload { let (entry_address, entity_address) = match payload {
Either::A(in_entry) => { 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 connection = state.upend.connection().map_err(ErrorInternalServerError)?;
let in_entry = in_entry.into_inner();
Ok(( debug!("PUTting {in_entry:?}");
connection
.insert_entry(entry.clone()) match in_entry {
.map_err(ErrorInternalServerError)?, InEntry::Invariant(in_entry) => {
Some(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) => { Either::B(mut multipart) => {
if let Some(mut field) = multipart.try_next().await? { 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); let _ = upend_insert_val!(&connection, address, "LBL", filename);
} }
Ok((address, None)) Ok((None, address))
} else { } else {
Err(ErrorBadRequest(anyhow!("wat"))) Err(anyhow!("Multipart contains no fields."))
} }
} }
}?; }
.map_err(ErrorInternalServerError)?;
Ok(HttpResponse::Ok().json( Ok(HttpResponse::Ok().json([entry_address, Some(entity_address)]))
[(
b58_encode(result_address.encode().map_err(ErrorInternalServerError)?),
entry,
)]
.iter()
.cloned()
.collect::<HashMap<String, Option<Entry>>>(),
))
} }
#[put("/api/obj/{address}/{attribute}")] #[put("/api/obj/{address}/{attribute}")]

View File

@ -29,6 +29,9 @@ export interface ListingResult {
[key: string]: IEntry; [key: string]: IEntry;
} }
// entry address, entity address address
export type PutResult = [string | undefined, string];
// export type OrderedListing = [Address, IEntry][]; // export type OrderedListing = [Address, IEntry][];
export interface IFile { export interface IFile {

View File

@ -10,7 +10,7 @@
<script lang="ts"> <script lang="ts">
import { useNavigate } from "svelte-navigator"; import { useNavigate } from "svelte-navigator";
import type { ListingResult } from "upend/types"; import type { PutResult } from "upend/types";
import Icon from "./utils/Icon.svelte"; import Icon from "./utils/Icon.svelte";
import IconButton from "./utils/IconButton.svelte"; import IconButton from "./utils/IconButton.svelte";
const navigate = useNavigate(); const navigate = useNavigate();
@ -43,11 +43,11 @@
throw Error(await response.text()); 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(",")}`); navigate(`/browse/${addresses.join(",")}`);
} catch (error) { } catch (error) {
alert(error); alert(error);

View File

@ -1,5 +1,5 @@
import type { UpEntry } from "upend"; 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"; import { query as queryFn, queryOnce } from "../lib/entity";
export function baseSearch(query: string) { export function baseSearch(query: string) {
@ -25,24 +25,34 @@ export async function getObjects(
} }
export async function createLabelled(label: string) { export async function createLabelled(label: string) {
const response = await fetch(`api/obj`, { let body: unknown;
method: "PUT", if (label.match("^[\\w]+://[\\w]")) {
headers: { "Content-Type": "application/json" }, body = {
body: JSON.stringify({ entity: {
entity: !label.match("^[\\w]+://[\\w]") t: "Url",
? undefined c: label,
: { t: "Url", c: label }, },
};
} else {
body = {
attribute: "LBL", attribute: "LBL",
value: { value: {
t: "String", t: "String",
c: label, c: label,
}, },
}), };
}
const response = await fetch(`api/obj`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to create object: ${await response.text()}`); throw new Error(`Failed to create object: ${await response.text()}`);
} }
const result = (await response.json()) as ListingResult;
const address = Object.values(result)[0].entity; const [_, entry] = (await response.json()) as PutResult;
return address; return entry;
} }

View File

@ -4008,8 +4008,8 @@ __metadata:
"upend@file:../tools/upend_js::locator=svelte-app%40workspace%3A.": "upend@file:../tools/upend_js::locator=svelte-app%40workspace%3A.":
version: 0.0.1 version: 0.0.1
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=a864e4&locator=svelte-app%40workspace%3A." resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=7db405&locator=svelte-app%40workspace%3A."
checksum: 7fdd22d2098068c8c5467d0cada87425491223a5bc7d79a9cdfb03497365a969ea055d71817f18723b783869cb44cd3a4928ae8f765f37611f65eb900ead74e5 checksum: 2cf640ebac60141df688cafb62b3a14bc94edc797b701037cac00fc7216fe064067857ae13f6baf1d339afd34ad0666d8dc2fdb2e84aa6ac1e0968c0bd938eea
languageName: node languageName: node
linkType: hard linkType: hard