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 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 {
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
106
src/routes.rs
106
src/routes.rs
|
@ -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}")]
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue