-
-
Notifications
You must be signed in to change notification settings - Fork 883
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
Reorganize api endpoints (fixes #2022) #5216
base: main
Are you sure you want to change the base?
Conversation
df193dc
to
9072bb3
Compare
src/api_routes_http.rs
Outdated
) | ||
.route("/change_password", web::put().to(change_password)) | ||
.route("/totp/generate", web::post().to(generate_totp_secret)) | ||
.route("/totp/update", web::post().to(update_totp)), |
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 to organize these so that they share a single scope with the same rate limit.
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.
At first I was confused about the account
vs person/user
, but it makes sense to me now. account is things I do to my account, while person/user should be things anyone can do to it (currently only reading).
Suggestions below.
Additional context: #4428
src/api_routes_http.rs
Outdated
.wrap(rate_limit.import_user_settings()) | ||
.route(web::get().to(export_settings)) | ||
.route(web::post().to(import_settings)), |
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 don't like that import is just a POST
to account/export. Should probably be separate account/export
and account/import
endpoints to be clearer.
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.
It needs to have a common path so that the rate limit can apply to both. I changed it to GET /account/settings/export
and POST /account/settings/import
now.
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.
You could just override the rate limit for those specifically. export / import are actions, not settings, but I don't feel too strongly about that either way.
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.
Then it requires two separate blocks like this which is too verbose:
.service(
web::resource("/user/export_settings")
.wrap(rate_limit.export_user_settings())
.route(web::get().to(export_settings)),
) .service(
web::resource("/user/import_settings")
.wrap(rate_limit.import_user_settings())
.route(web::post().to(import_settings)),
)
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.
That's pretty minor imo. They could be put under a account/takeout
or something tho, especially since we'll eventually need to add a full data export too (even though that won't be importable anywhere).
src/api_routes_http.rs
Outdated
.route( | ||
"/mark_all_as_read", | ||
"/mention/mark_all_as_read", | ||
web::post().to(mark_all_notifications_read), | ||
) | ||
.route("/save_user_settings", web::put().to(save_user_settings)) |
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.
Maybe just save
? I dunno.
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.
Made it /account/settings/save
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.
This one too, burying it under settings doesn't seem right, when all the other actions aren't under settings either.
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.
Its specifically about settings so it makes sense (same place as import/export). And buried is the wrong word, its still easy to find in docs etc.
src/api_routes_http.rs
Outdated
@@ -161,33 +161,25 @@ use lemmy_utils::rate_limit::RateLimitCell; | |||
|
|||
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { | |||
cfg.service( | |||
web::scope("/api/v3") | |||
web::scope("/api/v4") | |||
.wrap(rate_limit.message()) |
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.
Moved the message rate limit here so it doesnt have to be applied separately to many routes. This means a route like POST /api/v3/comment will have both message and post rate limits applied, but that shouldnt be a problem.
(moved here so that rate limit doesnt apply to image or federation endpoints)
Also moved SiteView.my_user to a separate endpoint ( |
crates/api_crud/src/user/my_user.rs
Outdated
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 like this.
Beyond the bit of reorganizing done here, I think it would be good to handle fetching individual resources by including the id in the route instead of the POST body, e.g. fetching a single comment would be Besides that, I believe it could be worth looking into making some datatypes specific for returning as responses. We already have a few types like that, but many of them return views (e.g. There's probably more to be said, but these two points are the biggest on my mind so far. |
All the params for GET requests are currently like:
It'd probably require a bunch of strange manipulations to parse the required params into a distinct part of the route like
I'd be perfectly good with creating slimmer versions of CommentView and PostView, that are used in different places, as long as they are predictably and strongly-typed with different names, and we're sure that these slimmer versions aren't going to be missing anything that clients need. Feel free to open an issue for that, as well as the redundant / unecessary columns in certain places. |
Besides that, I'll gladly pour some time into tweaking the API design. It's something I've been interested in for awhile. Sometime in the next month I was thinking of opening a PR similar to this with some annotations as to why I made the choices I did. |
Im not sure its worth changing the way request parameters are passed, because that would require a lot of work for client devs to make things compatible, for little or no benefit. With this PR all thats needed is changing a few paths while parameters are the same. Also there are cases like GetPost where the same item can be fetched in different ways. But in general, this PR is only one part of the changes for API v4. If there are other changes you want to include, make a PR so we can discuss it (best change only a small part as example first, and then make full changes later once we agree that it makes sense). |
Updated with routes for api v3 and api v4. For api v3 I added the On the other hand I removed endpoints for unreleased features from api v3, so that client devs have an incentive to upgrade. This includes the new endpoints for taglines and emojis which were removed from /api/v3/site, so it may be necessary to add that back in. Edit: Strangely the api tests fail with dependency |
It must be failing at register user for some reason. The Also I'm seeing a few
Its probably fine to keep the v3 API routes, as long as we say that we will not support them in the future. |
139a5e7
to
8ac9e2b
Compare
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 need some feedback from other ppl on these too.
@@ -448,14 +448,10 @@ pub struct GetSiteResponse { | |||
pub site_view: SiteView, | |||
pub admins: Vec<PersonView>, | |||
pub version: String, | |||
#[cfg_attr(feature = "full", ts(optional))] | |||
#[cfg_attr(feature = "full", ts(skip))] | |||
pub my_user: Option<MyUserInfo>, |
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.
This should just be removed now right? Or just add deprecated like the other fields, and always have it be None in the code.
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.
This is used for get_site_v3 below.
use std::sync::LazyLock; | ||
|
||
#[tracing::instrument(skip(context))] | ||
pub async fn get_site( | ||
pub async fn get_site_v3( |
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 backward-compatibility hacks (and serving up api/v3 completely) should really be handled in client libraries IMO, not piling them on top of each other in the lemmy codebase.
Not only is it going to get increasingly complicated, but we're going to be on the hook to support both of these alongside each other, with all the object changes. That might be okay for a bigger dev team, but not with our current dev resources.
IMO since this is a breaking changes release, we should just increment the api version, but don't support the old one.
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.
If we remove the v3 api directly in 0.20, it means that all clients and frontends will be completely broken until the devs rewrite their code for v4, which could take months. Even just dropping GetSiteResponse.my_user
would probably break all authentication functionality, or even lead to crashes.
The backwards compatibility is really easy to implement in this case, and it prevents a lot of problems for users. Lemmy has a huge userbase now which relies on a certain level of stability, so even in major releases we cannot break everything just to save a little bit of maintenance work.
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 dropping v3 support should wait for whenever we release Lemmy 1.0.0. A breaking change of that size corresponds well with what's communicated in the version number: a major version update that breaks backwards compatibility.
scope("/account/settings") | ||
.wrap(rate_limit.import_user_settings()) | ||
.route("/export", get().to(export_settings)) | ||
.route("/import", post().to(import_settings)), |
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 like the idea of using a heading name like takeout
for this. Eventually we'll need to add #4540 here anyway.
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 dont need to implement that in the backend, and then that would be under a different endpoint.
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.
IMO that one does need to be in the back-end, to avoid ppl having to page through their comments.
src/api_routes_v4.rs
Outdated
"/mention/mark_all_as_read", | ||
post().to(mark_all_notifications_read), | ||
) | ||
.route("/settings/save", put().to(save_user_settings)) |
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 still think this should just be /save
, but all of this needs feedback from others also.
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.
/account/save
? Its not saving the account but the settings.
e6d32a2
to
d14f8dc
Compare
Tests are all passing now. I also moved the different user block endpoints to similar routes:
Also changed |
There are major API changes here, and since this needsto be fairly future proof, we need feedback from @phiresky @SleeplessOne1917 @dullbananas |
The general way things are grouped make sense. As far as how the endpoints are laid out, I don't think it needs to be blocked, but I didn't comb through the rest of the code much yet. I'll defer to the others you tagged for if this needs to be blocked. I plan on making a draft PR with some more breaking changes some time mid December once the semester for my masters degree is over. |
src/api_routes_v4.rs
Outdated
.route("/delete", post().to(delete_post)) | ||
.route("/remove", post().to(remove_post)) | ||
.route("/mark_as_read", post().to(mark_post_as_read)) | ||
.route("/mark_many_as_read", post().to(mark_posts_as_read)) |
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.
Do something like this. If we later want to create a function that creates both bulk and non-bulk routes under the given path ("/mark_as_read" in this case), this will allow it to be done more cleanly.
.route("/mark_many_as_read", post().to(mark_posts_as_read)) | |
.route("/mark_as_read/many", post().to(mark_posts_as_read)) |
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.
Done, also changed /account/mention/mark_all_as_read
to /account/mention/mark_as_read/all
.route("/delete", post().to(delete_post)) | ||
.route("/remove", post().to(remove_post)) |
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 any idea for better distinguishing what's currently called "delete" and "remove"? Changing it later would be less convenient.
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.
Could do something like /user_delete
and /mod_remove
but it doesnt sound good.
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 delete (for content you created) vs remove (for content others created) is probably fine.
I thought about whether we should move all of the mod related actions to a mod subheading, but its probably better to keep them at the item level like they are now (/comment/remove
), or else we'd just have to replicate the same things but under a different heading (/mod/comment/remove
)
.route("/report", post().to(create_pm_report)) | ||
.route("/report/resolve", put().to(resolve_pm_report)) | ||
.route("/report/list", get().to(list_pm_reports)), | ||
) |
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.
Consider moving thinks like distinguish and lock to the update enpoints, and moving things like save and like to new "actions" update endpoints.
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.
You mean /comment/update/distinguish
? Not sure that makes sense, because edits use /comment
directly and there is no /comment/update
. And /comment/action/
sounds too generic, it could cover anything.
src/api_routes_v4.rs
Outdated
) | ||
.service( | ||
scope("/account") | ||
.route("/my_user", get().to(get_my_user)) |
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.
This would be less confusing
.route("/my_user", get().to(get_my_user)) | |
.route("", get().to(get_my_user)) |
"/registration_application", | ||
get().to(get_registration_application), | ||
) | ||
.route("/list_all_media", get().to(list_all_media)) |
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.
Both "/account/list_media" and "/admin/list_all_media" should be moved to something like "/media/list"
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.
Then how to distinguish between admin and user endpoints? They need to be separate because admins may also want to view their personal uploads.
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.
Either one works for me, but I think I like the /admin/list_all_media
a little better than /media/list_all
Sounds like we can merge it then. For additional changes we can make more pull requests. |
I'm good with that. There's no reason to keep this a long standing PR, and we can open up things to fix specific issues in the future. |
@dullbananas Still needs your approval |
These changes are mainly to discuss what the new API v4 routes should look like. Once we come to an agreement, I will adjust it so that the existing API v3 remains usable with the same paths.