Delete pool
This commit is contained in:
parent
7553dd31dc
commit
261623960e
7 changed files with 100 additions and 26 deletions
|
|
@ -19,21 +19,21 @@ Still very much an early WIP.
|
|||
|
||||
### Roadmap
|
||||
|
||||
- [ ] Delete pools
|
||||
- [ ] Logging and improved error handling
|
||||
- [ ] Lossless compression
|
||||
- [ ] Caching
|
||||
- [ ] Text media
|
||||
- [ ] Improve CSS
|
||||
- [ ] Garbage collection background tasks (tags, pools)
|
||||
- [ ] Background tasks for garbage collection (dangling tags)
|
||||
- [ ] User management
|
||||
- [ ] Testing
|
||||
- [ ] Migrate to Cot...?
|
||||
|
||||
## Running
|
||||
|
||||
### Prerequisites
|
||||
### Dependencies
|
||||
|
||||
- `ffmpeg` and `ffprobe`
|
||||
- `ffmpeg` (with `ffprobe`)
|
||||
|
||||
### Development
|
||||
|
||||
|
|
|
|||
18
src/error.rs
18
src/error.rs
|
|
@ -8,32 +8,46 @@ use axum::{
|
|||
#[template(path = "pages/not_found.html")]
|
||||
struct NotFoundTemplate;
|
||||
|
||||
/// Errors from Samey.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SameyError {
|
||||
/// Integer conversion error.
|
||||
#[error("Integer conversion error: {0}")]
|
||||
IntConversion(#[from] std::num::TryFromIntError),
|
||||
/// Integer parsing error.
|
||||
#[error("Integer parsing error: {0}")]
|
||||
IntParse(#[from] std::num::ParseIntError),
|
||||
/// IO error.
|
||||
#[error("IO error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
/// Task error.
|
||||
#[error("Task error: {0}")]
|
||||
Join(#[from] tokio::task::JoinError),
|
||||
/// Template render error.
|
||||
#[error("Template render error: {0}")]
|
||||
Render(#[from] askama::Error),
|
||||
/// Database error.
|
||||
#[error("Database error: {0}")]
|
||||
Database(#[from] sea_orm::error::DbErr),
|
||||
/// File streaming error.
|
||||
#[error("File streaming error: {0}")]
|
||||
Multipart(#[from] axum::extract::multipart::MultipartError),
|
||||
/// Image error.
|
||||
#[error("Image error: {0}")]
|
||||
Image(#[from] image::ImageError),
|
||||
#[error("Not found")]
|
||||
NotFound,
|
||||
/// Authentication error.
|
||||
#[error("Authentication error: {0}")]
|
||||
Authentication(String),
|
||||
/// Not found.
|
||||
#[error("Not found")]
|
||||
NotFound,
|
||||
/// Not allowed.
|
||||
#[error("Not allowed")]
|
||||
Forbidden,
|
||||
/// Bad request.
|
||||
#[error("Bad request: {0}")]
|
||||
BadRequest(String),
|
||||
/// Custom internal error.
|
||||
#[error("Internal error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
|
|
|||
34
src/lib.rs
34
src/lib.rs
|
|
@ -1,3 +1,5 @@
|
|||
//! Sam's small image board.
|
||||
|
||||
pub(crate) mod auth;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod entities;
|
||||
|
|
@ -63,14 +65,26 @@ pub(crate) struct AppState {
|
|||
app_config: Arc<RwLock<AppConfig>>,
|
||||
}
|
||||
|
||||
/// Helper function to create a single user.
|
||||
///
|
||||
/// You can specify if they must be an admin user via the `is_admin` flag.
|
||||
///
|
||||
/// ```
|
||||
/// use samey::create_user;
|
||||
///
|
||||
/// # async fn _main() {
|
||||
/// let db = sea_orm::Database::connect("sqlite:db.sqlite3?mode=rwc").await.unwrap();
|
||||
/// create_user(db, "admin", "secretPassword", true).await.expect("Unable to add admin user");
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn create_user(
|
||||
db: DatabaseConnection,
|
||||
username: String,
|
||||
password: String,
|
||||
username: &str,
|
||||
password: &str,
|
||||
is_admin: bool,
|
||||
) -> Result<(), SameyError> {
|
||||
SameyUser::insert(samey_user::ActiveModel {
|
||||
username: Set(username),
|
||||
username: Set(username.into()),
|
||||
password: Set(generate_hash(password)),
|
||||
is_admin: Set(is_admin),
|
||||
..Default::default()
|
||||
|
|
@ -80,6 +94,18 @@ pub async fn create_user(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates an Axum router for a Samey application.
|
||||
///
|
||||
/// ```
|
||||
/// use samey::get_router;
|
||||
///
|
||||
/// # async fn _main() {
|
||||
/// let db = sea_orm::Database::connect("sqlite:db.sqlite3?mode=rwc").await.unwrap();
|
||||
/// let app = get_router(db, "files").await.unwrap();
|
||||
/// let listener = tokio::net::TcpListener::bind(("0.0.0.0", 3000)).await.unwrap();
|
||||
/// axum::serve(listener, app).await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn get_router(
|
||||
db: DatabaseConnection,
|
||||
files_dir: impl AsRef<Path>,
|
||||
|
|
@ -123,7 +149,7 @@ pub async fn get_router(
|
|||
.route_with_tsr("/pools", get(get_pools))
|
||||
.route_with_tsr("/pools/{page}", get(get_pools_page))
|
||||
.route_with_tsr("/pool", post(create_pool))
|
||||
.route_with_tsr("/pool/{pool_id}", get(view_pool))
|
||||
.route_with_tsr("/pool/{pool_id}", get(view_pool).delete(delete_pool))
|
||||
.route_with_tsr("/pool/{pool_id}/name", put(change_pool_name))
|
||||
.route_with_tsr("/pool/{pool_id}/public", put(change_pool_visibility))
|
||||
.route_with_tsr("/pool/{pool_id}/post", post(add_post_to_pool))
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async fn main() {
|
|||
}
|
||||
|
||||
Commands::AddAdminUser { username, password } => {
|
||||
create_user(db, username, password, true)
|
||||
create_user(db, &username, &password, true)
|
||||
.await
|
||||
.expect("Unable to add admin user");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub(crate) fn generate_thumbnail(
|
|||
max_thumbnail_dimension: u32,
|
||||
) -> Result<(), SameyError> {
|
||||
let status = Command::new("ffmpeg")
|
||||
.args(&[
|
||||
.args([
|
||||
"-i",
|
||||
input_path,
|
||||
"-vf",
|
||||
|
|
@ -39,7 +39,7 @@ pub(crate) fn generate_thumbnail(
|
|||
|
||||
pub(crate) fn get_dimensions_for_video(input_path: &str) -> Result<(u32, u32), SameyError> {
|
||||
let output = Command::new("ffprobe")
|
||||
.args(&[
|
||||
.args([
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
|
|
|
|||
46
src/views.rs
46
src/views.rs
|
|
@ -357,6 +357,7 @@ pub(crate) async fn upload(
|
|||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&file_path)?;
|
||||
while let Some(chunk) = field.chunk().await? {
|
||||
file.write_all(&chunk)?;
|
||||
|
|
@ -407,6 +408,7 @@ pub(crate) async fn upload(
|
|||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&file_path)?;
|
||||
while let Some(chunk) = field.chunk().await? {
|
||||
file.write_all(&chunk)?;
|
||||
|
|
@ -528,13 +530,13 @@ pub(crate) async fn search_tags(
|
|||
State(AppState { db, .. }): State<AppState>,
|
||||
Form(body): Form<SearchTagsForm>,
|
||||
) -> Result<impl IntoResponse, SameyError> {
|
||||
let tags = match body.tags[..body.selection_end].split(' ').last() {
|
||||
let tags = match body.tags[..body.selection_end].split(' ').next_back() {
|
||||
Some(mut tag) => {
|
||||
tag = tag.trim();
|
||||
if tag.is_empty() {
|
||||
vec![]
|
||||
} else if tag.starts_with(NEGATIVE_PREFIX) {
|
||||
if tag[NEGATIVE_PREFIX.len()..].starts_with(RATING_PREFIX) {
|
||||
} else if let Some(stripped_tag) = tag.strip_prefix(NEGATIVE_PREFIX) {
|
||||
if stripped_tag.starts_with(RATING_PREFIX) {
|
||||
[
|
||||
format!("{}u", RATING_PREFIX),
|
||||
format!("{}s", RATING_PREFIX),
|
||||
|
|
@ -542,7 +544,7 @@ pub(crate) async fn search_tags(
|
|||
format!("{}e", RATING_PREFIX),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|t| t.starts_with(&tag[NEGATIVE_PREFIX.len()..]))
|
||||
.filter(|t| t.starts_with(stripped_tag))
|
||||
.map(|tag| SearchTag {
|
||||
value: format!("-{}", &tag),
|
||||
name: tag,
|
||||
|
|
@ -552,7 +554,7 @@ pub(crate) async fn search_tags(
|
|||
SameyTag::find()
|
||||
.filter(Expr::cust_with_expr(
|
||||
"LOWER(\"samey_tag\".\"name\") LIKE CONCAT(?, '%')",
|
||||
tag[NEGATIVE_PREFIX.len()..].to_lowercase(),
|
||||
stripped_tag.to_lowercase(),
|
||||
))
|
||||
.limit(10)
|
||||
.all(&db)
|
||||
|
|
@ -1115,12 +1117,10 @@ pub(crate) async fn sort_pool(
|
|||
};
|
||||
let max_index = if body.new_index == posts.len().saturating_sub(1) {
|
||||
None
|
||||
} else {
|
||||
if body.new_index < body.old_index {
|
||||
} else if body.new_index < body.old_index {
|
||||
Some(body.new_index)
|
||||
} else {
|
||||
Some(body.new_index + 1)
|
||||
}
|
||||
};
|
||||
let min = min_index.map(|index| posts[index].position).unwrap_or(0.0);
|
||||
let max = max_index
|
||||
|
|
@ -1148,6 +1148,30 @@ pub(crate) async fn sort_pool(
|
|||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_pool(
|
||||
State(AppState { db, .. }): State<AppState>,
|
||||
auth_session: AuthSession,
|
||||
Path(pool_id): Path<i32>,
|
||||
) -> Result<impl IntoResponse, SameyError> {
|
||||
let pool = SameyPool::find_by_id(pool_id)
|
||||
.one(&db)
|
||||
.await?
|
||||
.ok_or(SameyError::NotFound)?;
|
||||
|
||||
let can_edit = match auth_session.user.as_ref() {
|
||||
None => false,
|
||||
Some(user) => user.is_admin || pool.uploader_id == user.id,
|
||||
};
|
||||
|
||||
if !can_edit {
|
||||
return Err(SameyError::Forbidden);
|
||||
}
|
||||
|
||||
SameyPool::delete_by_id(pool_id).exec(&db).await?;
|
||||
|
||||
Ok(Redirect::to("/"))
|
||||
}
|
||||
|
||||
// Bulk edit tag views
|
||||
|
||||
enum BulkEditTagMessage {
|
||||
|
|
@ -1629,14 +1653,14 @@ pub(crate) async fn submit_post_details(
|
|||
}
|
||||
|
||||
let title = match body.title.trim() {
|
||||
title if title.is_empty() => None,
|
||||
"" => None,
|
||||
title => Some(title.to_owned()),
|
||||
};
|
||||
let description = match body.description.trim() {
|
||||
description if description.is_empty() => None,
|
||||
"" => None,
|
||||
description => Some(description.to_owned()),
|
||||
};
|
||||
let parent_post = if let Some(parent_id) = body.parent_post.trim().parse().ok() {
|
||||
let parent_post = if let Ok(parent_id) = body.parent_post.trim().parse() {
|
||||
match filter_posts_by_user(SameyPost::find_by_id(parent_id), auth_session.user.as_ref())
|
||||
.one(&db)
|
||||
.await?
|
||||
|
|
|
|||
|
|
@ -106,6 +106,16 @@
|
|||
value="true"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
hx-confirm="Are you sure that you want to delete this pool? This can't be undone!"
|
||||
hx-delete="/pool/{{ pool.id }}"
|
||||
hx-target="body"
|
||||
hx-replace-url="/"
|
||||
>
|
||||
Delete pool
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
</body>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue