Server: Expose NSFW toggle, Dark Mode toggle

Ensure all submission view permission logic is the same
This commit is contained in:
asonix 2021-02-03 21:07:45 -06:00
parent 81edfa7123
commit 010dd2952f
13 changed files with 345 additions and 123 deletions

View file

@ -66,10 +66,16 @@ impl ViewBrowseState {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let store = state.profiles.clone(); let store = state.profiles.clone();
let can_view_sensitive = if let Some(viewer) = viewer {
state.settings.for_profile(viewer).await?.sensitive
} else {
false
};
let state = actix_web::web::block(move || { let state = actix_web::web::block(move || {
let mut cache = Cache::new(); let mut cache = Cache::new();
let page = browse_page(viewer, &store, &mut cache, source); let page = browse_page(viewer, can_view_sensitive, &store, &mut cache, source);
Ok(ViewBrowseState { Ok(ViewBrowseState {
cache, cache,

View file

@ -163,16 +163,16 @@ fn can_view_logged_out(
None => return Ok(false), None => return Ok(false),
}; };
if submission.is_followers_only() { if crate::pagination::submission::can_view(
return Ok(false); None,
} &submission,
&store.store,
let submissioner = match store.store.profiles.by_id(submission.profile_id())? { &mut Default::default(),
Some(s) => s, true,
None => return Ok(false), false,
}; )
.is_none()
if submissioner.login_required() { {
return Ok(false); return Ok(false);
} }
@ -217,6 +217,7 @@ fn can_view_comment_logged_out_no_recurse(
fn can_view( fn can_view(
profile: &Profile, profile: &Profile,
can_view_sensitive: bool,
comment: &Comment, comment: &Comment,
store: &hyaenidae_profiles::State, store: &hyaenidae_profiles::State,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
@ -225,47 +226,19 @@ fn can_view(
None => return Ok(false), None => return Ok(false),
}; };
let submissioner_id = submission.profile_id(); if crate::pagination::submission::can_view(
Some(profile.id()),
let blocking_submissioner = store &submission,
.store &store.store,
.view &mut Default::default(),
.blocks true,
.by_forward(submissioner_id, profile.id())? can_view_sensitive,
.is_some(); )
.is_none()
if blocking_submissioner { {
return Ok(false); return Ok(false);
} }
let blocked_by_submissioner = store
.store
.view
.blocks
.by_forward(profile.id(), submissioner_id)?
.is_some();
if blocked_by_submissioner {
return Ok(false);
}
if submission.is_followers_only() {
let is_submissioner = profile.id() == submissioner_id;
if !is_submissioner {
let follows_submissioner = store
.store
.view
.follows
.by_forward(submissioner_id, profile.id())?
.is_some();
if !follows_submissioner {
return Ok(false);
}
}
}
can_view_comment(profile, comment, store) can_view_comment(profile, comment, store)
} }
@ -325,8 +298,14 @@ async fn prepare_view(
profile: Option<&Profile>, profile: Option<&Profile>,
state: &State, state: &State,
) -> Result<Option<CommentView>, Error> { ) -> Result<Option<CommentView>, Error> {
let can_view_sensitive = if let Some(profile) = profile {
state.settings.for_profile(profile.id()).await?.sensitive
} else {
false
};
match profile { match profile {
Some(profile) if !can_view(&profile, &comment, &state.profiles)? => { Some(profile) if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? => {
return Ok(None); return Ok(None);
} }
None if !can_view_logged_out(&comment, &state.profiles)? => { None if !can_view_logged_out(&comment, &state.profiles)? => {
@ -550,7 +529,9 @@ async fn reply(
None => return Ok(crate::to_404()), None => return Ok(crate::to_404()),
}; };
if !can_view(&profile, &comment, &state.profiles)? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
return Ok(crate::to_404()); return Ok(crate::to_404());
} }
@ -738,7 +719,9 @@ async fn report_page(
None => return Ok(crate::to_404()), None => return Ok(crate::to_404()),
}; };
if !can_view(&profile, &comment, &state.profiles)? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
return Ok(crate::to_404()); return Ok(crate::to_404());
} }
@ -775,7 +758,9 @@ async fn report(
None => return Ok(crate::to_404()), None => return Ok(crate::to_404()),
}; };
if !can_view(&profile, &comment, &state.profiles)? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
return Ok(crate::to_404()); return Ok(crate::to_404());
} }
@ -828,7 +813,9 @@ async fn report_success_page(
None => return Ok(crate::to_404()), None => return Ok(crate::to_404()),
}; };
if !can_view(&profile, &comment, &state.profiles)? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
return Ok(crate::to_404()); return Ok(crate::to_404());
} }

View file

@ -221,6 +221,7 @@ struct State {
spawn: jobs::Spawn, spawn: jobs::Spawn,
apub: apub::Apub, apub: apub::Apub,
images: images::Images, images: images::Images,
settings: profiles::SettingStore,
domain: String, domain: String,
base_url: url::Url, base_url: url::Url,
db: Db, db: Db,
@ -240,6 +241,7 @@ impl State {
let domain = base_url.domain().req()?.to_owned(); let domain = base_url.domain().req()?.to_owned();
let admin = admin::Store::build(db)?; let admin = admin::Store::build(db)?;
let settings = profiles::SettingStore::build(db)?;
Ok(State { Ok(State {
profiles: hyaenidae_profiles::State::build( profiles: hyaenidae_profiles::State::build(
@ -254,6 +256,7 @@ impl State {
spawn, spawn,
apub, apub,
images, images,
settings,
base_url, base_url,
domain, domain,
db: db.clone(), db: db.clone(),

View file

@ -63,6 +63,12 @@ impl FromRequest for NavState {
format!("{}?show_nav=true", path) format!("{}?show_nav=true", path)
}; };
let dark = if let Some(profile) = &profile {
state.settings.for_profile(profile.id()).await?.dark
} else {
true
};
let notification_count = if let Some(profile) = &profile { let notification_count = if let Some(profile) = &profile {
total_for_profile(profile.id(), &state).await.ok() total_for_profile(profile.id(), &state).await.ok()
} else { } else {
@ -99,7 +105,7 @@ impl FromRequest for NavState {
admin, admin,
href, href,
is_open, is_open,
dark: true, dark,
}) })
}) })
} }

View file

@ -22,12 +22,14 @@ impl Cache {
pub(crate) fn browse_page( pub(crate) fn browse_page(
viewer: Option<Uuid>, viewer: Option<Uuid>,
can_view_sensitive: bool,
store: &hyaenidae_profiles::State, store: &hyaenidae_profiles::State,
cache: &mut Cache, cache: &mut Cache,
source: Option<PageSource>, source: Option<PageSource>,
) -> Page { ) -> Page {
Page::from_pagination( Page::from_pagination(
BrowsePager { BrowsePager {
can_view_sensitive,
viewer, viewer,
store, store,
cache, cache,
@ -39,6 +41,7 @@ pub(crate) fn browse_page(
pub(crate) fn draft_page( pub(crate) fn draft_page(
viewer: Option<Uuid>, viewer: Option<Uuid>,
can_view_sensitive: bool,
profile_id: Uuid, profile_id: Uuid,
store: &hyaenidae_profiles::State, store: &hyaenidae_profiles::State,
cache: &mut Cache, cache: &mut Cache,
@ -46,6 +49,7 @@ pub(crate) fn draft_page(
) -> Page { ) -> Page {
Page::from_pagination( Page::from_pagination(
DraftPager { DraftPager {
can_view_sensitive,
viewer, viewer,
profile_id, profile_id,
store, store,
@ -58,6 +62,7 @@ pub(crate) fn draft_page(
pub(crate) fn main_page( pub(crate) fn main_page(
viewer: Option<Uuid>, viewer: Option<Uuid>,
can_view_sensitive: bool,
profile_id: Uuid, profile_id: Uuid,
store: &hyaenidae_profiles::State, store: &hyaenidae_profiles::State,
cache: &mut Cache, cache: &mut Cache,
@ -65,6 +70,7 @@ pub(crate) fn main_page(
) -> Page { ) -> Page {
Page::from_pagination( Page::from_pagination(
SubmissionPager { SubmissionPager {
can_view_sensitive,
viewer, viewer,
profile_id, profile_id,
store, store,
@ -76,6 +82,7 @@ pub(crate) fn main_page(
} }
struct BrowsePager<'b> { struct BrowsePager<'b> {
can_view_sensitive: bool,
viewer: Option<Uuid>, viewer: Option<Uuid>,
store: &'b hyaenidae_profiles::State, store: &'b hyaenidae_profiles::State,
cache: &'b mut Cache, cache: &'b mut Cache,
@ -93,6 +100,7 @@ impl<'b> Pagination for BrowsePager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
false, false,
self.can_view_sensitive,
)), )),
) )
} }
@ -108,6 +116,7 @@ impl<'b> Pagination for BrowsePager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
false, false,
self.can_view_sensitive,
)), )),
) )
} }
@ -123,12 +132,14 @@ impl<'b> Pagination for BrowsePager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
false, false,
self.can_view_sensitive,
)), )),
) )
} }
} }
struct DraftPager<'b> { struct DraftPager<'b> {
can_view_sensitive: bool,
viewer: Option<Uuid>, viewer: Option<Uuid>,
profile_id: Uuid, profile_id: Uuid,
store: &'b hyaenidae_profiles::State, store: &'b hyaenidae_profiles::State,
@ -147,6 +158,7 @@ impl<'b> Pagination for DraftPager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
true, true,
self.can_view_sensitive,
)), )),
) )
} }
@ -162,6 +174,7 @@ impl<'b> Pagination for DraftPager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
true, true,
self.can_view_sensitive,
)), )),
) )
} }
@ -177,12 +190,14 @@ impl<'b> Pagination for DraftPager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
true, true,
self.can_view_sensitive,
)), )),
) )
} }
} }
struct SubmissionPager<'b> { struct SubmissionPager<'b> {
can_view_sensitive: bool,
viewer: Option<Uuid>, viewer: Option<Uuid>,
profile_id: Uuid, profile_id: Uuid,
store: &'b hyaenidae_profiles::State, store: &'b hyaenidae_profiles::State,
@ -201,6 +216,7 @@ impl<'b> Pagination for SubmissionPager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
true, true,
self.can_view_sensitive,
)), )),
) )
} }
@ -216,6 +232,7 @@ impl<'b> Pagination for SubmissionPager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
true, true,
self.can_view_sensitive,
)), )),
) )
} }
@ -231,6 +248,7 @@ impl<'b> Pagination for SubmissionPager<'b> {
&self.store.store, &self.store.store,
self.cache, self.cache,
true, true,
self.can_view_sensitive,
)), )),
) )
} }
@ -241,6 +259,7 @@ fn filter_submissions<'a>(
store: &'a hyaenidae_profiles::store::Store, store: &'a hyaenidae_profiles::store::Store,
cache: &'a mut Cache, cache: &'a mut Cache,
show_unlisted: bool, show_unlisted: bool,
can_view_sensitive: bool,
) -> impl FnMut(Uuid) -> Option<Uuid> + 'a { ) -> impl FnMut(Uuid) -> Option<Uuid> + 'a {
move |submission_id| { move |submission_id| {
if !cache.submission_map.contains_key(&submission_id) { if !cache.submission_map.contains_key(&submission_id) {
@ -251,7 +270,14 @@ fn filter_submissions<'a>(
cache.profile_map.insert(profile.id(), profile); cache.profile_map.insert(profile.id(), profile);
} }
let opt = can_view(viewer, &submission, store, cache, show_unlisted); let opt = can_view(
viewer,
&submission,
store,
cache,
show_unlisted,
can_view_sensitive,
);
if let Some(file_id) = submission.files().get(0) { if let Some(file_id) = submission.files().get(0) {
if !cache.file_map.contains_key(file_id) { if !cache.file_map.contains_key(file_id) {
@ -266,7 +292,14 @@ fn filter_submissions<'a>(
} else { } else {
let submission = cache.submission_map.get(&submission_id)?.clone(); let submission = cache.submission_map.get(&submission_id)?.clone();
can_view(viewer, &submission, store, cache, show_unlisted)?; can_view(
viewer,
&submission,
store,
cache,
show_unlisted,
can_view_sensitive,
)?;
Some(submission_id) Some(submission_id)
} }
@ -279,11 +312,17 @@ pub(crate) fn can_view(
store: &hyaenidae_profiles::store::Store, store: &hyaenidae_profiles::store::Store,
cache: &mut Cache, cache: &mut Cache,
show_unlisted: bool, show_unlisted: bool,
can_view_sensitive: bool,
) -> Option<()> { ) -> Option<()> {
if let Some(viewer) = viewer { if let Some(viewer) = viewer {
if viewer == submission.profile_id() { if viewer == submission.profile_id() {
return Some(()); return Some(());
} }
if submission.is_sensitive() && !can_view_sensitive {
return None;
}
if let Some(block) = cache.blocks.get(&submission.profile_id()) { if let Some(block) = cache.blocks.get(&submission.profile_id()) {
if *block { if *block {
return None; return None;

View file

@ -15,9 +15,11 @@ use i18n_embed_fl::fl;
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
mod settings;
mod state; mod state;
mod update; mod update;
pub(crate) use settings::{SettingStore, Settings};
pub use state::{EditProfileState, ViewProfileState}; pub use state::{EditProfileState, ViewProfileState};
pub use update::HandleState; pub use update::HandleState;

52
src/profiles/settings.rs Normal file
View file

@ -0,0 +1,52 @@
use crate::error::Error;
use sled::{Db, Tree};
use uuid::Uuid;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub(crate) struct Settings {
pub(crate) dark: bool,
pub(crate) sensitive: bool,
}
#[derive(Clone, Debug)]
pub(crate) struct SettingStore {
tree: Tree,
}
impl SettingStore {
pub(crate) fn build(db: &Db) -> Result<Self, sled::Error> {
Ok(SettingStore {
tree: db.open_tree("/main/settings")?,
})
}
pub(crate) async fn for_profile(&self, profile_id: Uuid) -> Result<Settings, Error> {
let this = self.clone();
let opt = actix_web::web::block(move || {
Ok(this
.tree
.get(profile_id.as_bytes())?
.and_then(|ivec| serde_json::from_slice(&ivec).ok()))
})
.await?;
Ok(opt.unwrap_or(Settings {
dark: true,
sensitive: false,
}))
}
pub(crate) async fn update(&self, profile_id: Uuid, settings: Settings) -> Result<(), Error> {
let this = self.clone();
actix_web::web::block(move || {
let vec = serde_json::to_vec(&settings)?;
this.tree.insert(profile_id.as_bytes(), vec)?;
Ok(()) as Result<_, Error>
})
.await?;
Ok(())
}
}

View file

@ -6,6 +6,7 @@ use crate::{
submission::{draft_page, main_page, Cache}, submission::{draft_page, main_page, Cache},
PageSource, PageSource,
}, },
profiles::settings::Settings,
views::ProfileView, views::ProfileView,
ActixLoader, State, ActixLoader, State,
}; };
@ -44,6 +45,8 @@ pub struct EditProfileState {
pub(crate) banner_error: Option<String>, pub(crate) banner_error: Option<String>,
login_required_value: Option<bool>, login_required_value: Option<bool>,
pub(crate) login_required_error: Option<String>, pub(crate) login_required_error: Option<String>,
pub(crate) settings_error: Option<String>,
pub(crate) settings: Settings,
} }
impl ViewProfileState { impl ViewProfileState {
@ -226,6 +229,12 @@ impl ViewProfileState {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let store = state.profiles.clone(); let store = state.profiles.clone();
let can_view_sensitive = if let Some(viewer) = viewer {
state.settings.for_profile(viewer).await?.sensitive
} else {
false
};
let state = actix_web::web::block(move || { let state = actix_web::web::block(move || {
let mut cache = Cache::new(); let mut cache = Cache::new();
@ -248,9 +257,23 @@ impl ViewProfileState {
cache.profile_map.insert(profile.id(), profile); cache.profile_map.insert(profile.id(), profile);
let page = if drafts { let page = if drafts {
draft_page(viewer, profile_id, &store, &mut cache, source) draft_page(
viewer,
can_view_sensitive,
profile_id,
&store,
&mut cache,
source,
)
} else { } else {
main_page(viewer, profile_id, &store, &mut cache, source) main_page(
viewer,
can_view_sensitive,
profile_id,
&store,
&mut cache,
source,
)
}; };
let is_self = viewer.map(|id| id == profile_id).unwrap_or(false); let is_self = viewer.map(|id| id == profile_id).unwrap_or(false);
@ -308,7 +331,12 @@ impl ViewProfileState {
} }
impl EditProfileState { impl EditProfileState {
pub(super) fn new(profile: Profile, icon: Option<File>, banner: Option<File>) -> Self { pub(super) fn new(
profile: Profile,
icon: Option<File>,
banner: Option<File>,
settings: Settings,
) -> Self {
EditProfileState { EditProfileState {
profile, profile,
icon, icon,
@ -321,6 +349,8 @@ impl EditProfileState {
banner_error: None, banner_error: None,
login_required_value: None, login_required_value: None,
login_required_error: None, login_required_error: None,
settings_error: None,
settings,
} }
} }
@ -330,6 +360,8 @@ impl EditProfileState {
let icon_id = profile.icon(); let icon_id = profile.icon();
let banner_id = profile.banner(); let banner_id = profile.banner();
let settings = state.settings.for_profile(profile.id()).await?;
let (icon, banner) = actix_web::web::block(move || { let (icon, banner) = actix_web::web::block(move || {
let icon = if let Some(id) = icon_id { let icon = if let Some(id) = icon_id {
store.store.files.by_id(id)? store.store.files.by_id(id)?
@ -346,7 +378,7 @@ impl EditProfileState {
}) })
.await?; .await?;
Ok(Self::new(profile, icon, banner)) Ok(Self::new(profile, icon, banner, settings))
} }
pub(crate) fn profile<'a>(&'a self) -> ProfileView<'a> { pub(crate) fn profile<'a>(&'a self) -> ProfileView<'a> {
@ -497,4 +529,9 @@ impl EditProfileState {
self.login_required_error = Some(err.to_owned()); self.login_required_error = Some(err.to_owned());
self self
} }
pub(super) fn settings_error(&mut self, err: String) -> &mut Self {
self.settings_error = Some(err);
self
}
} }

View file

@ -2,7 +2,7 @@ use crate::{
error::{Error, OptionExt}, error::{Error, OptionExt},
middleware::{ProfileData, UserProfile}, middleware::{ProfileData, UserProfile},
nav::NavState, nav::NavState,
profiles::{state::EditProfileState, to_current_profile}, profiles::{settings::Settings, state::EditProfileState, to_current_profile},
ActixLoader, State, ActixLoader, State,
}; };
use actix_session::Session; use actix_session::Session;
@ -66,6 +66,11 @@ pub(super) fn update_scope() -> Scope {
.route(web::post().to(update_require_login)) .route(web::post().to(update_require_login))
.route(web::get().to(to_current_profile)), .route(web::get().to(to_current_profile)),
) )
.service(
web::resource("/settings")
.route(web::post().to(update_settings))
.route(web::get().to(to_current_profile)),
)
} }
pub(super) fn to_create() -> HttpResponse { pub(super) fn to_create() -> HttpResponse {
@ -328,6 +333,39 @@ async fn update_require_login(
}) })
} }
#[derive(serde::Deserialize)]
struct SettingsForm {
sensitive: Option<String>,
dark: Option<String>,
}
async fn update_settings(
loader: ActixLoader,
form: web::Form<SettingsForm>,
profile: UserProfile,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let profile = profile.0;
let settings = Settings {
sensitive: form.sensitive.is_some(),
dark: form.dark.is_some(),
};
let error = match state.settings.update(profile.id(), settings).await {
Ok(_) => return Ok(to_current_profile()),
Err(e) => e.to_string(),
};
let mut profile_state = EditProfileState::for_profile(profile, &state).await?;
profile_state.settings_error(error);
crate::rendered(HttpResponse::InternalServerError(), |cursor| {
crate::templates::profiles::current(cursor, &loader, &profile_state, &nav_state)
})
}
#[derive(Clone, Debug, serde::Deserialize)] #[derive(Clone, Debug, serde::Deserialize)]
pub struct HandleForm { pub struct HandleForm {
pub(crate) handle: String, pub(crate) handle: String,

View file

@ -5,6 +5,7 @@ use crate::{
images::{largest_image, FullImage, ImageType}, images::{largest_image, FullImage, ImageType},
middleware::{CurrentSubmission, UserProfile}, middleware::{CurrentSubmission, UserProfile},
nav::NavState, nav::NavState,
profiles::Settings,
views::{OwnedProfileView, OwnedSubmissionView}, views::{OwnedProfileView, OwnedSubmissionView},
ActixLoader, State, ActixLoader, State,
}; };
@ -195,7 +196,16 @@ async fn report_page(
let view = ReportView::build(submission_id.into_inner(), &state).await?; let view = ReportView::build(submission_id.into_inner(), &state).await?;
if let Some(res) = can_view(Some(profile.id()), &view.author, &view.submission, &state).await? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if let Some(res) = can_view(
Some(profile.id()),
can_view_sensitive,
&view.submission,
&state,
)
.await?
{
return Ok(res); return Ok(res);
} }
@ -223,7 +233,16 @@ async fn report(
let mut view = ReportView::build(submission_id.into_inner(), &state).await?; let mut view = ReportView::build(submission_id.into_inner(), &state).await?;
if let Some(res) = can_view(Some(profile.id()), &view.author, &view.submission, &state).await? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if let Some(res) = can_view(
Some(profile.id()),
can_view_sensitive,
&view.submission,
&state,
)
.await?
{
return Ok(res); return Ok(res);
} }
@ -267,7 +286,16 @@ async fn report_success_page(
let view = ReportView::build(submission_id.into_inner(), &state).await?; let view = ReportView::build(submission_id.into_inner(), &state).await?;
if let Some(res) = can_view(Some(profile.id()), &view.author, &view.submission, &state).await? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if let Some(res) = can_view(
Some(profile.id()),
can_view_sensitive,
&view.submission,
&state,
)
.await?
{
return Ok(res); return Ok(res);
} }
@ -293,12 +321,15 @@ pub struct SubmissionState {
pub(crate) delete_error: Option<String>, pub(crate) delete_error: Option<String>,
pub(crate) visibility_error: Option<String>, pub(crate) visibility_error: Option<String>,
pub(crate) sensitive_error: Option<String>, pub(crate) sensitive_error: Option<String>,
pub(crate) settings: Settings,
} }
impl SubmissionState { impl SubmissionState {
async fn new(submission: Submission, state: &State) -> Result<Self, Error> { async fn new(submission: Submission, state: &State) -> Result<Self, Error> {
let file_ids: Vec<Uuid> = submission.files().iter().copied().collect(); let file_ids: Vec<Uuid> = submission.files().iter().copied().collect();
let settings = state.settings.for_profile(submission.profile_id()).await?;
let store = state.profiles.clone(); let store = state.profiles.clone();
let files = web::block(move || { let files = web::block(move || {
let mut files = HashMap::new(); let mut files = HashMap::new();
@ -327,6 +358,7 @@ impl SubmissionState {
delete_error: None, delete_error: None,
visibility_error: None, visibility_error: None,
sensitive_error: None, sensitive_error: None,
settings,
}) })
} }
@ -613,52 +645,29 @@ impl ViewSubmissionState {
async fn can_view( async fn can_view(
viewer: Option<Uuid>, viewer: Option<Uuid>,
poster: &Profile, can_view_sensitive: bool,
submission: &Submission, submission: &Submission,
state: &State, state: &State,
) -> Result<Option<HttpResponse>, Error> { ) -> Result<Option<HttpResponse>, Error> {
if submission.is_sensitive() { let store = state.profiles.clone();
if viewer.is_none() { let submission = submission.clone();
return Ok(Some(crate::to_404())); let opt = web::block(move || {
} Ok(crate::pagination::submission::can_view(
} viewer,
&submission,
&store.store,
&mut Default::default(),
true,
can_view_sensitive,
)) as Result<Option<()>, Error>
})
.await?;
if poster.login_required() && viewer.is_none() { if opt.is_none() {
return Ok(Some(crate::to_404())); return Ok(Some(crate::to_404()));
}
if poster.local_owner().is_none() && viewer.is_none() {
return Ok(Some(crate::to_404()));
}
let is_self = viewer
.as_ref()
.map(|pid| *pid == poster.id())
.unwrap_or(false);
if submission.published().is_none() && !is_self {
return Ok(Some(crate::to_404()));
}
let is_follower_or_self = if let Some(pid) = viewer {
let is_follower = state
.profiles
.store
.view
.follows
.by_forward(poster.id(), pid)?
.is_some();
is_follower || is_self
} else { } else {
false Ok(None)
};
if submission.is_followers_only() && !is_follower_or_self {
return Ok(Some(crate::to_404()));
} }
Ok(None)
} }
async fn files_for_submission( async fn files_for_submission(
@ -746,6 +755,7 @@ fn submission_nav(
async fn adjacent_submissions( async fn adjacent_submissions(
viewer: Option<Uuid>, viewer: Option<Uuid>,
can_view_sensitive: bool,
submission: &Submission, submission: &Submission,
state: &State, state: &State,
) -> Result<(Option<Uuid>, Option<Uuid>), Error> { ) -> Result<(Option<Uuid>, Option<Uuid>), Error> {
@ -769,6 +779,7 @@ async fn adjacent_submissions(
&inner_store.store, &inner_store.store,
&mut Default::default(), &mut Default::default(),
true, true,
can_view_sensitive,
) )
.map(move |_| id) .map(move |_| id)
}); });
@ -787,6 +798,7 @@ async fn adjacent_submissions(
&store.store, &store.store,
&mut Default::default(), &mut Default::default(),
true, true,
can_view_sensitive,
) )
.map(move |_| id) .map(move |_| id)
}); });
@ -808,6 +820,7 @@ async fn adjacent_submissions(
&inner_store.store, &inner_store.store,
&mut Default::default(), &mut Default::default(),
true, true,
can_view_sensitive,
) )
.map(move |_| id) .map(move |_| id)
}); });
@ -826,6 +839,7 @@ async fn adjacent_submissions(
&store.store, &store.store,
&mut Default::default(), &mut Default::default(),
true, true,
can_view_sensitive,
) )
.map(move |_| id) .map(move |_| id)
}); });
@ -862,12 +876,18 @@ async fn submission_page(
let cache = files_for_submission(&submission, cache, &state).await?; let cache = files_for_submission(&submission, cache, &state).await?;
let cache = files_for_profile(&poster, cache, &state).await?; let cache = files_for_profile(&poster, cache, &state).await?;
if let Some(res) = can_view(viewer, &poster, &submission, &state).await? { let can_view_sensitive = if let Some(viewer) = viewer {
state.settings.for_profile(viewer).await?.sensitive
} else {
false
};
if let Some(res) = can_view(viewer, can_view_sensitive, &submission, &state).await? {
return Ok(res); return Ok(res);
} }
let (next_submission, previous_submission) = let (next_submission, previous_submission) =
adjacent_submissions(viewer, &submission, &state).await?; adjacent_submissions(viewer, can_view_sensitive, &submission, &state).await?;
let (nav, current_file) = submission_nav( let (nav, current_file) = submission_nav(
page.map(|p| p.into_inner()), page.map(|p| p.into_inner()),
@ -969,7 +989,10 @@ async fn create_comment(
let poster_id = submission.profile_id(); let poster_id = submission.profile_id();
let poster = web::block(move || store.store.profiles.by_id(poster_id)?.req()).await?; let poster = web::block(move || store.store.profiles.by_id(poster_id)?.req()).await?;
if let Some(res) = can_view(Some(profile.id()), &poster, &submission, &state).await? { let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
if let Some(res) = can_view(Some(profile.id()), can_view_sensitive, &submission, &state).await?
{
return Ok(res); return Ok(res);
} }
@ -999,7 +1022,7 @@ async fn create_comment(
let cache = files_for_profile(&poster, cache, &state).await?; let cache = files_for_profile(&poster, cache, &state).await?;
let (next_submission, previous_submission) = let (next_submission, previous_submission) =
adjacent_submissions(Some(profile.id()), &submission, &state).await?; adjacent_submissions(Some(profile.id()), can_view_sensitive, &submission, &state).await?;
let (nav, current_file) = submission_nav( let (nav, current_file) = submission_nav(
page.map(|p| p.into_inner()), page.map(|p| p.into_inner()),

View file

@ -30,6 +30,27 @@
@:button_group(&state.buttons(loader)) @:button_group(&state.buttons(loader))
}) })
}) })
@:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="/profiles/update/settings">
@:card_title({
@fl!(loader, "update-settings-heading")
})
@if let Some(error) = &state.settings_error {
@:card_body({
@error
})
}
@:card_body({
@:checkbox("sensitive", &fl!(loader, "sensitive-checkbox"), state.settings.sensitive)
@:checkbox("dark", &fl!(loader, "dark-checkbox"), state.settings.dark)
})
@:card_body({
@:button_group(&[
Button::primary(&fl!(loader, "update-settings-button")),
])
})
</form>
})
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ @:card_title({
@fl!(loader, "update-profile-heading") @fl!(loader, "update-profile-heading")

View file

@ -70,21 +70,23 @@
</form> </form>
}) })
} }
@:card(&Card::full_width().dark(nav_state.dark()), { @if state.settings.sensitive {
<form method="POST" action="@state.sensitive_path()"> @:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ <form method="POST" action="@state.sensitive_path()">
@fl!(loader, "submission-sensitive-heading") @:card_title({
}) @fl!(loader, "submission-sensitive-heading")
@:card_body({ })
@:checkbox("sensitive", &fl!(loader, "submission-sensitive-checkbox"), state.submission.is_sensitive()) @:card_body({
}) @:checkbox("sensitive", &fl!(loader, "submission-sensitive-checkbox"), state.submission.is_sensitive())
@:card_body({ })
@:button_group(&[ @:card_body({
Button::primary(&fl!(loader, "submission-sensitive-button")), @:button_group(&[
]) Button::primary(&fl!(loader, "submission-sensitive-button")),
}) ])
</form> })
}) </form>
})
}
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@state.update_path()"> <form method="POST" action="@state.update_path()">
@:card_title({ @:card_title({

View file

@ -138,6 +138,12 @@ file-num = file {$num}
profile-settings-title = Profile Settings profile-settings-title = Profile Settings
profile-settings-subtitle = Edit {$profileName}'s profile on {site-name} profile-settings-subtitle = Edit {$profileName}'s profile on {site-name}
update-settings-heading = Update Profile Settings
sensitive-checkbox = Show me NSFW content
dark-checkbox = Enable dark mode
update-settings-button = Save
update-profile-heading = Update Profile update-profile-heading = Update Profile
update-bio-heading = Update Bio update-bio-heading = Update Bio
update-bio-description = update-bio-description =