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
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Account'
|
$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:
|
/api/v1/statuses:
|
||||||
post:
|
post:
|
||||||
summary: Create new post.
|
summary: Create new post.
|
||||||
|
@ -446,6 +473,31 @@ components:
|
||||||
description: Ethereum wallet address.
|
description: Ethereum wallet address.
|
||||||
type: string
|
type: string
|
||||||
example: '0xd8da6bf...'
|
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:
|
Relationship:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::mastodon_api::accounts::types::Account;
|
use crate::mastodon_api::accounts::types::Account;
|
||||||
use crate::mastodon_api::statuses::types::Status;
|
use crate::mastodon_api::statuses::types::Status;
|
||||||
use crate::models::notifications::types::{EventType, Notification};
|
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/
|
/// https://docs.joinmastodon.org/entities/notification/
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ApiNotification {
|
pub struct ApiNotification {
|
||||||
|
|
|
@ -7,27 +7,71 @@ use crate::database::{Pool, get_database_client};
|
||||||
use crate::errors::HttpError;
|
use crate::errors::HttpError;
|
||||||
use crate::mastodon_api::oauth::auth::get_current_user;
|
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||||
use crate::models::notifications::queries::get_notifications;
|
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("")]
|
#[get("")]
|
||||||
async fn get_notifications_view(
|
async fn get_notifications_view(
|
||||||
auth: BearerAuth,
|
auth: BearerAuth,
|
||||||
config: web::Data<Config>,
|
config: web::Data<Config>,
|
||||||
db_pool: web::Data<Pool>,
|
db_pool: web::Data<Pool>,
|
||||||
|
query_params: web::Query<NotificationQueryParams>,
|
||||||
) -> Result<HttpResponse, HttpError> {
|
) -> Result<HttpResponse, HttpError> {
|
||||||
let db_client = &**get_database_client(&db_pool).await?;
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let notifications: Vec<ApiNotification> = get_notifications(
|
let notifications: Vec<ApiNotification> = get_notifications(
|
||||||
db_client,
|
db_client,
|
||||||
¤t_user.id,
|
¤t_user.id,
|
||||||
|
query_params.max_id,
|
||||||
|
query_params.limit.into(),
|
||||||
).await?
|
).await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| ApiNotification::from_db(item, &config.instance_url()))
|
.map(|item| ApiNotification::from_db(item, &config.instance_url()))
|
||||||
.collect();
|
.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 {
|
pub fn notification_api_scope() -> ActixScope {
|
||||||
web::scope("/api/v1/notifications")
|
web::scope("/api/v1/notifications")
|
||||||
.service(get_notifications_view)
|
.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(
|
pub async fn get_notifications(
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
recipient_id: &Uuid,
|
recipient_id: &Uuid,
|
||||||
|
max_id: Option<i32>,
|
||||||
|
limit: i64,
|
||||||
) -> Result<Vec<Notification>, DatabaseError> {
|
) -> Result<Vec<Notification>, DatabaseError> {
|
||||||
let statement = format!(
|
let statement = format!(
|
||||||
"
|
"
|
||||||
|
@ -111,8 +113,11 @@ pub async fn get_notifications(
|
||||||
ON notification.post_id = post.id
|
ON notification.post_id = post.id
|
||||||
LEFT JOIN actor_profile AS post_author
|
LEFT JOIN actor_profile AS post_author
|
||||||
ON post.author_id = post_author.id
|
ON post.author_id = post_author.id
|
||||||
WHERE recipient_id = $1
|
WHERE
|
||||||
ORDER BY notification.created_at DESC
|
recipient_id = $1
|
||||||
|
AND ($2::integer IS NULL OR notification.id < $2)
|
||||||
|
ORDER BY notification.id DESC
|
||||||
|
LIMIT $3
|
||||||
",
|
",
|
||||||
related_attachments=RELATED_ATTACHMENTS,
|
related_attachments=RELATED_ATTACHMENTS,
|
||||||
related_mentions=RELATED_MENTIONS,
|
related_mentions=RELATED_MENTIONS,
|
||||||
|
@ -120,7 +125,7 @@ pub async fn get_notifications(
|
||||||
);
|
);
|
||||||
let rows = db_client.query(
|
let rows = db_client.query(
|
||||||
statement.as_str(),
|
statement.as_str(),
|
||||||
&[&recipient_id],
|
&[&recipient_id, &max_id, &limit],
|
||||||
).await?;
|
).await?;
|
||||||
let mut notifications: Vec<Notification> = rows.iter()
|
let mut notifications: Vec<Notification> = rows.iter()
|
||||||
.map(Notification::try_from)
|
.map(Notification::try_from)
|
||||||
|
|
Loading…
Reference in a new issue