Add reordering
Some checks reported errors
continuous-integration/drone/push Build was killed

This commit is contained in:
Aode (Lion) 2022-01-30 14:52:49 -06:00
parent e4e6119f0d
commit dd8d95346e
5 changed files with 219 additions and 42 deletions

51
Cargo.lock generated
View file

@ -119,15 +119,14 @@ dependencies = [
[[package]]
name = "actix-tls"
version = "3.0.1"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b161450ff646361005a300716e85856780ddc2fde569fd81335cc3c15b2e0933"
checksum = "a31ab31563b611fa822480b4255e8750cf0af9ce1b8b7bde298afe8447ef9333"
dependencies = [
"actix-codec",
"actix-rt",
"actix-service",
"actix-utils",
"derive_more",
"futures-core",
"http",
"log",
@ -789,9 +788,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689"
checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e"
dependencies = [
"bytes",
"fnv",
@ -1015,9 +1014,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.113"
version = "0.2.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74"
[[package]]
name = "local-channel"
@ -1039,9 +1038,9 @@ checksum = "902eb695eb0591864543cbfbf6d742510642a605a61fc5e97fe6ceb5a30ac4fb"
[[package]]
name = "lock_api"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
dependencies = [
"scopeguard",
]
@ -1262,9 +1261,9 @@ dependencies = [
[[package]]
name = "num_threads"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71a1eb3a36534514077c1e079ada2fb170ef30c47d203aa6916138cf882ecd52"
checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
dependencies = [
"libc",
]
@ -1376,7 +1375,7 @@ dependencies = [
[[package]]
name = "pict-rs-aggregator"
version = "0.1.23"
version = "0.1.24"
dependencies = [
"actix-rt",
"actix-web",
@ -1741,18 +1740,18 @@ checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
[[package]]
name = "serde"
version = "1.0.135"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.135"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
@ -1842,9 +1841,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f82496b90c36d70af5fcd482edaa2e0bd16fade569de1330405fecbbdac736b"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
@ -1957,9 +1956,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d54b9298e05179c335de2b9645d061255bcd5155f843b3e328d2cfe0a5b413"
checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d"
dependencies = [
"itoa 1.0.1",
"libc",
@ -1983,9 +1982,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.15.0"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
dependencies = [
"bytes",
"libc",
@ -2277,9 +2276,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77be66445c4eeebb934a7340f227bfe7b338173d3f8c00a60a5a58005c9faecf"
checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22"
dependencies = [
"ansi_term",
"lazy_static",
@ -2445,9 +2444,9 @@ checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]]
name = "which"
version = "4.2.2"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9"
checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2"
dependencies = [
"either",
"lazy_static",

View file

@ -1,7 +1,7 @@
[package]
name = "pict-rs-aggregator"
description = "A simple image aggregation service for pict-rs"
version = "0.1.23"
version = "0.1.24"
authors = ["asonix <asonix@asonix.dog>"]
license = "AGPL-3.0"
readme = "README.md"
@ -32,7 +32,11 @@ tracing-error = "0.2"
tracing-futures = "0.2"
tracing-log = "0.1"
tracing-opentelemetry = "0.16"
tracing-subscriber = { version = "0.3", features = ["ansi", "env-filter", "fmt"] }
tracing-subscriber = { version = "0.3", features = [
"ansi",
"env-filter",
"fmt",
] }
url = { version = "2.2", features = ["serde"] }
uuid = { version = "0.8.1", features = ["serde", "v4"] }

View file

@ -90,6 +90,24 @@ impl Config {
}
}
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
pub enum Direction {
#[serde(rename = "up")]
Up,
#[serde(rename = "down")]
Down,
}
impl std::fmt::Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Up => write!(f, "up"),
Self::Down => write!(f, "down"),
}
}
}
#[derive(Clone)]
pub struct State {
upstream: Url,
@ -161,6 +179,19 @@ impl State {
))
}
fn move_entry_path(
&self,
collection_id: Uuid,
id: Uuid,
token: &ValidToken,
direction: Direction,
) -> String {
self.scoped(&format!(
"{}/entry/{}/move/{}?token={}",
collection_id, id, direction, token.token
))
}
fn delete_entry_path(
&self,
collection_id: Uuid,
@ -247,6 +278,9 @@ pub fn configure(cfg: &mut web::ServiceConfig, state: State, client: Client) {
.service(
web::scope("/{entry}")
.service(web::resource("").route(web::post().to(update_entry)))
.service(
web::resource("/move/{direction}").route(web::get().to(move_entry)),
)
.service(web::resource("/delete").route(web::get().to(delete_entry))),
),
),
@ -448,7 +482,11 @@ impl CollectionPath {
format!("{}", self.collection)
}
fn entry_range(&self) -> std::ops::Range<Vec<u8>> {
fn order_key(&self) -> String {
format!("{}/order", self.collection)
}
fn entry_range(&self) -> std::ops::RangeInclusive<Vec<u8>> {
let base = format!("{}/entry/", self.collection).as_bytes().to_vec();
let mut start = base.clone();
let mut end = base;
@ -456,7 +494,7 @@ impl CollectionPath {
start.push(0x0);
end.push(0xff);
start..end
start..=end
}
fn token_key(&self) -> String {
@ -470,12 +508,36 @@ struct EntryPath {
entry: Uuid,
}
#[derive(Debug, serde::Deserialize)]
struct MoveEntryPath {
collection: Uuid,
entry: Uuid,
direction: Direction,
}
impl EntryPath {
fn key(&self) -> String {
format!("{}/entry/{}", self.collection, self.entry)
}
}
impl MoveEntryPath {
fn order_key(&self) -> String {
format!("{}/order", self.collection)
}
fn entry_range(&self) -> std::ops::RangeInclusive<Vec<u8>> {
let base = format!("{}/entry/", self.collection).as_bytes().to_vec();
let mut start = base.clone();
let mut end = base;
start.push(0x0);
end.push(0xff);
start..=end
}
}
#[tracing::instrument(name = "Upload image", skip(pl))]
async fn upload(
req: HttpRequest,
@ -588,7 +650,10 @@ async fn view_collection(
Some(collection) => collection,
None => return Ok(to_404(&state)),
};
let entries = state.store.entries(path.entry_range()).await?;
let entries = state
.store
.entries(path.order_key(), path.entry_range())
.await?;
rendered(
|cursor| {
@ -608,7 +673,10 @@ async fn edit_collection(
Some(collection) => collection,
None => return Ok(to_404(&state)),
};
let entries = state.store.entries(path.entry_range()).await?;
let entries = state
.store
.entries(path.order_key(), path.entry_range())
.await?;
rendered(
|cursor| {
@ -672,6 +740,21 @@ async fn update_collection(
Ok(to_edit_page(path.collection, &token, &state))
}
async fn move_entry(
path: web::Path<MoveEntryPath>,
token: ValidToken,
state: web::Data<State>,
) -> Result<HttpResponse, StateError> {
store::MoveEntry {
move_entry_path: &path,
}
.exec(&state.store)
.await
.stateful(&state)?;
Ok(to_edit_page(path.collection, &token, &state))
}
#[tracing::instrument(name = "Update Entry")]
async fn update_entry(
entry_path: web::Path<EntryPath>,
@ -774,7 +857,7 @@ async fn delete_collection(
let entries = state
.store
.entries(path.entry_range())
.entries(path.order_key(), path.entry_range())
.await
.stateful(&state)?;

View file

@ -1,7 +1,8 @@
use crate::{Collection, CollectionPath, Entry, EntryPath, Token};
use crate::{Collection, CollectionPath, Direction, Entry, EntryPath, MoveEntryPath, Token};
use actix_web::web;
use sled::{Db, Tree};
use std::ops::Range;
use std::collections::BTreeMap;
use std::ops::RangeInclusive;
use uuid::Uuid;
#[derive(Clone)]
@ -35,6 +36,10 @@ pub(crate) struct CreateEntry<'a> {
pub(crate) entry: &'a Entry,
}
pub(crate) struct MoveEntry<'a> {
pub(crate) move_entry_path: &'a MoveEntryPath,
}
pub(crate) struct UpdateEntry<'a> {
pub(crate) entry_path: &'a EntryPath,
pub(crate) entry: &'a Entry,
@ -68,6 +73,12 @@ impl<'a> CreateEntry<'a> {
}
}
impl<'a> MoveEntry<'a> {
pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> {
store.move_entry(self).await
}
}
impl<'a> UpdateEntry<'a> {
pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> {
store.update_entry(self).await
@ -161,6 +172,52 @@ impl Store {
Ok(())
}
async fn move_entry(&self, config: MoveEntry<'_>) -> Result<(), Error> {
let order_key = config.move_entry_path.order_key();
let tree = self.tree.clone();
let order_key_2 = order_key.clone();
let order = web::block(move || tree.get(order_key_2)).await??;
let mut order = if let Some(order) = order {
serde_json::from_slice::<Vec<Uuid>>(&order)?
} else {
let order_key_2 = order_key.clone();
let entries = self
.entries(order_key_2, config.move_entry_path.entry_range())
.await?;
entries.into_iter().map(|(k, _)| k).collect()
};
let entry_uuid = config.move_entry_path.entry;
let direction = config.move_entry_path.direction;
for i in 0..order.len() {
if i + 1 < order.len() {
match direction {
Direction::Up if order[i + 1] == entry_uuid => {
order[i + 1] = order[i];
order[i] = entry_uuid;
break;
}
Direction::Down if order[i] == entry_uuid => {
order[i] = order[i + 1];
order[i + 1] = entry_uuid;
break;
}
_ => {}
}
}
}
let order_vec = serde_json::to_vec(&order)?;
let tree = self.tree.clone();
web::block(move || tree.insert(order_key, order_vec)).await??;
Ok(())
}
async fn update_entry(&self, config: UpdateEntry<'_>) -> Result<(), Error> {
let entry_key = config.entry_path.key();
let entry_value = serde_json::to_string(&config.entry)?;
@ -214,11 +271,16 @@ impl Store {
}
}
pub(crate) async fn entries(&self, range: Range<Vec<u8>>) -> Result<Vec<(Uuid, Entry)>, Error> {
pub(crate) async fn entries(
&self,
order_key: String,
range: RangeInclusive<Vec<u8>>,
) -> Result<Vec<(Uuid, Entry)>, Error> {
let tree = self.tree.clone();
let v = web::block(move || {
tree.range(range)
let mut hashmap = tree
.range(range)
.map(|res| match res {
Err(e) => Err(e.into()),
Ok((k, v)) => {
@ -226,12 +288,31 @@ impl Store {
let entry_uuid = string_key.split(|b| b == '/').rev().next().unwrap();
let uuid: Uuid = entry_uuid.parse()?;
let string_value = String::from_utf8_lossy(&v);
let entry: Entry = serde_json::from_str(&string_value)?;
let entry: Entry = serde_json::from_slice(&v)?;
Ok((uuid, entry))
}
})
.collect::<Result<Vec<_>, Error>>()
.collect::<Result<BTreeMap<Uuid, Entry>, Error>>()?;
let order = if let Some(order) = tree.get(&order_key)? {
serde_json::from_slice::<Vec<Uuid>>(&order)?
} else {
Vec::new()
};
let mut vec = order.into_iter().fold(Vec::new(), |mut vec, uuid| {
if let Some(entry) = hashmap.remove(&uuid) {
vec.push((uuid, entry));
vec
} else {
vec
}
});
vec.extend(hashmap);
Ok(vec) as Result<_, Error>
})
.await??;

View file

@ -1,4 +1,4 @@
@use crate::{ui::ButtonKind, Collection, Entry, State, ValidToken};
@use crate::{ui::ButtonKind, Collection, Direction, Entry, State, ValidToken};
@use super::{button, button_link, image, file_input, layout, return_home, text_area, text_input, statics::file_upload_js};
@use uuid::Uuid;
@ -43,7 +43,7 @@
</form>
</article>
<ul>
@for (id, entry) in entries {
@for (i, (id, entry)) in entries.into_iter().enumerate() {
<li class="content-group">
<article>
<div class="edit-row">
@ -60,6 +60,16 @@
@:button("Update Image", ButtonKind::Submit)
@:button_link("Delete Image", &state.delete_entry_path(collection_id, *id, token, false), ButtonKind::Outline)
</div>
<div class="button-group button-space">
@if i != 0 {
@:button_link("Move Up", &state.move_entry_path(collection_id, *id, token, Direction::Up), ButtonKind::Outline)
}
@if (i + 1) != entries.len() {
@:button_link("Move Down", &state.move_entry_path(collection_id, *id, token, Direction::Down), ButtonKind::Outline)
}
</div>
</form>
</div>
</div>