Followers API reference
Follow requests, acceptance and removal between users. The follow graph is visible only to its owner and their accepted followers. See Sharing, privacy and followers.
Service
Application logic for follower relationships.
The privacy-aware read surface (whether a requester may view a target's follower / following graph) and the follow / accept / unfollow writes. Access decisions live here, not in the router, so the same rule applies however this module is reached.
A user's follow graph is visible only to the user and their accepted followers. Endurain has no public-profile concept: profiles are never public, and only individual activities can be shared (via server-level public links), so no "public" tier applies to the follow graph.
This is the module's own application layer. What other modules may consume
lives in :mod:modules.followers.integration_service.
accept_follow_request
accept_follow_request(requester_user_id, follower_id, db)
Accept a pending follow request and publish follower.accepted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requester_user_id
|
int
|
The authenticated user accepting the request. |
required |
follower_id
|
int
|
The user whose pending request is being accepted. |
required |
db
|
Session
|
Database session. |
required |
Returns:
| Type | Description |
|---|---|
FollowRelationshipRead
|
The accepted relationship, read back after the flush. |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
When no pending request from that user exists. |
Source code in backend/app/modules/followers/service.py
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
delete_relationship
delete_relationship(followee_id, follower_id, requester_user_id, db)
Delete a follow relationship on behalf of either party.
One operation serves unfollow and remove-follower: the row is the same and the direction is explicit in the arguments, so the only difference is which of the two parties asked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
followee_id
|
int
|
The user being followed. |
required |
follower_id
|
int
|
The user doing the following. |
required |
requester_user_id
|
int
|
The authenticated caller, who must be one of the two. |
required |
db
|
Session
|
Database session. |
required |
Raises:
| Type | Description |
|---|---|
AccessConcealedError
|
When the caller is not part of the relationship; answered as a missing relationship so it does not confirm one exists. |
Source code in backend/app/modules/followers/service.py
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
follow_user
follow_user(requester_user_id, target_user_id, db)
Create a follow request and publish follower.requested.
The CRUD row write is the source of truth; the notification is produced by the
follower.requested subscriber reacting to the published event, so the
request succeeds even if notification delivery later fails.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requester_user_id
|
int
|
The authenticated user requesting to follow. |
required |
target_user_id
|
int
|
The user to follow. |
required |
db
|
Session
|
Database session. |
required |
Returns:
| Type | Description |
|---|---|
FollowRelationshipRead
|
The newly created follow relationship as a DTO. |
Raises:
| Type | Description |
|---|---|
InvalidInputError
|
If the requester targets themselves. |
ConflictError
|
If the relationship already exists. |
NotFoundError
|
If the target user does not exist. |
Source code in backend/app/modules/followers/service.py
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | |
get_user_relationship
get_user_relationship(other_user_id, requester_user_id, db)
Return the authenticated user's relationship with another user (both directions).
Only ever reports relationships the requester is part of — their outgoing
follow of other_user_id and other_user_id's incoming follow of them —
so there is no way to probe arbitrary pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other_user_id
|
int
|
The other user in the relationship. |
required |
requester_user_id
|
int
|
The authenticated requester. |
required |
db
|
Session
|
Database session. |
required |
Returns:
| Type | Description |
|---|---|
RelationshipRead
|
The requester's outgoing and incoming follow relationships with the other |
RelationshipRead
|
user (either may be None). |
Source code in backend/app/modules/followers/service.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | |
list_followers
list_followers(target_user_id, requester_user_id, db, *, page_number=1, num_records=core_pagination.DEFAULT_NUM_RECORDS, accepted_only=False)
Return one page of the target's followers with the matching total.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_user_id
|
int
|
The user whose followers to list. |
required |
requester_user_id
|
int
|
The authenticated requester. |
required |
db
|
Session
|
Database session. |
required |
page_number
|
int
|
1-based page number. |
1
|
num_records
|
int
|
Page size. |
DEFAULT_NUM_RECORDS
|
accepted_only
|
bool
|
Exclude pending follow requests when True. |
False
|
Returns:
| Type | Description |
|---|---|
FollowRelationshipPage
|
The page envelope. |
FollowRelationshipPage
|
filter, not just this page. |
Raises:
| Type | Description |
|---|---|
AccessConcealedError
|
When the requester may not view this network; answered like a missing user. |
Source code in backend/app/modules/followers/service.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
list_following
list_following(target_user_id, requester_user_id, db, *, page_number=1, num_records=core_pagination.DEFAULT_NUM_RECORDS, accepted_only=False)
Return one page of who the target follows with the matching total.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_user_id
|
int
|
The user whose following list to return. |
required |
requester_user_id
|
int
|
The authenticated requester. |
required |
db
|
Session
|
Database session. |
required |
page_number
|
int
|
1-based page number. |
1
|
num_records
|
int
|
Page size. |
DEFAULT_NUM_RECORDS
|
accepted_only
|
bool
|
Exclude pending follow requests when True. |
False
|
Returns:
| Type | Description |
|---|---|
FollowRelationshipPage
|
The page envelope. |
FollowRelationshipPage
|
filter, not just this page. |
Raises:
| Type | Description |
|---|---|
AccessConcealedError
|
When the requester may not view this network; answered like a missing user. |
Source code in backend/app/modules/followers/service.py
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
list_pending_requests
list_pending_requests(requester_user_id, db, *, page_number=1, num_records=core_pagination.DEFAULT_NUM_RECORDS)
Return one page of follow requests awaiting the caller's decision.
Always scoped to the caller: a pending request is private to the two parties,
so there is no target_user_id to pass and therefore nothing to probe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requester_user_id
|
int
|
The authenticated caller. |
required |
db
|
Session
|
Database session. |
required |
page_number
|
int
|
1-based page number. |
1
|
num_records
|
int
|
Page size. |
DEFAULT_NUM_RECORDS
|
Returns:
| Type | Description |
|---|---|
FollowRelationshipPage
|
The page envelope. |
Source code in backend/app/modules/followers/service.py
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | |
reject_follow_request
reject_follow_request(requester_user_id, follower_id, db)
Decline a pending follow request addressed to the caller.
Distinct from :func:delete_relationship, which severs an already-accepted
relationship. Both delete the same row, but only one of them is a decision
the requester never had granted — so this one is scoped to PENDING, and
an accepted follower is removed through the relationship route instead of
silently through here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requester_user_id
|
int
|
The authenticated user declining the request. |
required |
follower_id
|
int
|
The user whose pending request is declined. |
required |
db
|
Session
|
Database session. |
required |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
When no pending request from that user exists. |
Source code in backend/app/modules/followers/service.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | |
Schemas
Pydantic schemas for follower relationships.
FollowRelationshipRead
Bases: BaseModel
Serialized representation of a follow relationship.
Attributes:
| Name | Type | Description |
|---|---|---|
follower_id |
StrictInt
|
The user who follows (the requester). |
followee_id |
StrictInt
|
The user being followed. |
status |
FollowStatus
|
Whether the request is pending or accepted. |
Source code in backend/app/modules/followers/schema.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
FollowRequestAccept
Bases: BaseModel
The body of an accept applied to a pending follow request.
Named for the single transition it expresses. accepted is the only valid
one: declining deletes the request rather than parking it in a rejected
state, so a later request from the same user is a fresh decision instead of
hitting a tombstone. The field is still required so the accept is explicit in
the payload rather than implied by the route. Unknown fields are rejected
(extra="forbid") rather than silently ignored, matching every other
request-body schema in the activities/followers template modules.
Attributes:
| Name | Type | Description |
|---|---|---|
status |
FollowStatus
|
The target status; only |
Source code in backend/app/modules/followers/schema.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
RelationshipRead
Bases: BaseModel
The authenticated user's relationship with another user, both directions.
Attributes:
| Name | Type | Description |
|---|---|---|
outgoing |
FollowRelationshipRead | None
|
The authenticated user's follow of the other user, if any. |
incoming |
FollowRelationshipRead | None
|
The other user's follow of the authenticated user, if any. |
Source code in backend/app/modules/followers/schema.py
63 64 65 66 67 68 69 70 71 72 73 74 | |
Events
Domain event channels owned by the followers module.
Channel names (event_type values) are owned by the publishing domain, not the
platform substrate. The follow service publishes the facts below; the follower
notification subscriber reacts by subscribing to the same constants, so producer
and subscriber cannot drift on the string. Convention: <domain>.<fact>, past
tense.
FollowerAcceptedPayload
Bases: VersionedPayload
Validated payload for the follower.accepted event.
Attributes:
| Name | Type | Description |
|---|---|---|
accepter_user_id |
int
|
The user who accepted the request. |
requester_user_id |
int
|
The original requester (who is notified). |
Source code in backend/app/modules/followers/events.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 | |
FollowerRequestedPayload
Bases: VersionedPayload
Validated payload for the follower.requested event.
Subscribers validate against this schema so a malformed payload raises at the boundary — with the offending fields named — instead of being silently dropped by ad-hoc type checks.
Attributes:
| Name | Type | Description |
|---|---|---|
requester_user_id |
int
|
The user who requested to follow. |
target_user_id |
int
|
The user being followed (who is notified). |
Source code in backend/app/modules/followers/events.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |