[1.0] API response format for `action` fields
At the moment API responses on main branch look like this (excerpt from /api/v4/community):
{
"community_view": {
"community_actions": {
"community_id": 3,
"person_id": 2,
"followed": "2025-03-24T10:15:47.709074Z",
"follow_state": "Accepted",
"became_moderator": "2025-03-24T10:15:47.705749Z"
},
"instance_actions": null,
"can_mod": true,
...
},
...
}
There are a few problems with this:
- Timestamps for fields like
subscribed,blockedorbecame_moderatorare completely unnecessary. These dont exist in 0.19 and were never requested in any issue. For mod actions the timestamps are stored in the modlog anyway. It is also very unintuitive that clients need to compare with null to find out if a user is moderator or was banned. So these should be changed back to simple booleans. It may even be worth changing the originalsmoosh-tables-togethermigration, as writing bools should be much faster than generating timestamps. - Field names should be simplified to be as before, ie
bannedinstead ofreceived_ban,is_moderatorinstead ofbecame_moderatoretc. - Structs like
instance_actionsare nullable, which makes it impossible to explore the API by browsing the site and observing requests in browser dev tools. This means that client devs need to check the documentation every time, which makes development unnecessarily complicated. We should change it so that if no db rowinstance_actionsexists, a default value is returned with null/false values. - Fields
community_id,person_idetc are unnecessary for clients and can be markedserde(skip), or even completely removed from the Rust structs.
All these changes will make it easier for clients to adopt the new API, and make the migration to 1.0 smoother.
The timestamps are nice to have and don't hurt anything. I imagine some apps will like having them as a hover to show exactly when something was done (IE when exactly did I save this comment?). Its not a good idea to change those back, as we'd only lose potentially useful info.
Field names should be simplified to be as before, ie banned instead of received_ban, is_moderator instead of became_moderator etc.
Not a big issue to me, but I like the current language since it shows that these are actions taken, not always current states.
Structs like instance_actions are nullable, which makes it impossible to explore the API by browsing the site and observing requests in browser dev tools. This means that client devs need to check the documentation every time, which makes development unnecessarily complicated. We should change it so that if no db row instance_actions exists, a default value is returned with null/false values.
They would only have to do this if they incorrectly defined the type any didn't account for optional fields in the API client (or trying to browse the bare JSON objects). I don't like the idea of inserting fake action rows where none exist. If you use any language library (like lemmy-js-client), this is simple to account for : community_view.instance_actions?.received_ban.
I don't know of any language this is going to be a problem for, we already have tons of optionals in the API.
Fields community_id, person_id etc are unnecessary for clients and can be marked serde(skip), or even completely removed from the Rust structs.
I'm fine with skipping those, don't remove them from the structs as they're necessary for reading / inserting oftentimes.
I have been working in https://github.com/MV-GH/LemmyBackwardsCompatibleAPI/tree/lemmy-1.0.0 to add 1.0.0 support.
I have gotten pretty far but currently I am struggling with the "Actions" datatypes mapping. I'll make more detailed report later, dunno if you prefer it to be here or in separate issue. Not exactly as the above issues, It's mostly with trying to understand what the current fields mean.
Small example of what I mean:
CommentView 0.19
data class CommentView(
val comment: Comment,
val creator: Person,
val post: Post,
val community: Community,
val counts: CommentAggregates,
val creator_banned_from_community: Boolean,
val banned_from_community: Boolean,
val creator_is_moderator: Boolean,
val creator_is_admin: Boolean,
val subscribed: SubscribedType /* "Subscribed" | "NotSubscribed" | "Pending" */,
val saved: Boolean,
val creator_blocked: Boolean,
val my_vote: Int = 0,
)
CommentView 1.0.0
data class CommentView(
val comment: Comment,
val creator: Person,
val post: Post,
val community: Community,
val community_actions: CommunityActions? = null,
val comment_actions: CommentActions? = null,
val person_actions: PersonActions? = null,
val instance_actions: InstanceActions? = null,
val creator_home_instance_actions: InstanceActions? = null,
val creator_local_instance_actions: InstanceActions? = null,
val creator_community_actions: CommunityActions? = null,
val creator_is_admin: Boolean,
val post_tags: TagsView,
val can_mod: Boolean,
val creator_banned: Boolean,
)
person_actions: PersonActions 1.0.0
data class PersonActions(
val target_id: PersonId,
val person_id: PersonId,
val blocked: String? = null,
)
Who is target_id here? The creator? If so is this block than equivalent of previous creator_blocked field?
Then there is also InstanceActions which hold if the instance is blocked, or user is banned? For myself (on every post), creator home instance, creator local instance.
From user perspective not sure what I am with home actions? Those should be federated to the local actions? So should be the same, not sure what I would show from this in the UI. If anything just if the user is banned from the (local) instance? Which was the previous banned field from creator: Person ?
Then there is also things like creator_community_actions so everyone can see if they follow certain community
internal data class CommunityActions(
val community_id: CommunityId,
val person_id: PersonId,
val followed: String? = null,
val follow_state: String? /* "Accepted" | "Pending" | "ApprovalRequired" */ = null,
val follow_approver_id: PersonId? = null,
val blocked: String? = null,
val became_moderator: String? = null,
val received_ban: String? = null,
val ban_expires: String? = null,
)
Not sure if follow_approver_id should be exposed either.
Field names should be simplified to be as before, ie banned instead of received_ban, is_moderator instead of became_moderator etc.
Unrelated, an idea: use at as suffix for all timestamped fields.
For example:
- followed -> followed_at
- banned -> received_ban -> banned_at
- is_moderator -> became_moderator -> moderator_at
I ve been using this to denote timestamps in my DB schemas. makes it pretty clear when ur dealing with TS or a bool. :)
Who is target_id here? The creator? If so is this block than equivalent of previous creator_blocked field?
Yes that's the creator_id. Yep it is.
Then there is also InstanceActions which hold if the instance is blocked, or user is banned? For myself (on every post), creator home instance, creator local instance. From user perspective not sure what I am with home actions? Those should be federated to the local actions? So should be the same, not sure what I would show from this in the UI. If anything just if the user is banned from the (local) instance? Which was the previous banned field from creator: Person ?
Yes its mainly for bans. You can just use creator_banned for now, which combines the data for a local instance or a home instance ban. In the future we can differentiate instance bans made from their instance vs ours, but its not totally necessary IMO.
Which was the previous banned field from creator: Person ?
Correct. Instance bans are now in a different table, and they're federated out.
Not sure if follow_approver_id should be exposed either.
@Nutomic could we skip serializing that? Or is it necessary.
Unrelated, an idea: use at as suffix for all timestamped fields.
I wouldn't be opposed, although that'd be changing a LOT of DB fields. Copy-paste that into a new issue if you would so we can discuss.
The different *Action structs come from https://github.com/LemmyNet/lemmy/pull/4459. Basically instead of separate tables post_like, post_hide, post_saved etc which all have similar data, these are merged into a single post_actions table. Same principle for community_actions etc. Having multiple instance_action fields is described in https://github.com/LemmyNet/lemmy/pull/4571 (and implemented in https://github.com/LemmyNet/lemmy/pull/5515).
I made a PR to exclude some of these fields from the api (https://github.com/LemmyNet/lemmy/pull/5739).
Here is one way to simplify the api: Instead of returning CommunityActions etc exactly like they are stored in the api, we could lift out the individual fields and move them directly into the respective view like PostView.creator_banned_from_community. This way responses would be almost identical to 0.19.
Edit: Partially implemented in https://github.com/LemmyNet/lemmy/pull/5784
We moved away from doing that for good reason, it was cumbersome to maintain all these custom fields, especially when they need to be replicated across all the combined views now.
Much easier and more maintainable to serve the bare actions objects. We shouldn't go back to the old way.
This should be resolved now as creator_* fields are removed in https://github.com/LemmyNet/lemmy/pull/5808. Other action fields make sense.