-
-
Notifications
You must be signed in to change notification settings - Fork 884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow empty string to clear URL-type DB fields. #4780
Conversation
dessalines
commented
Jun 5, 2024
- To address difficulties with clearing URL-type fields like avatars, banners, site icons, this PR turns the URL type form fields into strings.
- This allows an empty string to be used as a "clear data", as in the case with the regular text form fields.
- Also includes various cleanups.
- Fixes [Bug]: Site logo disappearing #4777
- Context: Editing a post body field will not save an empty string #2287
- To address difficulties with clearing URL-type fields like avatars, banners, site icons, this PR turns the URL type form fields into strings. - This allows an empty string to be used as a "clear data", as in the case with the regular text form fields. - Also includes various cleanups. - Fixes #4777 - Context: #2287
max_length_check(body, BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?; | ||
}; | ||
} | ||
pub fn is_valid_body_field(body: &str, post: bool) -> LemmyResult<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a lot of if let Some(...
in the API code, so I've removed a few of these internal if let wrappers, as they're pretty pointless. Makes sense to just work with the data directly in a lot of cases.
@@ -350,12 +340,11 @@ pub fn build_url_str_without_scheme(url_str: &str) -> LemmyResult<String> { | |||
} | |||
|
|||
#[cfg(test)] | |||
#[allow(clippy::unwrap_used)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing really important below, just removing the unwraps in favor of using LemmyResult
in the tests.
crates/api_crud/src/site/update.rs
Outdated
&process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?, | ||
); | ||
|
||
let icon = diesel_url_update(&data.icon)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The heart of it is here. We transform these url-type ones into proper diesel forms for an update to an optional url column (IE Option<Option<DbUrl>>
), before doing later actions to it.
@@ -198,4 +199,14 @@ test("Set a new avatar, old avatar is deleted", async () => { | |||
// make sure only the new avatar is kept | |||
const listMediaRes3 = await alphaImage.listMedia(); | |||
expect(listMediaRes3.images.length).toBe(1); | |||
|
|||
// Now try to save a user settings, with the icon missing, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a test here to make sure that it isn't clearing the avatar, when that field is missing.
old_image: &Option<DbUrl>, | ||
context: &Data<LemmyContext>, | ||
) -> LemmyResult<()> { | ||
if let (Some(new_image), Some(old_image)) = (new_image, old_image) { | ||
if let (Some(Some(new_image)), Some(old_image)) = (new_image, old_image) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the following should work:
if let (Some(Some(new_image)), Some(old_image)) = (new_image, old_image) { | |
if let Some((new_image, old_image)) = new_image.flatten().zip(old_image) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might work, but seems less clear to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair. Personally, I find this a bit easier than needing to parse nested Some
s.
@@ -1004,26 +1004,25 @@ pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> Lemmy | |||
} | |||
|
|||
pub async fn proxy_image_link_opt_api( | |||
link: &Option<String>, | |||
link: Option<Option<DbUrl>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything preventing us from using a single layer of Option
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These ones I left as helper functions, because of the internal async call, and not being able to use async map. The one right below this uses the single option version.
@@ -340,15 +340,15 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Lemm | |||
|
|||
/// When adding a new avatar, banner or similar image, delete the old one. | |||
pub async fn replace_image( | |||
new_image: &Option<String>, | |||
new_image: &Option<Option<DbUrl>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as here.
context: &LemmyContext, | ||
) -> LemmyResult<Option<Option<DbUrl>>> { | ||
proxy_image_link_api(link, context).await.map(Some) | ||
if let Some(Some(link)) = link { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can call flatten
on link
if you're stuck using 2 levels of Option
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried, but at least for this one, it would've forced me to use a clone and deref with it, in this case its cleaner to just do Some(Some
.
check_slurs(name, &slur_regex)?; | ||
} | ||
|
||
if let Some(Some(body)) = &body { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can call flatten
on body
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried, but in all these cases it forces me to do a .clone()
, in order to not move the value of body, and .flatten()
doesn't work on refs of options for some reason.
if let Some(Some(alt_text)) = &alt_text { | ||
is_valid_alt_text_field(alt_text)?; | ||
} | ||
|
||
if let Some(Some(url)) = &url { | ||
is_url_blocked(url, &url_blocklist)?; | ||
check_url_scheme(url)?; | ||
} | ||
|
||
if let Some(Some(custom_thumbnail)) = &custom_thumbnail { | ||
check_url_scheme(custom_thumbnail)?; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flatten
crates/db_schema/src/utils.rs
Outdated
pub fn diesel_option_overwrite(opt: Option<String>) -> Option<Option<String>> { | ||
match opt { | ||
/// Takes an API text input, and converts it to an optional diesel DB update. | ||
pub fn diesel_string_update(opt: &Option<String>) -> Option<Option<String>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably be taking an Option<&String>
, maybe even Option<&str>
. See here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that at first, but it forces you to add a lot of deref calls. This one uses the least amount of cloning and mutating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does calling as_ref
on the param at the site of the function call work for you? It should let you turn, e.g., an &Option<String>
to Option<&String>
. Docs for reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably won't make too big a difference, but I've now updated it. Does add a lot of deref calls unfortunately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should at least prevent us from needing to clone and pass owned objects around as much, which I think is a win.
I know I've asked the question before, but what is with all of the |
If I understand correctly, |
crates/db_schema/src/utils.rs
Outdated
match opt.as_ref().map(String::as_str) { | ||
/// Takes an optional API URL-type input, and converts it to an optional diesel DB update. | ||
/// Also cleans the url params. | ||
pub fn diesel_url_update(opt: &Option<String>) -> LemmyResult<Option<Option<DbUrl>>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as here.
I suppose that makes sense. Is that how diesel usually handles this sort of thing, or is that idiosyncratic to our codebase? |
That's correct. I made a table for this somewhere but I can't find it, for update type (create or update), column type (nullable vs not null), and default type (has default or no default value). But the main outlier case, is that if its a nullable DB column update, then you need to use |