diff --git a/admin-frontend/src/components/manage-files-form.js b/admin-frontend/src/components/manage-files-form.js index b743abf057247d150ab5cdff786eddb8743f54cd..8aefd98c1b090c84249bd6e2f5cafc1a6499f91e 100644 --- a/admin-frontend/src/components/manage-files-form.js +++ b/admin-frontend/src/components/manage-files-form.js @@ -1,11 +1,11 @@ "use strict"; -const { fetch_post_file } = require("../xhr"); +const { fetch_post_file, fetch_delete_static_file } = require("../xhr"); class FilesIndexView { - constructor(index) { - this.index = index; - this.show_category = Object.keys(this.index)[0]; + constructor(params) { + this.params = params; + this.show_category = Object.keys(this.params.get_files_index())[0]; this.id = "files-index-view"; } @@ -34,41 +34,63 @@ class FilesIndexView { } get_item_view(item) { - switch (this.show_category) { - case "images": - return { - tag: "a", - href: item, - target: "_blank", - style_rules: { - backgroundImage: `url(${item})`, - backgroundRepeat: "no-repeat", - backgroundSize: "cover", - backgroundPosition: "center", - width: "100px", - height: "100px", - display: "block" - } - } - case "sounds": - return { - tag: "a", - href: item, - target: "_blank", - contents: [ - { - tag: "audio", - src: item + const get_link = () => { + switch (this.show_category) { + case "images": + return { + tag: "a", + href: item, + target: "_blank", + style_rules: { + backgroundImage: `url(${item})`, + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + backgroundPosition: "center", + width: "100px", + height: "100px", + display: "block" } - ] - } - default: - return { - tag: "a", - href: item, - target: "_blank", - contents: item + } + case "sounds": + return { + tag: "a", + href: item, + target: "_blank", + contents: [ + { + tag: "audio", + src: item + } + ] + } + default: + return { + tag: "a", + href: item, + target: "_blank", + contents: item + } + } + } + + return { + tag: "div", + contents: [ + get_link(), + { + tag: "button", + contents: "DEL", + onclick: () => { + const rev_split = item.split("/").reverse(); + const filename = rev_split.shift(); + const category = rev_split.shift(); + const dir = rev_split[0] === "uploads" ? "uploads" : "base"; + fetch_delete_static_file(dir, category, filename).then(res => { + this.params.refresh_index(this.refresh.bind(this)); + }).catch(err => console.log(err)) + } } + ] } } @@ -77,6 +99,7 @@ class FilesIndexView { } render() { + const files_index = this.params.get_files_index(); return { tag: "div", id: this.id, @@ -89,7 +112,7 @@ class FilesIndexView { margin: 0, padding: 0, }, - contents: Object.keys(this.index).map(cat => { + contents: Object.keys(files_index).map(cat => { return { tag: "li", style_rules: { @@ -109,7 +132,7 @@ class FilesIndexView { { tag: "ul", style_rules: this.get_files_list_style(this.show_category), - contents: this.index[this.show_category].map(item => { + contents: files_index[this.show_category].map(item => { return { tag: "li", contents: [ @@ -126,24 +149,32 @@ class FilesIndexView { class ManageFilesForm { constructor(params) { this.params = params; + this.id = "static-file-manager-view"; } render_index_view() { - return new FilesIndexView(this.params.files_index).render() + return new FilesIndexView(this.params).render() + } + + refresh() { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" }) } render() { return { tag: "div", + id: this.id, contents: [ this.render_index_view(), { tag: "form", style_rules: { border: "1px solid black" }, enctype: "multipart/form-data", - onsubmit: function (e) { + onsubmit: e => { e.preventDefault(); - fetch_post_file(e.target).then(res => console.log(res)).catch(err => console.log(err)); + fetch_post_file(e.target).then(() => { + this.params.refresh_index(this.refresh.bind(this)) + }).catch(err => console.log(err)); }, contents: [ { tag: "input", name: "file", type: "file", multiple: true }, diff --git a/admin-frontend/src/components/root.js b/admin-frontend/src/components/root.js index d14afadd7c3a64c59ca1716010000d49f0efeba0..ce72602c49c4b4c22a45534873ad7a1910e98dc1 100644 --- a/admin-frontend/src/components/root.js +++ b/admin-frontend/src/components/root.js @@ -1,5 +1,5 @@ -const { fetch_post_file, fetch_static_files_index } = require("../xhr"); +const { fetch_static_files_index } = require("../xhr"); const CreateArticleForm = require("./create-article-form"); const ManageFilesForm = require("./manage-files-form"); const UpdateArticleForm = require("./update-article-form"); @@ -12,7 +12,7 @@ class RootComponent { static_files_index: {} }; - this.fetch_files_index(); + this.fetch_files_index(obj2htm.renderCycle.bind(obj2htm)); } build_index_object(files_urls) { @@ -24,7 +24,7 @@ class RootComponent { }, {}); } - fetch_files_index() { + fetch_files_index(cb) { fetch_static_files_index() .then(files => this.state.static_files_index = this.build_index_object(files) @@ -32,7 +32,7 @@ class RootComponent { .catch(err => console.log(err)) .finally(() => { this.state.loading_index = false; - obj2htm.renderCycle(); + cb(); }); } @@ -49,7 +49,7 @@ class RootComponent { case "update": return new UpdateArticleForm({ files_index }).render(); case "files": - return new ManageFilesForm({ files_index }).render() + return new ManageFilesForm({ get_files_index: () => this.state.static_files_index, refresh_index: this.fetch_files_index.bind(this) }).render() default: return undefined; } diff --git a/admin-frontend/src/xhr.js b/admin-frontend/src/xhr.js index 4c360f060046fd4344a1dc8c1001d45503351555..07a02badfdf39fb81d6a41904dabba8103d95294 100644 --- a/admin-frontend/src/xhr.js +++ b/admin-frontend/src/xhr.js @@ -117,7 +117,6 @@ function fetch_post_file(form) { } }).catch(err => reject(err)) }) - } function fetch_static_files_index() { @@ -132,6 +131,22 @@ function fetch_static_files_index() { }) } +function fetch_delete_static_file(dir, category, filename) { + return new Promise((resolve, reject) => { + fetch(`/delete-file/${dir}/${category}/${filename}`, { + credentials: 'include', + method: "DELETE", + }).then(async res => { + const text = await res.text(); + if (res.status >= 400 && res.status < 600) { + reject(text) + } else { + resolve(text); + } + }).catch(err => reject(err)) + }) +} + module.exports = { fetch_article, @@ -142,4 +157,5 @@ module.exports = { fetch_all_articles, fetch_post_file, fetch_static_files_index, + fetch_delete_static_file, } \ No newline at end of file diff --git a/src/core/static_files.rs b/src/core/static_files.rs index e8057dc4666bc65af40d4b3442b8a8932b1811f9..bf5b4c7b8fb67b079c3c0947a28a801b27631394 100644 --- a/src/core/static_files.rs +++ b/src/core/static_files.rs @@ -43,7 +43,6 @@ impl StaticFilesIndex { } pub fn rebuild(&mut self, env: &Env) { - // let root = env.public_dir.join("assets"); let public_dir = StaticFilesIndex::get_public_dir(env); let root = public_dir.join("assets"); self.0 = Vec::new(); @@ -55,6 +54,15 @@ impl StaticFilesIndex { StaticFilesIndex::_push_path(path, &mut self.0, &strip_from); } + pub fn remove_path(&mut self, strpath: String) { + self.0 = self + .0 + .iter() + .filter(|url| strpath != **url) + .map(|s| s.to_owned()) + .collect(); + } + pub fn get_index(&self) -> Vec<String> { self.0.clone() } diff --git a/src/main.rs b/src/main.rs index 07ac8b4e0c1822f397e213fc8a5d4a24cccd0a2e..ac266aa6a8a7c24aa6870dd7c231b99533b2e124 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,6 +96,7 @@ async fn main() -> std::io::Result<()> { .service(get_all_articles) .service(post_files) .service(get_static_files_index) + .service(delete_static_file) ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // STANDARD FILES /////////////////////////////////////////////////////////////////////////////////////////// .service(resource("/favicon.ico").route(get().to(favicon))) diff --git a/src/service/static_files.rs b/src/service/static_files.rs index 96850a27510f6d77f8d0231faa44d8b60c496d47..46215780f7a25b15e7328ec3ad26090095718380 100644 --- a/src/service/static_files.rs +++ b/src/service/static_files.rs @@ -1,6 +1,10 @@ use crate::{core::static_files::*, middleware::AuthenticatedAdminMiddleware, AppState}; use actix_multipart::Multipart; -use actix_web::{get, post, web::Data, HttpRequest, HttpResponse, Responder}; +use actix_web::{ + delete, get, post, + web::{Data, Path}, + HttpRequest, HttpResponse, Responder, +}; use futures::StreamExt; use std::{ fs::{remove_file, File}, @@ -118,6 +122,40 @@ async fn get_static_files_index( ) } +#[delete("/delete-file/{dir}/{category}/{filename}")] +async fn delete_static_file( + app_state: Data<AppState>, + static_files_index: Data<std::sync::Mutex<StaticFilesIndex>>, + fileinfo: Path<(String, String, String)>, + middleware: Data<AuthenticatedAdminMiddleware<'_>>, + req: HttpRequest, +) -> impl Responder { + if middleware.exec(&app_state, &req, None).await.is_err() { + return HttpResponse::Unauthorized().finish(); + } + + let (dir, cat, fname) = fileinfo.into_inner(); + let fpath = std::path::PathBuf::from(cat).join(fname); + let fbasedir = { + let p = std::path::PathBuf::from("assets"); + if dir == "uploads" { + p.join("uploads") + } else { + p + } + }; + + match remove_file(app_state.env.public_dir.join(&fbasedir).join(&fpath)) { + Ok(_) => { + let indexed_url = std::path::PathBuf::from("/").join(&fbasedir).join(&fpath); + let mut files_index = static_files_index.lock().unwrap(); + files_index.remove_path(indexed_url.to_str().unwrap().to_owned()); + HttpResponse::Accepted().body("File was deleted") + } + Err(e) => HttpResponse::InternalServerError().body(format!("Error deleting file {:?}", e)), + } +} + // EXAMPLE FROM ACTIX REPO (using threadpool) // use futures::TryStreamExt;