Enable pagination for notification list
This commit is contained in:
parent
aea6db5acb
commit
f1f3829b8d
4 changed files with 118 additions and 6 deletions
|
@ -238,6 +238,33 @@ paths:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Account'
|
||||
/api/v1/notifications:
|
||||
get:
|
||||
summary: Notifications concerning the user.
|
||||
parameters:
|
||||
- name: max_id
|
||||
in: query
|
||||
description: Return results older than this ID.
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
- name: limit
|
||||
in: query
|
||||
description: Maximum number of results to return.
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: Notification list
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Notification'
|
||||
/api/v1/statuses:
|
||||
post:
|
||||
summary: Create new post.
|
||||
|
@ -446,6 +473,31 @@ components:
|
|||
description: Ethereum wallet address.
|
||||
type: string
|
||||
example: '0xd8da6bf...'
|
||||
Notification:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: The id of the notification in the database.
|
||||
type: string
|
||||
type:
|
||||
description: The type of event that resulted in the notification.
|
||||
type: string
|
||||
enum:
|
||||
- follow
|
||||
- follow_request
|
||||
- reply
|
||||
- favourite
|
||||
- mention
|
||||
- reblog
|
||||
example: reply
|
||||
created_at:
|
||||
description: The timestamp of the notification.
|
||||
type: string
|
||||
format: dateTime
|
||||
account:
|
||||
$ref: '#/components/schemas/Account'
|
||||
status:
|
||||
$ref: '#/components/schemas/Account'
|
||||
Relationship:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::mastodon_api::accounts::types::Account;
|
||||
use crate::mastodon_api::statuses::types::Status;
|
||||
use crate::models::notifications::types::{EventType, Notification};
|
||||
|
||||
fn default_page_size() -> u8 { 20 }
|
||||
|
||||
/// https://docs.joinmastodon.org/methods/notifications/
|
||||
#[derive(Deserialize)]
|
||||
pub struct NotificationQueryParams {
|
||||
pub max_id: Option<i32>,
|
||||
|
||||
#[serde(default = "default_page_size")]
|
||||
pub limit: u8,
|
||||
}
|
||||
|
||||
/// https://docs.joinmastodon.org/entities/notification/
|
||||
#[derive(Serialize)]
|
||||
pub struct ApiNotification {
|
||||
|
|
|
@ -7,27 +7,71 @@ use crate::database::{Pool, get_database_client};
|
|||
use crate::errors::HttpError;
|
||||
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||
use crate::models::notifications::queries::get_notifications;
|
||||
use super::types::ApiNotification;
|
||||
use super::types::{ApiNotification, NotificationQueryParams};
|
||||
|
||||
fn get_pagination_header(
|
||||
instance_url: &str,
|
||||
last_id: &str,
|
||||
) -> String {
|
||||
let next_page_url = format!(
|
||||
"{}/api/v1/notifications?max_id={}",
|
||||
instance_url,
|
||||
last_id
|
||||
);
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
|
||||
format!(r#"<{}>; rel="next""#, next_page_url)
|
||||
}
|
||||
|
||||
#[get("")]
|
||||
async fn get_notifications_view(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
query_params: web::Query<NotificationQueryParams>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let notifications: Vec<ApiNotification> = get_notifications(
|
||||
db_client,
|
||||
¤t_user.id,
|
||||
query_params.max_id,
|
||||
query_params.limit.into(),
|
||||
).await?
|
||||
.into_iter()
|
||||
.map(|item| ApiNotification::from_db(item, &config.instance_url()))
|
||||
.collect();
|
||||
Ok(HttpResponse::Ok().json(notifications))
|
||||
let max_index = usize::from(query_params.limit - 1);
|
||||
let response = if let Some(item) = notifications.get(max_index) {
|
||||
let pagination_header = get_pagination_header(&config.instance_url(), &item.id);
|
||||
HttpResponse::Ok()
|
||||
.header("Link", pagination_header)
|
||||
// Link header needs to be exposed
|
||||
// https://github.com/actix/actix-extras/issues/192
|
||||
.header("Access-Control-Expose-Headers", "Link")
|
||||
.json(notifications)
|
||||
} else {
|
||||
HttpResponse::Ok().json(notifications)
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn notification_api_scope() -> ActixScope {
|
||||
web::scope("/api/v1/notifications")
|
||||
.service(get_notifications_view)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const INSTANCE_URL: &str = "https://example.org";
|
||||
|
||||
#[test]
|
||||
fn test_get_next_page_link() {
|
||||
let result = get_pagination_header(INSTANCE_URL, "123");
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"<https://example.org/api/v1/notifications?max_id=123>; rel="next""#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,8 @@ pub async fn create_repost_notification(
|
|||
pub async fn get_notifications(
|
||||
db_client: &impl GenericClient,
|
||||
recipient_id: &Uuid,
|
||||
max_id: Option<i32>,
|
||||
limit: i64,
|
||||
) -> Result<Vec<Notification>, DatabaseError> {
|
||||
let statement = format!(
|
||||
"
|
||||
|
@ -111,8 +113,11 @@ pub async fn get_notifications(
|
|||
ON notification.post_id = post.id
|
||||
LEFT JOIN actor_profile AS post_author
|
||||
ON post.author_id = post_author.id
|
||||
WHERE recipient_id = $1
|
||||
ORDER BY notification.created_at DESC
|
||||
WHERE
|
||||
recipient_id = $1
|
||||
AND ($2::integer IS NULL OR notification.id < $2)
|
||||
ORDER BY notification.id DESC
|
||||
LIMIT $3
|
||||
",
|
||||
related_attachments=RELATED_ATTACHMENTS,
|
||||
related_mentions=RELATED_MENTIONS,
|
||||
|
@ -120,7 +125,7 @@ pub async fn get_notifications(
|
|||
);
|
||||
let rows = db_client.query(
|
||||
statement.as_str(),
|
||||
&[&recipient_id],
|
||||
&[&recipient_id, &max_id, &limit],
|
||||
).await?;
|
||||
let mut notifications: Vec<Notification> = rows.iter()
|
||||
.map(Notification::try_from)
|
||||
|
|
Loading…
Reference in a new issue