From b6479579c08517b2a2edcbd8f4f765a30136693e Mon Sep 17 00:00:00 2001 From: JieningYu Date: Mon, 15 Jan 2024 18:13:01 +0800 Subject: [PATCH] respect js/ts devs --- src/handle/account.rs | 36 +++++----- src/handle/notification.rs | 59 +++++++++-------- src/handle/post.rs | 130 ++++++++++++++++++++----------------- src/handle/resource.rs | 40 +++++++----- src/lib.rs | 63 +++++++++++++++++- src/main.rs | 2 +- src/post.rs | 19 +++--- src/resource.rs | 18 ++--- src/tests/account.rs | 7 +- 9 files changed, 228 insertions(+), 146 deletions(-) diff --git a/src/handle/account.rs b/src/handle/account.rs index e3d7db5..6e02909 100644 --- a/src/handle/account.rs +++ b/src/handle/account.rs @@ -18,7 +18,7 @@ use sms4_backend::{ verify::{self, Captcha}, Account, Permission, Tag, TagEntry, Unverified, }, - Error, + Error, Id, }; use crate::{Auth, Global}; @@ -107,7 +107,7 @@ pub struct LoginReq { #[derive(Serialize, Deserialize)] pub struct LoginRes { - pub id: u64, + pub id: Id, pub token: String, pub expire_at: Option, } @@ -129,7 +129,7 @@ pub async fn login( })?; Ok(axum::Json(LoginRes { - id: lazy.id(), + id: lazy.id().into(), token, expire_at: exp_time, })) @@ -180,7 +180,7 @@ pub async fn reset_password( let mut lazy = gd!(select, unverified.email_hash()).ok_or(Error::PermissionDenied)?; let account = lazy.get_mut().await?; account.reset_password(captcha, new_password)?; - // Clear all tokens after reseting password + // Clear all tokens after resetting password account.clear_tokens(); Ok(()) } @@ -269,7 +269,7 @@ pub async fn logout( #[derive(Deserialize)] pub struct SetPermissionsReq { - pub target_account: u64, + pub target_account: Id, pub permissions: Vec, } @@ -294,8 +294,8 @@ pub async fn set_permissions( .filter_map(Tag::as_permission) .copied(); - let select_t = sd!(worlds.account, target_account); - let mut lazy_t = gd!(select_t, target_account).ok_or(Error::AccountNotFound)?; + let select_t = sd!(worlds.account, target_account.0); + let mut lazy_t = gd!(select_t, target_account.0).ok_or(Error::AccountNotFound)?; let target = lazy_t.get_mut().await?; if this .tags() @@ -391,14 +391,14 @@ impl Info { } pub async fn get_info( - Path(target): Path, + Path(target): Path, auth: Auth, State(Global { worlds, .. }): State>, ) -> Result, Error> { let select = sd!(worlds.account, auth.account); let this_lazy = va!(auth, select); - let select = sd!(worlds.account, target); - let lazy = gd!(select, target).ok_or(Error::AccountNotFound)?; + let select = sd!(worlds.account, target.0); + let lazy = gd!(select, target.0).ok_or(Error::AccountNotFound)?; let account = lazy.get().await?; if auth.account == account.id() { @@ -424,7 +424,7 @@ pub async fn get_info( #[derive(Deserialize)] pub struct BulkGetInfoReq { - pub ids: Vec, + pub ids: Vec, } /// Bulk gets account info, returns a map from account id to simple account info, @@ -433,21 +433,21 @@ pub async fn bulk_get_info( auth: Auth, State(Global { worlds, .. }): State>, Json(BulkGetInfoReq { ids }): Json, -) -> Result>, Error> { +) -> Result>, Error> { let select = sd!(worlds.account, auth.account); va!(auth, select => ViewSimpleAccount); - if let Some(last) = ids.first().copied() { - let mut select = worlds.account.select(0, last); + if let Some(first) = ids.first().copied() { + let mut select = worlds.account.select(0, first.0); for account in &ids[1..] { - select = select.plus(0, *account); + select = select.plus(0, account.0); } - select = select.hints(ids[..].into_iter().copied()); + select = select.hints(ids.iter().copied().map(From::from)); let mut iter = select.iter(); let mut res = HashMap::with_capacity(ids.len()); while let Some(Ok(lazy)) = iter.next().await { - if ids.contains(&lazy.id()) { + if ids.contains(&Id(lazy.id())) { if let Ok(account) = lazy.get().await { - res.insert(account.id(), Info::from_simple(account)); + res.insert(account.id().into(), Info::from_simple(account)); } } } diff --git a/src/handle/notification.rs b/src/handle/notification.rs index a703295..da81d5b 100644 --- a/src/handle/notification.rs +++ b/src/handle/notification.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use sms4_backend::{ account::{Permission, Tag}, notification::Notification, + Id, }; use time::{Date, Duration, OffsetDateTime}; @@ -59,7 +60,7 @@ pub struct NotifyReq { #[derive(Serialize)] pub struct NotifyRes { /// Id of the notification. - pub id: u64, + pub id: Id, } /// Creates a new notification. @@ -84,7 +85,7 @@ pub async fn notify( va!(auth, select => ManageNotifications); let notification = Notification::new(title, body, time, auth.account); - let id = notification.id(); + let id = Id(notification.id()); worlds.notification.insert(notification).await?; Ok(Json(NotifyRes { id })) } @@ -104,7 +105,7 @@ pub struct FilterNotificationParams { /// Filter notifications after this id.\ /// The field can be omitted. #[serde(default)] - pub from: Option, + pub from: Option, /// Max notifications to return.\ /// The field can be omitted, /// and the default value is **16**. @@ -116,7 +117,7 @@ pub struct FilterNotificationParams { /// /// This only works with the permission [`Permission::ManageNotifications`]. #[serde(default)] - pub sender: Option, + pub sender: Option, } impl FilterNotificationParams { @@ -127,7 +128,7 @@ impl FilterNotificationParams { #[derive(Serialize)] pub struct FilterNotificationRes { /// Notifications ids. - pub notifications: Vec, + pub notifications: Box<[Id]>, } /// Filters notifications. @@ -152,7 +153,7 @@ pub async fn filter( let mut select = worlds.notification.select_all(); if let Some(from) = from { - select = select.and(0, from..); + select = select.and(0, from.0..); } if let Some((before, after)) = after.zip(before) { if after + Duration::days(365) > before { @@ -170,24 +171,26 @@ pub async fn filter( 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) { + if from.is_some_and(|a| lazy.id() <= a.0) { continue; } if let Ok(val) = lazy.get().await { - if sender.is_some_and(|c| val.sender() != c && permitted_manage) + if sender.is_some_and(|c| Id(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()); + notifications.push(Id(val.id())); if notifications.len() == limit { break; } } } - Ok(Json(FilterNotificationRes { notifications })) + Ok(Json(FilterNotificationRes { + notifications: notifications.into_boxed_slice(), + })) } #[derive(Serialize)] @@ -220,7 +223,7 @@ impl Info { /// Gets a notification. pub async fn get_info( - Path(id): Path, + Path(Id(id)): Path, auth: Auth, State(Global { worlds, .. }): State>, ) -> Result, Error> { @@ -246,7 +249,7 @@ pub async fn get_info( #[derive(Deserialize)] pub struct BulkGetInfoReq { - pub notifications: Box<[u64]>, + pub notifications: Box<[Id]>, } pub async fn bulk_get_info( @@ -267,16 +270,16 @@ pub async fn bulk_get_info( }; let mut select = worlds .notification - .select(0, first) - .hints(notifications.iter().copied()); + .select(0, first.0) + .hints(notifications.iter().copied().map(From::from)); for id in notifications[1..].iter().copied() { - select = select.plus(0, id); + select = select.plus(0, id.0); } let mut iter = select.iter(); let mut res = HashMap::with_capacity(notifications.len().max(64)); let now = OffsetDateTime::now_utc(); while let Some(Ok(lazy)) = iter.next().await { - if notifications.contains(&lazy.id()) { + if notifications.contains(&Id(lazy.id())) { if let Ok(val) = lazy.get().await { if val.time() <= now && !permitted_manage { continue; @@ -298,16 +301,16 @@ pub async fn bulk_get_info( /// /// The request must be authorized with [`Permission::ManageNotifications`]. pub async fn remove( - Path(id): Path, + Path(id): Path, auth: Auth, State(Global { worlds, .. }): State>, ) -> Result<(), Error> { let select = sd!(worlds.account, auth.account); va!(auth, select => ManageNotifications); - let select = sd!(worlds.notification, id); - gd!(select, id) - .ok_or(Error::NotificationNotFound(id))? + let select = sd!(worlds.notification, id.0); + gd!(select, id.0) + .ok_or(Error::NotificationNotFound(id.0))? .destroy() .await?; @@ -317,7 +320,7 @@ pub async fn remove( /// Request body for bulk removing notifications. #[derive(Deserialize)] pub struct BulkRemoveReq { - pub notifications: Box<[u64]>, + pub notifications: Box<[Id]>, } /// Bulk removes notifications. @@ -340,15 +343,15 @@ pub async fn bulk_remove( if let Some(first) = notifications.first().copied() { let mut select = worlds .notification - .select(0, first) - .hints(notifications.iter().copied()); + .select(0, first.0) + .hints(notifications.iter().copied().map(From::from)); for id in notifications[1..].iter().copied() { - select = select.plus(0, id); + select = select.plus(0, id.0); } let mut iter = select.iter(); while let Some(Ok(lazy)) = iter.next().await { - if notifications.contains(&lazy.id()) { + if notifications.contains(&Id(lazy.id())) { lazy.destroy().await?; } } @@ -377,15 +380,15 @@ pub struct ModifyReq { /// /// The request body is declared as [`ModifyReq`]. pub async fn modify( - Path(id): Path, + Path(id): Path, auth: Auth, State(Global { worlds, .. }): State>, Json(ModifyReq { title, body, time }): Json, ) -> Result<(), Error> { let select = sd!(worlds.account, auth.account); va!(auth, select => ManageNotifications); - let select = sd!(worlds.notification, id); - let mut lazy = gd!(select, id).ok_or(Error::NotificationNotFound(id))?; + let select = sd!(worlds.notification, id.0); + let mut lazy = gd!(select, id.0).ok_or(Error::NotificationNotFound(id.0))?; let val = lazy.get_mut().await?; if let Some(title) = title { diff --git a/src/handle/post.rs b/src/handle/post.rs index aa3116b..d5aa12e 100644 --- a/src/handle/post.rs +++ b/src/handle/post.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use sms4_backend::{ account::{Permission, Tag}, post::{Post, Priority, Status}, - Error, + Error, Id, }; use time::{Date, OffsetDateTime}; @@ -47,7 +47,7 @@ pub struct NewPostReq { /// Time range of the post. pub time: RangeInclusive, /// List of resource ids this post used. - pub resources: Box<[u64]>, + pub resources: Box<[Id]>, /// Whether this post should be played as /// a full sequence. pub grouped: bool, @@ -67,7 +67,7 @@ pub struct NewPostReq { #[derive(Serialize)] pub struct NewPostRes { /// Id of the new post. - pub id: u64, + pub id: Id, } /// Creates a new post. @@ -110,16 +110,16 @@ pub async fn new_post( let mut validated = 0; let mut select = worlds .resource - .select(0, *resources.first().ok_or(Error::PostResourceEmpty)?) - .hints(resources.iter().copied()); + .select(0, resources.first().ok_or(Error::PostResourceEmpty)?.0) + .hints(resources.iter().copied().map(From::from)); for id in resources.iter().copied() { - select = select.plus(0, id) + select = select.plus(0, id.0) } let mut iter = select.iter(); while let Some(Ok(mut lazy)) = iter.next().await { - if resources.contains(&lazy.id()) { + if resources.contains(&Id(lazy.id())) { if let Ok(val) = lazy.get().await { - if val.owner() == auth.account { + if val.owner() == Id(auth.account) { if let Ok(val) = lazy.get_mut().await { val.block()?; lazy.close().await?; @@ -153,7 +153,7 @@ pub async fn new_post( .await .map_err(|_| Error::PermissionDenied)?; - Ok(Json(NewPostRes { id })) + Ok(Json(NewPostRes { id: id.into() })) } /// Request URL query parameters for filtering posts. @@ -171,7 +171,7 @@ pub struct FilterPostsParams { /// Filter posts after this id.\ /// The field can be omitted. #[serde(default)] - pub from: Option, + pub from: Option, /// Max posts to return.\ /// The field can be omitted, /// and the default value is **64**. @@ -181,7 +181,7 @@ pub struct FilterPostsParams { /// Filter posts creator.\ /// The field can be omitted. #[serde(default)] - pub creator: Option, + pub creator: Option, /// Filter with post status.\ /// The field can be omitted. #[serde(default)] @@ -214,7 +214,7 @@ impl FilterPostsParams { #[derive(Serialize)] pub struct FilterPostsRes { /// List of post ids. - pub posts: Vec, + pub posts: Box<[Id]>, } /// Filters posts with given filter options. @@ -253,10 +253,10 @@ pub async fn filter( let mut select = worlds.post.select_all(); if let Some(from) = from { - select = select.and(0, from..); + select = select.and(0, from.0..); } if let Some(creator) = creator { - select = select.and(2, creator); + select = select.and(2, creator.0); } if let Some(status) = status { if matches!(status, sms4_backend::post::Status::Approved) { @@ -278,7 +278,7 @@ pub async fn filter( let mut iter = select.iter(); let mut posts = Vec::new(); while let Some(Ok(lazy)) = iter.next().await { - if from.is_some_and(|a| lazy.id() <= a) + if from.is_some_and(|a| lazy.id() <= a.0) || screen.is_some_and(|s| lazy.id() % (s + 1) as u64 != 0) { continue; @@ -287,7 +287,7 @@ pub async fn filter( if creator.is_some_and(|c| val.creator() != c) || status.is_some_and(|s| val.state().status() != s) || on.is_some_and(|d| !val.time().contains(&d)) - || (val.creator() != auth.account + || (val.creator() != Id(auth.account) && !if matches!(val.state().status(), sms4_backend::post::Status::Approved) { permitted_get_pub } else { @@ -296,13 +296,15 @@ pub async fn filter( { continue; } - posts.push(val.id()); + posts.push(Id(val.id())); if posts.len() == limit { break; } } } - Ok(Json(FilterPostsRes { posts })) + Ok(Json(FilterPostsRes { + posts: posts.into_boxed_slice(), + })) } /// Represents information of a post. @@ -314,9 +316,9 @@ pub enum Info { /// Title of the post. title: String, /// Creator of this post. - creator: u64, + creator: Id, /// List of resource ids this post used. - resources: Box<[u64]>, + resources: Box<[Id]>, /// Whether this post should be played as /// a full sequence. grouped: bool, @@ -362,7 +364,7 @@ impl Info { } pub async fn get_info( - Path(id): Path, + Path(Id(id)): Path, auth: Auth, State(Global { worlds, .. }): State>, ) -> Result, Error> { @@ -383,7 +385,7 @@ pub async fn get_info( let lazy = gd!(select, id).ok_or(Error::PostNotFound(id))?; let val = lazy.get().await?; - if val.creator() == auth.account || permitted_review { + if val.creator() == Id(auth.account) || permitted_review { return Ok(Json(Info::from_full(&val))); } else if permitted_get_pub && matches!(val.state().status(), sms4_backend::post::Status::Approved) @@ -397,7 +399,7 @@ pub async fn get_info( #[derive(Deserialize)] pub struct BulkGetInfoReq { - pub posts: Box<[u64]>, + pub posts: Box<[Id]>, } #[derive(Serialize)] @@ -426,17 +428,20 @@ pub async fn bulk_get_info( let Some(first) = posts.get(0).copied() else { return Ok(Json(HashMap::new())); }; - let mut select = worlds.post.select(0, first).hints(posts.iter().copied()); + let mut select = worlds + .post + .select(0, first.0) + .hints(posts.iter().copied().map(From::from)); for id in posts[1..].iter().copied() { - select = select.plus(0, id); + select = select.plus(0, id.0); } let mut iter = select.iter(); let mut res = HashMap::with_capacity(posts.len().max(64)); let now = OffsetDateTime::now_utc().date(); while let Some(Ok(lazy)) = iter.next().await { - if posts.contains(&lazy.id()) { + if posts.contains(&Id(lazy.id())) { if let Ok(val) = lazy.get().await { - if val.creator() == auth.account || permitted_review { + if val.creator() == Id(auth.account) || permitted_review { res.insert(val.id(), Info::from_full(val)); } else if permitted_get_pub && matches!(val.state().status(), sms4_backend::post::Status::Approved) @@ -465,25 +470,25 @@ pub struct ModifyReq { /// Overrides the linked post resources /// with given ones. #[serde(default)] - pub resources: Option>, + pub resources: Option>, #[serde(default)] pub grouped: Option, } pub async fn modify( - Path(id): Path, + Path(id): Path, auth: Auth, State(Global { worlds, .. }): State>, Json(mut req): Json, ) -> Result<(), Error> { let select = sd!(worlds.account, auth.account); va!(auth, select => Post); - let select = sd!(worlds.post, id); - let mut lazy = gd!(select, id).ok_or(Error::PostNotFound(id))?; + let select = sd!(worlds.post, id.0); + let mut lazy = gd!(select, id.0).ok_or(Error::PostNotFound(id.0))?; let post = lazy.get_mut().await?; - if post.creator() != auth.account { - return Err(Error::PostNotFound(id)); + if post.creator() != Id(auth.account) { + return Err(Error::PostNotFound(id.0)); } macro_rules! modify { @@ -525,22 +530,22 @@ pub async fn modify( .iter() .copied() .next() - .unwrap_or_else(|| old_diff.iter().copied().next().unwrap()), + .map_or_else(|| old_diff.iter().copied().next().unwrap().0, |i| i.0), ) - .hints(new_diff.iter().copied()) - .hints(old_diff.iter().copied()); + .hints(new_diff.iter().copied().map(From::from)) + .hints(old_diff.iter().copied().map(From::from)); for id in old_diff.iter().copied().chain(new_diff.iter().copied()) { - select = select.plus(0, id) + select = select.plus(0, id.0) } let mut iter = select.iter(); while let Some(Ok(mut lazy)) = iter.next().await { - if old_diff.contains(&lazy.id()) { + if old_diff.contains(&Id(lazy.id())) { lazy.destroy().await?; - } else if new_diff.contains(&lazy.id()) { + } else if new_diff.contains(&Id(lazy.id())) { let res = lazy.get_mut().await?; res.block()?; - result.push(lazy.id()); + result.push(Id(lazy.id())); lazy.close().await?; } else { continue; @@ -567,7 +572,7 @@ pub struct ReviewReq { } pub async fn review( - Query(id): Query, + Path(id): Path, auth: Auth, State(Global { worlds, .. }): State>, Json(ReviewReq { status, message }): Json, @@ -577,8 +582,8 @@ pub async fn review( if !matches!(status, Status::Approved | Status::Rejected) { return Err(Error::InvalidPostStatus); } - let select = sd!(worlds.post, id); - let mut lazy = gd!(select, id).ok_or(Error::PostNotFound(id))?; + let select = sd!(worlds.post, id.0); + let mut lazy = gd!(select, id.0).ok_or(Error::PostNotFound(id.0))?; let post = lazy.get_mut().await?; post.pust_state(sms4_backend::post::State::new( status, @@ -589,40 +594,40 @@ pub async fn review( } pub async fn remove( - Query(id): Query, + Path(id): Path, auth: Auth, State(Global { worlds, .. }): State>, ) -> Result<(), Error> { let select = sd!(worlds.account, auth.account); let this_lazy = va!(auth, select => Post); - let select = sd!(worlds.post, id); - let lazy = gd!(select, id).ok_or(Error::PostNotFound(id))?; + let select = sd!(worlds.post, id.0); + let lazy = gd!(select, id.0).ok_or(Error::PostNotFound(id.0))?; let post = lazy.get().await?; - if post.creator() != auth.account + if post.creator() != Id(auth.account) && !this_lazy .get() .await? .tags() .contains_permission(&Tag::Permission(Permission::RemovePost)) { - return Err(Error::PostNotFound(id)); + return Err(Error::PostNotFound(id.0)); } if let Some(first) = post.resources().first().copied() { let resources = post.resources(); let mut select = worlds .resource - .select(0, first) + .select(0, first.0) .and(1, 1) - .hints(resources.iter().copied()); + .hints(resources.iter().copied().map(From::from)); for id in resources.iter().copied() { - select = select.plus(0, id) + select = select.plus(0, id.0) } let mut iter = select.iter(); while let Some(Ok(lazy)) = iter.next().await { - if resources.contains(&lazy.id()) { + if resources.contains(&Id(lazy.id())) { lazy.destroy().await?; } } @@ -633,7 +638,7 @@ pub async fn remove( #[derive(Deserialize)] #[serde(tag = "type")] pub enum BulkRemoveReq { - Posts { posts: Box<[u64]> }, + Posts { posts: Box<[Id]> }, Unused, } @@ -656,16 +661,19 @@ pub async fn bulk_remove( let Some(first) = posts.get(0).copied() else { return Ok(()); }; - let mut select = worlds.post.select(0, first).hints(posts.iter().copied()); + let mut select = worlds + .post + .select(0, first.0) + .hints(posts.iter().copied().map(From::from)); for id in posts[1..].iter().copied() { - select = select.plus(0, id); + select = select.plus(0, id.0); } let mut iter = select.iter(); while let Some(Ok(lazy)) = iter.next().await { - if posts.contains(&lazy.id()) { + if posts.contains(&Id(lazy.id())) { let post = lazy.get().await?; - if post.creator() != auth.account && !permitted_rm { + if post.creator() != Id(auth.account) && !permitted_rm { continue; } resources_rm.extend_from_slice(post.resources()); @@ -700,15 +708,15 @@ pub async fn bulk_remove( if let Some(first) = resources_rm.first().copied() { let mut select = worlds .resource - .select(0, first) + .select(0, first.0) .and(1, 1) - .hints(resources_rm.iter().copied()); + .hints(resources_rm.iter().copied().map(From::from)); for id in resources_rm.iter().copied() { - select = select.plus(0, id) + select = select.plus(0, id.0) } let mut iter = select.iter(); while let Some(Ok(lazy)) = iter.next().await { - if resources_rm.contains(&lazy.id()) { + if resources_rm.contains(&Id(lazy.id())) { lazy.destroy().await?; } } diff --git a/src/handle/resource.rs b/src/handle/resource.rs index 7c86e09..eace3a0 100644 --- a/src/handle/resource.rs +++ b/src/handle/resource.rs @@ -11,7 +11,10 @@ use serde::{Deserialize, Serialize}; #[allow(unused_imports)] use sms4_backend::account::Permission; -use sms4_backend::resource::{Resource, Variant}; +use sms4_backend::{ + resource::{Resource, Variant}, + Id, +}; use tokio::{fs::File, io::BufReader}; use crate::{Auth, Error, Global}; @@ -47,7 +50,7 @@ pub struct NewSessionReq { pub struct NewSessionRes { /// Id of the upload session.\ /// This is **not** id of the resource. - pub id: u64, + pub id: Id, } /// Creates a new upload session. @@ -75,17 +78,17 @@ pub async fn new_session( let select = sd!(worlds.account, auth.account); va!(auth, select => UploadResource); - let resource = Resource::new(variant, auth.account); + let resource = Resource::new(variant, Id(auth.account)); let id = resource.id(); resource_sessions.lock().await.insert(resource); - Ok(Json(NewSessionRes { id })) + Ok(Json(NewSessionRes { id: Id(id) })) } /// Response body for [`upload`]. #[derive(Serialize)] pub struct UploadRes { /// Id of the resource. - pub id: u64, + pub id: Id, } /// Uploads a resource within the given session. @@ -102,7 +105,7 @@ pub struct UploadRes { /// /// The response body is declared as [`UploadRes`]. pub async fn upload( - Path(id): Path, + Path(Id(id)): Path, auth: Auth, State(Global { worlds, @@ -148,8 +151,8 @@ pub async fn upload( let resource = resource_sessions .lock() .await - .accept(id, hasher, auth.account)?; - let id = resource.id(); + .accept(Id(id), hasher, Id(auth.account))?; + let id = Id(resource.id()); let path = config.resource_path.join(resource.file_name()); tokio::fs::rename(buf_path, path) .await @@ -178,7 +181,7 @@ pub async fn upload( /// - [`Error::ResourceNotFound`] if the resource with the given id does not exist. /// - [`Error::PermissionDenied`] if the resource is not blocked **and** is not owned by the authorized account. pub async fn get_payload( - Path(id): Path, + Path(Id(id)): Path, auth: Auth, State(Global { worlds, config, .. }): State>, ) -> Result { @@ -187,7 +190,7 @@ pub async fn get_payload( let select = sd!(worlds.resource, id); let lazy = gd!(select, id).ok_or(Error::ResourceNotFound(id))?; let resource = lazy.get().await?; - if resource.owner() != auth.account && !resource.is_blocked() { + if resource.owner() != Id(auth.account) && !resource.is_blocked() { return Err(Error::PermissionDenied); } @@ -206,7 +209,7 @@ pub struct Info { } pub async fn get_info( - Path(id): Path, + Path(Id(id)): Path, auth: Auth, State(Global { worlds, .. }): State>, ) -> Result, Error> { @@ -215,7 +218,7 @@ pub async fn get_info( let select = sd!(worlds.resource, id).and(1, 1); let lazy = gd!(select, id).ok_or(Error::ResourceNotFound(id))?; let resource = lazy.get().await?; - if resource.owner() != auth.account && !resource.is_blocked() { + if resource.owner() != Id(auth.account) && !resource.is_blocked() { return Err(Error::PermissionDenied); } Ok(Json(Info { @@ -227,7 +230,7 @@ pub async fn get_info( #[derive(Deserialize)] pub struct BulkGetInfoReq { /// Ids of the resources. - pub ids: Box<[u64]>, + pub ids: Box<[Id]>, } pub async fn bulk_get_info( @@ -239,15 +242,18 @@ pub async fn bulk_get_info( va!(auth, select => GetPubPost); let mut infos = HashMap::with_capacity(ids.len()); - let mut select = worlds.resource.select(1, 1).hints(ids.iter().copied()); + let mut select = worlds + .resource + .select(1, 1) + .hints(ids.iter().copied().map(From::from)); for &id in &*ids { - select = select.and(0, id); + select = select.and(0, id.0); } let mut iter = select.iter(); while let Some(Ok(lazy)) = iter.next().await { - if ids.contains(&lazy.id()) { + if ids.contains(&Id(lazy.id())) { if let Ok(resource) = lazy.get().await { - if resource.owner() != auth.account && !resource.is_blocked() { + if resource.owner() != Id(auth.account) && !resource.is_blocked() { return Err(Error::PermissionDenied); } infos.insert( diff --git a/src/lib.rs b/src/lib.rs index 4105da1..057f4d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::atomic::AtomicBool; use account::verify::VerifyVariant; use axum::{http::StatusCode, response::IntoResponse}; use lettre::transport::smtp; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use time::Duration; pub mod config; @@ -152,3 +152,64 @@ impl_from! { axum::http::header::ToStrError => HeaderNonAscii, dmds::Error => Database, } + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct Id(pub u64); + +impl Serialize for Id { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} + +impl<'de> Deserialize<'de> for Id { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Repr<'a> { + Num(u64), + Str(&'a str), + } + + match Repr::deserialize(deserializer)? { + Repr::Num(n) => Ok(Self(n)), + Repr::Str(s) => s.parse().map_err(|_| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Str(s), + &"number as a string", + ) + }), + } + } +} + +impl std::str::FromStr for Id { + type Err = std::num::ParseIntError; + + #[inline] + fn from_str(s: &str) -> Result { + s.parse().map(Self) + } +} + +impl From for Id { + #[inline] + fn from(value: u64) -> Self { + Self(value) + } +} + +impl From for u64 { + #[inline] + fn from(value: Id) -> Self { + value.0 + } +} diff --git a/src/main.rs b/src/main.rs index bdd465b..98ca19e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -196,7 +196,7 @@ fn routing(router: Router>) -> Router, /// List of resource ids this post used. - resources: Box<[u64]>, + resources: Box<[Id]>, /// Post states in time order.\ /// There should be at least one state in a post. @@ -66,7 +66,7 @@ impl Post { title: String, notes: String, time: RangeInclusive, - resources: Box<[u64]>, + resources: Box<[Id]>, account: u64, grouped: bool, priority: Priority, @@ -135,11 +135,12 @@ impl Post { /// Creator of this post. #[inline] - pub fn creator(&self) -> u64 { - self.states + pub fn creator(&self) -> Id { + Id(self + .states .first() .expect("there should be at least one state in a post") - .operator + .operator) } #[inline] @@ -163,12 +164,12 @@ impl Post { /// Gets the resources used by this post. #[inline] - pub fn resources(&self) -> &[u64] { + pub fn resources(&self) -> &[Id] { &self.resources } #[inline] - pub fn set_resources(&mut self, resources: Box<[u64]>) { + pub fn set_resources(&mut self, resources: Box<[Id]>) { self.resources = resources } @@ -192,7 +193,7 @@ impl dmds::Data for Post { match dim { 0 => self.id, 1 => self.time.start().ordinal() as u64, - 2 => self.creator(), + 2 => self.creator().0, 3 => self .states .last() diff --git a/src/resource.rs b/src/resource.rs index 8f5b79e..1ef12ab 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -7,7 +7,7 @@ use std::{ use serde::{Deserialize, Serialize}; use time::Instant; -use crate::Error; +use crate::{Error, Id}; /// Reference and metadata of a resource file. /// @@ -22,7 +22,7 @@ pub struct Resource { #[serde(skip)] id: u64, variant: Variant, - owner: u64, + owner: Id, #[serde(skip)] used: bool, @@ -33,7 +33,7 @@ impl Resource { /// /// The id will be generated randomly based on the /// time and account. - pub fn new(variant: Variant, account: u64) -> Self { + pub fn new(variant: Variant, account: Id) -> Self { let mut hasher = siphasher::sip::SipHasher24::new(); SystemTime::now().hash(&mut hasher); account.hash(&mut hasher); @@ -54,7 +54,7 @@ impl Resource { } #[inline] - pub fn owner(&self) -> u64 { + pub fn owner(&self) -> Id { self.owner } @@ -207,21 +207,21 @@ impl UploadSessions { /// tell the new id to the frontend. pub fn accept( &mut self, - id: u64, + id: Id, mut hasher: H, - user: u64, + user: Id, ) -> Result { self.cleanup(); let res = &self .inner - .get(&id) - .ok_or(Error::ResourceUploadSessionNotFound(id))? + .get(&id.0) + .ok_or(Error::ResourceUploadSessionNotFound(id.0))? .resource; if res.owner != user { return Err(Error::PermissionDenied); } - let mut res = self.inner.remove(&id).unwrap().resource; + let mut res = self.inner.remove(&id.0).unwrap().resource; SystemTime::now().hash(&mut hasher); user.hash(&mut hasher); res.id = hasher.finish(); diff --git a/src/tests/account.rs b/src/tests/account.rs index a7a8feb..178eb89 100644 --- a/src/tests/account.rs +++ b/src/tests/account.rs @@ -122,9 +122,12 @@ async fn login() { ); assert!(res.status().is_success()); let LoginRes { id, token, .. } = p_json!(res); - let auth = Auth { account: id, token }; + let auth = Auth { + account: id.0, + token, + }; - let select = sd!(state.worlds.account, id); + let select = sd!(state.worlds.account, id.0); assert!(async { Ok(va!(auth, select)) }.await.is_ok()); }