Skip to content

Commit

Permalink
Add more apis and permissions for notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
JieningYu committed Jan 8, 2024
1 parent 6dfcb3f commit 5005e0b
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 25 deletions.
14 changes: 14 additions & 0 deletions src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ pub enum Permission {
ViewFullAccount,
ViewSimpleAccount,

/// Manage notifications.
///
/// # Containing permissions
///
/// - [`Self::GetPubNotifications`]
ManageNotifications,
/// Get public notifications.
GetPubNotifications,

/// Upload resources.
UploadResource,

Expand All @@ -69,6 +78,7 @@ impl libaccount::Permission for Permission {
Self::GetPubPost,
Self::ViewSimpleAccount,
Self::UploadResource,
Self::GetPubNotifications,
]
.into()
}
Expand All @@ -88,6 +98,10 @@ impl libaccount::Permission for Permission {
Permission::RemovePost,
Permission::GetPubPost | Permission::ReviewPost
)
| (
Permission::ManageNotifications,
Permission::GetPubNotifications
)
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/handle/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ pub async fn set_permissions<Io: IoHandle>(
.copied();

let select_t = sd!(worlds.account, target_account);
let mut lazy_t = gd!(select_t, target_account).ok_or(Error::TargetAccountNotFound)?;
let mut lazy_t = gd!(select_t, target_account).ok_or(Error::AccountNotFound)?;
let target = lazy_t.get_mut().await?;
if this
.tags()
Expand Down Expand Up @@ -398,7 +398,7 @@ pub async fn get_info<Io: IoHandle>(
let select = sd!(worlds.account, auth.account);
let this_lazy = va!(auth, select => ViewSimpleAccount);
let select = sd!(worlds.account, target);
let lazy = gd!(select, target).ok_or(Error::TargetAccountNotFound)?;
let lazy = gd!(select, target).ok_or(Error::AccountNotFound)?;
let account = lazy.get().await?;

if auth.account == account.id() {
Expand Down
1 change: 1 addition & 0 deletions src/handle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,6 @@ macro_rules! va {
}

pub mod account;
pub mod notification;
pub mod post;
pub mod resource;
250 changes: 250 additions & 0 deletions src/handle/notification.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
use axum::{
extract::{Path, Query, State},
Json,
};
use dmds::{IoHandle, StreamExt};
use serde::{Deserialize, Serialize};
use sms4_backend::{
account::{Permission, Tag},
notification::Notification,
};
use time::{Date, Duration, OffsetDateTime};

use crate::{Auth, Error, Global};

/// Request body for creating a new notification.
///
/// # Examples
///
/// ```json
/// {
/// "title": "教务通知",
/// "body": "教务通知",
/// "time": 1620000000,
/// }
/// ```
#[derive(Deserialize)]
pub struct NotifyReq {
/// Title of the notification.
///
/// # Examples
///
/// ```txt
/// 教务通知
/// ```
///
/// ```txt
/// 教务公告
/// ```
pub title: String,
/// Body of the notification.
pub body: String,

/// Start time of the notification.
#[serde(with = "time::serde::timestamp")]
pub time: OffsetDateTime,
}

/// Response body for creating a new notification.
///
/// # Examples
///
/// ```json
/// {
/// "id": 19,
/// }
/// ```
pub struct NotifyRes {
/// Id of the notification.
pub id: u64,
}

/// Creates a new notification.
///
/// # Request
///
/// The request body is declared as [`NotifyReq`].
///
/// # Authorization
///
/// The request must be authorized with [`Permission::ManageNotifications`].
///
/// # Response
///
/// The response body is declared as [`NotifyRes`].
pub async fn notify<Io: IoHandle>(
auth: Auth,
State(Global { worlds, .. }): State<Global<Io>>,
Json(NotifyReq { title, body, time }): Json<NotifyReq>,
) -> Result<NotifyRes, Error> {
let select = sd!(worlds.account, auth.account);
va!(auth, select => ManageNotifications);

let notification = Notification::new(title, body, time, auth.account);
let id = notification.id();
worlds.notification.insert(notification).await?;
Ok(NotifyRes { id })
}

/// Request URL query parameters for filtering notifications.
#[derive(Deserialize)]
pub struct FilterNotificationParams {
/// Filter notifications from this date.\
/// The field can be omitted.
#[serde(default)]
pub after: Option<Date>,
/// Filter notifications until this date.\
/// The field can be omitted.
#[serde(default)]
pub before: Option<Date>,

/// Filter notifications after this id.\
/// The field can be omitted.
#[serde(default)]
pub from: Option<u64>,
/// Max notifications to return.\
/// The field can be omitted,
/// and the default value is **16**.
#[serde(default = "FilterNotificationParams::DEFAULT_LIMIT")]
pub limit: usize,

/// Filter notifications from this account.\
/// The field can be omitted.
///
/// This only works with the permission [`Permission::ManageNotifications`].
#[serde(default)]
pub sender: Option<u64>,
}

impl FilterNotificationParams {
const DEFAULT_LIMIT: fn() -> usize = || 16;
}

/// Response body for filtering notifications.
#[derive(Serialize)]
pub struct FilterNotificationRes {
/// Notifications ids.
pub notifications: Vec<u64>,
}

/// Filters notifications.
pub async fn filter<Io: IoHandle>(
Query(FilterNotificationParams {
after,
before,
from,
limit,
sender,
}): Query<FilterNotificationParams>,
auth: Auth,
State(Global { worlds, .. }): State<Global<Io>>,
) -> Result<Json<FilterNotificationRes>, Error> {
let select = sd!(worlds.account, auth.account);
let lazy_this = va!(auth, select => GetPubNotifications);
let permitted_manage = lazy_this
.get()
.await?
.tags()
.contains_permission(&Tag::Permission(Permission::ManageNotifications));

let mut select = worlds.notification.select_all();
if let Some(from) = from {
select = select.and(0, from..);
}
if let Some((before, after)) = after.zip(before) {
if after + Duration::days(365) > before {
if after.year() == before.year() {
select = select.and(1, after.ordinal() as u64..=before.ordinal() as u64);
} else {
select = select
.and(1, ..=before.ordinal() as u64)
.plus(1, after.ordinal() as u64..);
}
}
}

let mut iter = select.iter();
let mut notifications = Vec::new();
let now = OffsetDateTime::now_utc();
while let Some(Ok(lazy)) = iter.next().await {
if from.is_some_and(|a| lazy.id() <= a) {
continue;
}
if let Ok(val) = lazy.get().await {
if sender.is_some_and(|c| val.sender() != c && permitted_manage)
|| after.is_some_and(|d| val.time().date() >= d)
|| before.is_some_and(|d| val.time().date() <= d)
|| (!permitted_manage && val.time() > now)
{
continue;
}
notifications.push(val.id());
if notifications.len() == limit {
break;
}
}
}
Ok(Json(FilterNotificationRes { notifications }))
}

#[derive(Serialize)]
pub enum Info {
Simple {
title: String,
body: String,
},
Full {
#[serde(flatten)]
inner: Notification,
},
}

impl Info {
fn from_simple(notification: &Notification) -> Self {
Self::Simple {
title: notification.title.to_owned(),
body: notification.body.to_owned(),
}
}

#[inline]
fn from_full(notification: &Notification) -> Self {
Self::Full {
inner: notification.clone(),
}
}
}

/// Gets a notification.
pub async fn get_info<Io: IoHandle>(
Path(id): Path<u64>,
auth: Auth,
State(Global { worlds, .. }): State<Global<Io>>,
) -> Result<Json<Info>, Error> {
let select = sd!(worlds.account, auth.account);
let lazy_this = va!(auth, select => GetPubNotifications);
let permitted_manage = lazy_this
.get()
.await?
.tags()
.contains_permission(&Tag::Permission(Permission::ManageNotifications));
let select = sd!(worlds.notification, id);
let lazy = gd!(select, id).ok_or(Error::NotificationNotFound(id))?;
let notification = lazy.get().await?;
if notification.time() > OffsetDateTime::now_utc() && !permitted_manage {
return Err(Error::NotificationNotFound(id));
}
Ok(Json(if permitted_manage {
Info::from_full(notification)
} else {
Info::from_simple(notification)
}))
}

pub struct BulkGetInfoReq {
pub ids: Box<[u64]>,
}

pub async fn bulk_get_info<Io: IoHandle>() {
unimplemented!()
}
Loading

0 comments on commit 5005e0b

Please sign in to comment.