This commit is contained in:
parent
9dcb5114f3
commit
32aaa1c464
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
/target
|
||||
/sled
|
||||
/.envrc
|
||||
/.direnv
|
||||
/result
|
||||
|
|
436
Cargo.lock
generated
436
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "pict-rs-aggregator"
|
||||
description = "A simple image aggregation service for pict-rs"
|
||||
version = "0.2.0-beta.2"
|
||||
version = "0.2.0-beta.1"
|
||||
authors = ["asonix <asonix@asonix.dog>"]
|
||||
license = "AGPL-3.0"
|
||||
readme = "README.md"
|
||||
|
@ -18,7 +18,7 @@ default = []
|
|||
actix-rt = "2.7.0"
|
||||
actix-web = { version = "4.0.0", default-features = false }
|
||||
awc = { version = "3.0.0", default-features = false }
|
||||
bcrypt = "0.13"
|
||||
bcrypt = "0.14"
|
||||
clap = { version = "4.0.2", features = ["derive", "env"] }
|
||||
console-subscriber = "0.1"
|
||||
mime = "0.3"
|
||||
|
@ -46,7 +46,7 @@ uuid = { version = "1", features = ["serde", "v4"] }
|
|||
|
||||
|
||||
[dependencies.tracing-actix-web]
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
default-features = false
|
||||
features = ["emit_event_on_error", "opentelemetry_0_18"]
|
||||
|
||||
|
|
43
flake.lock
Normal file
43
flake.lock
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1676283394,
|
||||
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1678293141,
|
||||
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
34
flake.nix
Normal file
34
flake.nix
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
description = "pict-rs-aggregator";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = rec {
|
||||
pict-rs-aggregator = pkgs.callPackage ./pict-rs-aggregator.nix { };
|
||||
|
||||
default = pict-rs-aggregator;
|
||||
};
|
||||
|
||||
apps = rec {
|
||||
dev = flake-utils.lib.mkApp { drv = self.packages.${system}.pict-rs-aggregator; };
|
||||
default = dev;
|
||||
};
|
||||
|
||||
devShell = with pkgs; mkShell {
|
||||
nativeBuildInputs = [ cargo cargo-outdated cargo-zigbuild clippy gcc protobuf rust-analyzer rustc rustfmt ];
|
||||
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
};
|
||||
});
|
||||
}
|
27
pict-rs-aggregator.nix
Normal file
27
pict-rs-aggregator.nix
Normal file
|
@ -0,0 +1,27 @@
|
|||
{ lib
|
||||
, makeWrapper
|
||||
, nixosTests
|
||||
, protobuf
|
||||
, rustPlatform
|
||||
, stdenv
|
||||
}:
|
||||
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "pict-rs-aggregator";
|
||||
version = "0.2.0-beta.1";
|
||||
src = ./.;
|
||||
cargoSha256 = "UHTjI4UAVFP4KnkyWuubiVkFev+/z3PvCvDZVOy+Kxs=";
|
||||
|
||||
PROTOC = "${protobuf}/bin/protoc";
|
||||
PROTOC_INCLUDE = "${protobuf}/include";
|
||||
|
||||
nativeBuildInputs = [ ];
|
||||
|
||||
passthru.tests = { inherit (nixosTests) pict-rs-aggregator; };
|
||||
|
||||
meta = with lib; {
|
||||
description = "A simple image hosting service";
|
||||
homepage = "https://git.asonix.dog/asonix/pict-rs-aggregator";
|
||||
license = with licenses; [ agpl3Plus ];
|
||||
};
|
||||
}
|
64
src/lib.rs
64
src/lib.rs
|
@ -163,9 +163,9 @@ impl State {
|
|||
} else if s.is_empty() {
|
||||
self.scope.clone()
|
||||
} else if self.scope.is_empty() {
|
||||
format!("/{s}")
|
||||
format!("/{}", s)
|
||||
} else {
|
||||
format!("{}/{s}", self.scope)
|
||||
format!("{}/{}", self.scope, s)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,31 +174,37 @@ impl State {
|
|||
}
|
||||
|
||||
fn edit_collection_path(&self, id: Uuid, token: &ValidToken) -> String {
|
||||
self.scoped(&format!("{id}?token={}", token.token))
|
||||
self.scoped(&format!("{}?token={}", id, token.token))
|
||||
}
|
||||
|
||||
fn update_collection_path(&self, id: Uuid, token: &ValidToken) -> String {
|
||||
self.scoped(&format!("{id}?token={}", token.token))
|
||||
self.scoped(&format!("{}?token={}", id, token.token))
|
||||
}
|
||||
|
||||
fn delete_collection_path(&self, id: Uuid, token: &ValidToken, confirmed: bool) -> String {
|
||||
if confirmed {
|
||||
self.scoped(&format!("{id}/delete?token={}&confirmed=true", token.token))
|
||||
self.scoped(&format!(
|
||||
"{}/delete?token={}&confirmed=true",
|
||||
id, token.token
|
||||
))
|
||||
} else {
|
||||
self.scoped(&format!("{id}/delete?token={}", token.token))
|
||||
self.scoped(&format!("{}/delete?token={}", id, token.token))
|
||||
}
|
||||
}
|
||||
|
||||
fn public_collection_path(&self, id: Uuid) -> String {
|
||||
self.scoped(&format!("{id}"))
|
||||
self.scoped(&format!("{}", id))
|
||||
}
|
||||
|
||||
fn create_entry_path(&self, collection_id: Uuid, token: &ValidToken) -> String {
|
||||
self.scoped(&format!("{collection_id}/entry?token={}", token.token))
|
||||
self.scoped(&format!("{}/entry?token={}", collection_id, token.token))
|
||||
}
|
||||
|
||||
fn update_entry_path(&self, collection_id: Uuid, id: Uuid, token: &ValidToken) -> String {
|
||||
self.scoped(&format!("{collection_id}/entry/{id}?token={}", token.token))
|
||||
self.scoped(&format!(
|
||||
"{}/entry/{}?token={}",
|
||||
collection_id, id, token.token
|
||||
))
|
||||
}
|
||||
|
||||
fn move_entry_path(
|
||||
|
@ -209,8 +215,8 @@ impl State {
|
|||
direction: Direction,
|
||||
) -> String {
|
||||
self.scoped(&format!(
|
||||
"{collection_id}/entry/{id}/move/{direction}?token={}",
|
||||
token.token
|
||||
"{}/entry/{}/move/{}?token={}",
|
||||
collection_id, id, direction, token.token
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -223,24 +229,25 @@ impl State {
|
|||
) -> String {
|
||||
if confirmed {
|
||||
self.scoped(&format!(
|
||||
"{collection_id}/entry/{id}/delete?token={}&confirmed=true",
|
||||
token.token
|
||||
"{}/entry/{}/delete?token={}&confirmed=true",
|
||||
collection_id, id, token.token
|
||||
))
|
||||
} else {
|
||||
self.scoped(&format!(
|
||||
"{collection_id}/entry/{id}/delete?token={}",
|
||||
token.token
|
||||
"{}/entry/{}/delete?token={}",
|
||||
collection_id, id, token.token
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn statics_path(&self, file: &str) -> String {
|
||||
self.scoped(&format!("static/{file}"))
|
||||
self.scoped(&format!("static/{}", file))
|
||||
}
|
||||
|
||||
fn thumbnail_path(&self, filename: &str, size: u16, extension: pict::Extension) -> String {
|
||||
self.scoped(&format!(
|
||||
"image/thumbnail.{extension}?src={filename}&size={size}",
|
||||
"image/thumbnail.{}?src={}&size={}",
|
||||
extension, filename, size
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -249,8 +256,9 @@ impl State {
|
|||
|
||||
for size in connection::VALID_SIZES {
|
||||
sizes.push(format!(
|
||||
"{} {size}w",
|
||||
"{} {}w",
|
||||
self.thumbnail_path(filename, *size, extension),
|
||||
size,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -258,7 +266,7 @@ impl State {
|
|||
}
|
||||
|
||||
fn image_path(&self, filename: &str) -> String {
|
||||
self.scoped(&format!("image/full/{filename}"))
|
||||
self.scoped(&format!("image/full/{}", filename))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,7 +285,6 @@ pub fn configure(cfg: &mut web::ServiceConfig, state: State, client: Client) {
|
|||
client,
|
||||
)))
|
||||
.app_data(web::Data::new(state))
|
||||
.route("/healthz", web::get().to(healthz))
|
||||
.service(web::resource("/static/{filename}").route(web::get().to(static_files)))
|
||||
.service(web::resource("/404").route(web::get().to(not_found)))
|
||||
.service(
|
||||
|
@ -348,11 +355,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
async fn healthz(state: web::Data<State>) -> Result<HttpResponse, StateError> {
|
||||
state.store.check_health().await.stateful(&state)?;
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Static files")]
|
||||
async fn static_files(filename: web::Path<String>, state: web::Data<State>) -> HttpResponse {
|
||||
let filename = filename.into_inner();
|
||||
|
@ -678,7 +680,7 @@ async fn upload(
|
|||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("{e}");
|
||||
tracing::warn!("{}", e);
|
||||
let _ = store::DeleteEntry {
|
||||
entry_path: &entry_path2,
|
||||
}
|
||||
|
@ -775,13 +777,7 @@ async fn view_collection(
|
|||
|
||||
rendered(
|
||||
|cursor| {
|
||||
self::templates::view_collection_html(
|
||||
cursor,
|
||||
path.collection,
|
||||
&collection,
|
||||
&entries,
|
||||
&state,
|
||||
)
|
||||
self::templates::view_collection_html(cursor, path.collection, &collection, &entries, &state)
|
||||
},
|
||||
HttpResponse::Ok(),
|
||||
)
|
||||
|
@ -980,7 +976,7 @@ fn to_svg_string(qr: &qrcodegen::QrCode, border: i32) -> String {
|
|||
.checked_add(border.checked_mul(2).unwrap())
|
||||
.unwrap();
|
||||
result += &format!(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {dimension} {dimension}\" stroke=\"none\">\n");
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {0} {0}\" stroke=\"none\">\n", dimension);
|
||||
result += "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n";
|
||||
result += "\t<path d=\"";
|
||||
for y in 0..qr.size() {
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
@(text: &str, kind: ButtonKind)
|
||||
|
||||
@match kind {
|
||||
ButtonKind::Submit => {
|
||||
<div class="button submit">
|
||||
<span>@text</span>
|
||||
<button class="action" type="submit">@text</button>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Plain => {
|
||||
<div class="button plain">
|
||||
<span>@text</span>
|
||||
<button class="action">@text</button>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Outline => {
|
||||
<div class="button outline">
|
||||
<span>@text</span>
|
||||
<button class="action">@text</button>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Submit => {
|
||||
<div class="button submit">
|
||||
<span>@text</span>
|
||||
<button class="action" type="submit">@text</button>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Plain => {
|
||||
<div class="button plain">
|
||||
<span>@text</span>
|
||||
<button class="action">@text</button>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Outline => {
|
||||
<div class="button outline">
|
||||
<span>@text</span>
|
||||
<button class="action">@text</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,22 +3,23 @@
|
|||
@(text: &str, href: &str, kind: ButtonKind)
|
||||
|
||||
@match kind {
|
||||
ButtonKind::Submit => {
|
||||
<div class="button submit">
|
||||
<span>@text</span>
|
||||
<a class="action" href="@href">@text</a>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Plain => {
|
||||
<div class="button plain">
|
||||
<span>@text</span>
|
||||
<a class="action" href="@href">@text</a>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Outline => {
|
||||
<div class="button outline">
|
||||
<span>@text</span>
|
||||
<a class="action" href="@href">@text</a>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Submit => {
|
||||
<div class="button submit">
|
||||
<span>@text</span>
|
||||
<a class="action" href="@href">@text</a>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Plain => {
|
||||
<div class="button plain">
|
||||
<span>@text</span>
|
||||
<a class="action" href="@href">@text</a>
|
||||
</div>
|
||||
}
|
||||
ButtonKind::Outline => {
|
||||
<div class="button outline">
|
||||
<span>@text</span>
|
||||
<a class="action" href="@href">@text</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,25 +4,24 @@
|
|||
|
||||
@(id: Uuid, collection: &Collection, token: &ValidToken, state: &State)
|
||||
|
||||
@:layout_html(state, &format!("Delete: {}", collection.title), Some(&format!("Are you sure you want to delete {}",
|
||||
collection.title)), {
|
||||
<meta property="og:url" content="@state.delete_collection_path(id, token, false)" />
|
||||
@:layout_html(state, &format!("Delete: {}", collection.title), Some(&format!("Are you sure you want to delete {}", collection.title)), {
|
||||
<meta property="og:url" content="@state.delete_collection_path(id, token, false)" />
|
||||
}, {
|
||||
<section>
|
||||
<article>
|
||||
<div class="content-group">
|
||||
<h3>Delete @collection.title</h3>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<p class="subtitle">Are you sure you want to delete @collection.title</p>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="button-group">
|
||||
@:button_link_html("Delete Collection", &state.delete_collection_path(id, token, true), ButtonKind::Submit)
|
||||
@:button_link_html("Cancel", &state.edit_collection_path(id, token), ButtonKind::Outline)
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<div class="content-group">
|
||||
<h3>Delete @collection.title</h3>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<p class="subtitle">Are you sure you want to delete @collection.title</p>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="button-group">
|
||||
@:button_link_html("Delete Collection", &state.delete_collection_path(id, token, true), ButtonKind::Submit)
|
||||
@:button_link_html("Cancel", &state.edit_collection_path(id, token), ButtonKind::Outline)
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
@:return_home_html(state)
|
||||
})
|
||||
|
|
|
@ -5,29 +5,29 @@
|
|||
@(collection_id: Uuid, id: Uuid, entry: &Entry, token: &ValidToken, state: &State)
|
||||
|
||||
@:layout_html(state, "Delete Image", Some("Are you sure you want to delete this image?"), {
|
||||
<meta property="og:url" content="@state.delete_entry_path(collection_id, id, token, false)" />
|
||||
<meta property="og:url" content="@state.delete_entry_path(collection_id, id, token, false)" />
|
||||
}, {
|
||||
<section>
|
||||
<article>
|
||||
<div class="content-group">
|
||||
<h3>Delete Image</h3>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="edit-row">
|
||||
<div class="edit-item">
|
||||
@:image_html(entry, state)
|
||||
<article>
|
||||
<div class="content-group">
|
||||
<h3>Delete Image</h3>
|
||||
</div>
|
||||
<div class="edit-item">
|
||||
<p class="delete-confirmation">Are you sure you want to delete this image?</p>
|
||||
<div class="button-group button-space">
|
||||
@:button_link_html("Delete Image", &state.delete_entry_path(collection_id, id, token, true),
|
||||
ButtonKind::Submit)
|
||||
@:button_link_html("Cancel", &state.edit_collection_path(collection_id, token), ButtonKind::Outline)
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="edit-row">
|
||||
<div class="edit-item">
|
||||
@:image_html(entry, state)
|
||||
</div>
|
||||
<div class="edit-item">
|
||||
<p class="delete-confirmation">Are you sure you want to delete this image?</p>
|
||||
<div class="button-group button-space">
|
||||
@:button_link_html("Delete Image", &state.delete_entry_path(collection_id, id, token, true), ButtonKind::Submit)
|
||||
@:button_link_html("Cancel", &state.edit_collection_path(collection_id, token), ButtonKind::Outline)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</article>
|
||||
</section>
|
||||
@:return_home_html(state)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
@use crate::{ui::ButtonKind, Collection, Direction, Entry, State, ValidToken};
|
||||
@use super::{button_html, button_link_html, image_html, file_input_html, layout_html, return_home_html, text_area_html,
|
||||
text_input_html,
|
||||
@use super::{button_html, button_link_html, image_html, file_input_html, layout_html, return_home_html, text_area_html, text_input_html,
|
||||
statics::file_upload_js};
|
||||
@use uuid::Uuid;
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
|
||||
@:layout_html(state, "Error", Some(error), {}, {
|
||||
<section>
|
||||
<article>
|
||||
<div class="content-group">
|
||||
<h3>Error</h3>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<p class="subtitle">@error</p>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<div class="content-group">
|
||||
<h3>Error</h3>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<p class="subtitle">@error</p>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
@:return_home_html(state)
|
||||
})
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
@:layout_html(state, "Not Found", None, {}, {
|
||||
<section>
|
||||
<article class="content-group">
|
||||
<h3>Not Found</h3>
|
||||
</article>
|
||||
<article class="content-group">
|
||||
<p><a href="@state.create_collection_path()">Return Home</a></p>
|
||||
</article>
|
||||
<article class="content-group">
|
||||
<h3>Not Found</h3>
|
||||
</article>
|
||||
<article class="content-group">
|
||||
<p><a href="@state.create_collection_path()">Return Home</a></p>
|
||||
</article>
|
||||
</section>
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue