This commit is contained in:
parent
e4e6119f0d
commit
dd8d95346e
51
Cargo.lock
generated
51
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
|
93
src/lib.rs
93
src/lib.rs
|
@ -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)?;
|
||||
|
||||
|
|
95
src/store.rs
95
src/store.rs
|
@ -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??;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue