Skip to content
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

Image api rework #5260

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

Image api rework #5260

wants to merge 16 commits into from

Conversation

Nutomic
Copy link
Member

@Nutomic Nutomic commented Dec 13, 2024

  • Move all image related endpoints under /api/v4/image/
  • Image parameters and responses are now in crates/api_common/src/image.rs, no more exposing of pictrs data structures
  • Reorganize code in crates/routes/images
  • Only use adapt_request() for image upload, not for simple things like delete or health check
  • New endpoints POST /api/v4/account/avatar which directly takes image upload, instead of setting avatar url via /api/v4/account/settings/save
  • Also POST /api/v4/account/banner, POST /api/v4/community/icon, POST /api/v4/community/banner, POST /api/v4/site/icon and POST /api/v4/site/banner

use lemmy_utils::rate_limit::RateLimitCell;

// Deprecated, use api v4 instead.
// When removing api v3, we also need to rewrite all links in database with
// `/api/v3/image_proxy` to use `/api/v4/image_proxy` instead.
// `/api/v3/image_proxy` to use `/api/v4/image/proxy` instead.
pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation was changed in this file, use "hide whitespace" in github to review it.

@Nutomic Nutomic marked this pull request as ready for review December 18, 2024 10:58
@Nutomic
Copy link
Member Author

Nutomic commented Dec 18, 2024

Ready to review.

Copy link
Member

@dessalines dessalines left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job, adding these as API actions is going to be much better.

  • We still need a way to clear images. I'd add some POST or DELETE endpoints, that just delete the images and return that item. IE POST /site/icon/delete, /community/banner/delete...
    • Speaking of which, I see that this PR is the first one that adds HTTP DELETE. Maybe we should use that instead of POST for all our delete API actions.
  • Banners can't be square. So whether that means adding both a max height and max width, or just unfortunately not being able to resize them.
  • We're gonna need a DB migration to fix the history of all proxied images. So unfortunately that means doing a DB replace for comment.content, and post.body, where local = true. I believe the regular image endpoints stayed the same, so no need to correct those.

max_banner_size: 512
# Prevent users from uploading images for posts or embedding in markdown. Avatars, icons and
# banners can still be uploaded.
image_upload_disabled: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to run scripts/./update_config_defaults.sh

delete_old_image(&local_user_view.person.banner, &context).await?;

let form = PersonUpdateForm {
banner: Some(Some(image.image_url.into())),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So how are we clearing these now?

You removed the code that sent an empty string to be able to set Some(None) for these fields, so postgres can clear / unset an image. You need to have some way to be able to do this now.

@@ -46,14 +44,6 @@ pub async fn save_user_settings(
.as_deref(),
);

let avatar = diesel_url_update(data.avatar.as_deref())?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By removing this, we don't have a way to clear / unset images, see comment above.


// Pictrs cannot use proxy
#[allow(clippy::expect_used)]
pub(super) static PICTRS_CLIENT: LazyLock<ClientWithMiddleware> = LazyLock::new(|| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scares me, using a global static for a client. We should be trying to avoid these as much as possible, and instantiating a single client in the context and passing that around explicitly.

/// Otherwise we have to use crop, or use max_width/max_height which throws error
/// if image is larger.
/// TODO: whats a sensible default here?
#[default(512)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Banners should never be square, so we probably should do max width + max height.

I found some banner sizes for current websites here.


pub async fn upload_community_icon(
req: HttpRequest,
query: Query<CommunityIdQuery>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -18,7 +18,7 @@ pub fn markdown_rewrite_image_links(mut src: String) -> (String, Vec<Url>) {
// If link points to remote domain, replace with proxied link
if parsed.domain() != Some(&SETTINGS.hostname) {
let mut proxied = format!(
"{}/api/v4/image_proxy?url={}",
"{}/api/v4/image/proxy?url={}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be the right endpoint for new ones, but what about for all the past post bodies and comments? This is probably going to need a DB migration that fixes all the past ones, at least for our local images.

Comment on lines +415 to +416
.route("/proxy", get().to(image_proxy))
.route("/{filename}", get().to(get_image))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the filename one is the same, but the /proxy is new, correct? That means we're going to need to do a DB migration to correct all the past ones, or we're going to have broken proxied images.

@Nothing4You Nothing4You self-requested a review December 19, 2024 22:31
@Nothing4You
Copy link
Collaborator

is there a reason to expose the pict-rs delete token to the user?

i guess this was historically exposed to the user during upload before it got used in the media listing, but there isn't really any need to keep that, now that we have the media listing.
instead, the deletion api should probably just accept the image url itself and fetch the delete token from the user upload table, which can be combined with verifying that either the user is an admin or the user is the uploader.
for older uploads, in the unlikely case that someone recorded deletion tokens prior to lemmy recording which user the upload is associated with, a compatibility api could exist that allows deletion when the token is provided and the upload does not exist in the table.

Copy link
Collaborator

@dullbananas dullbananas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For handling old image proxy links, I would prefer keeping both the old and new image proxy api endpoints permanently. Replacing old links in markdown content is slow, complicated, error prone, and might break a few image links from outside of Lemmy.

Comment on lines +27 to +30
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.private_instance && local_user_view.is_none() {
return Ok(HttpResponse::Unauthorized().finish());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimization

Suggested change
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.private_instance && local_user_view.is_none() {
return Ok(HttpResponse::Unauthorized().finish());
}
if local_user_view.is_none() {
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.private_instance {
return Ok(HttpResponse::Unauthorized().finish());
}
}

Comment on lines +127 to +131
pub(super) fn file_type(file_type: Option<String>, name: &str) -> String {
file_type
.clone()
.unwrap_or_else(|| name.split('.').last().unwrap_or("jpg").to_string())
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally sure yet, but the calls to clone and unwrap_or_else probably shouldn't be in this function's body. The meaning of file_type.clone.unwrap_or_else(|| file_type(name)) could be more intuitive than file_type(file_type, name)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants