Generate favicon from post

This commit is contained in:
Bad Manners 2025-04-20 11:09:58 -03:00
parent 94269d82f0
commit 7553dd31dc
5 changed files with 34 additions and 7 deletions

View file

@ -19,7 +19,6 @@ Still very much an early WIP.
### Roadmap ### Roadmap
- [ ] Favicon from post
- [ ] Delete pools - [ ] Delete pools
- [ ] Logging and improved error handling - [ ] Logging and improved error handling
- [ ] Lossless compression - [ ] Lossless compression

View file

@ -12,6 +12,8 @@ struct NotFoundTemplate;
pub enum SameyError { pub enum SameyError {
#[error("Integer conversion error: {0}")] #[error("Integer conversion error: {0}")]
IntConversion(#[from] std::num::TryFromIntError), IntConversion(#[from] std::num::TryFromIntError),
#[error("Integer parsing error: {0}")]
IntParse(#[from] std::num::ParseIntError),
#[error("IO error: {0}")] #[error("IO error: {0}")]
IO(#[from] std::io::Error), IO(#[from] std::io::Error),
#[error("Task error: {0}")] #[error("Task error: {0}")]
@ -41,6 +43,7 @@ impl IntoResponse for SameyError {
println!("Server error - {}", &self); println!("Server error - {}", &self);
match &self { match &self {
SameyError::IntConversion(_) SameyError::IntConversion(_)
| SameyError::IntParse(_)
| SameyError::IO(_) | SameyError::IO(_)
| SameyError::Join(_) | SameyError::Join(_)
| SameyError::Render(_) | SameyError::Render(_)

View file

@ -17,7 +17,7 @@ use axum_extra::extract::Form;
use chrono::Utc; use chrono::Utc;
use image::{GenericImageView, ImageFormat, ImageReader}; use image::{GenericImageView, ImageFormat, ImageReader};
use itertools::Itertools; use itertools::Itertools;
use migration::{Expr, OnConflict}; use migration::{Expr, OnConflict, Query as MigrationQuery};
use rand::Rng; use rand::Rng;
use sea_orm::{ use sea_orm::{
ActiveValue::Set, ColumnTrait, Condition, EntityTrait, FromQueryResult, IntoSimpleExpr, ActiveValue::Set, ColumnTrait, Condition, EntityTrait, FromQueryResult, IntoSimpleExpr,
@ -1246,7 +1246,7 @@ pub(crate) async fn edit_tag(
.one(&db) .one(&db)
.await? .await?
{ {
let subquery = migration::Query::select() let subquery = MigrationQuery::select()
.column((SameyTagPost, samey_tag_post::Column::PostId)) .column((SameyTagPost, samey_tag_post::Column::PostId))
.from(SameyTagPost) .from(SameyTagPost)
.and_where(samey_tag_post::Column::TagId.eq(new_tag_db.id)) .and_where(samey_tag_post::Column::TagId.eq(new_tag_db.id))
@ -1332,11 +1332,17 @@ pub(crate) async fn settings(
pub(crate) struct UpdateSettingsForm { pub(crate) struct UpdateSettingsForm {
application_name: String, application_name: String,
base_url: String, base_url: String,
favicon_post_id: String,
age_confirmation: Option<bool>, age_confirmation: Option<bool>,
} }
pub(crate) async fn update_settings( pub(crate) async fn update_settings(
State(AppState { db, app_config, .. }): State<AppState>, State(AppState {
db,
app_config,
files_dir,
..
}): State<AppState>,
auth_session: AuthSession, auth_session: AuthSession,
Form(body): Form<UpdateSettingsForm>, Form(body): Form<UpdateSettingsForm>,
) -> Result<impl IntoResponse, SameyError> { ) -> Result<impl IntoResponse, SameyError> {
@ -1390,6 +1396,21 @@ pub(crate) async fn update_settings(
.await?; .await?;
} }
if let Some(favicon_post_id) = body.favicon_post_id.split_whitespace().next() {
match favicon_post_id.parse::<i32>() {
Ok(favicon_post_id) => {
let post = SameyPost::find_by_id(favicon_post_id)
.one(&db)
.await?
.ok_or(SameyError::NotFound)?;
ImageReader::open(files_dir.join(post.thumbnail))?
.decode()?
.save_with_format(files_dir.join("favicon.png"), ImageFormat::Png)?;
}
Err(err) => return Err(SameyError::IntParse(err)),
}
}
Ok(Redirect::to("/")) Ok(Redirect::to("/"))
} }
@ -1840,9 +1861,8 @@ pub(crate) async fn delete_post(
SameyPost::delete_by_id(post.id).exec(&db).await?; SameyPost::delete_by_id(post.id).exec(&db).await?;
tokio::spawn(async move { tokio::spawn(async move {
let base_path = files_dir.as_ref(); let _ = std::fs::remove_file(files_dir.join(post.media));
let _ = std::fs::remove_file(base_path.join(post.media)); let _ = std::fs::remove_file(files_dir.join(post.thumbnail));
let _ = std::fs::remove_file(base_path.join(post.thumbnail));
}); });
Ok(Redirect::to("/")) Ok(Redirect::to("/"))

View file

@ -1,5 +1,6 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/files/favicon.png" />
<script src="/static/htmx.js"></script> <script src="/static/htmx.js"></script>
<script defer src="/static/alpine.js"></script> <script defer src="/static/alpine.js"></script>
<link rel="stylesheet" href="/static/water.css" /> <link rel="stylesheet" href="/static/water.css" />

View file

@ -24,6 +24,10 @@
<label>Base URL</label> <label>Base URL</label>
<input name="base_url" type="text" value="{{ base_url }}" /> <input name="base_url" type="text" value="{{ base_url }}" />
</div> </div>
<div>
<label>Favicon post ID</label>
<input name="favicon_post_id" type="text" pattern="[0-9]*" />
</div>
<div> <div>
<label>Ask for age confirmation?</label> <label>Ask for age confirmation?</label>
<input <input