From 43f8a2717298580187532615c624ff8d30d91bf2 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sun, 16 Oct 2022 02:10:35 +0000 Subject: [PATCH] Impl /auth/user/list --- doc/api/auth.zh_CN.md | 4 ++ src/db/sqlite/db.rs | 50 +++++++++++++++++++++++ src/db/traits.rs | 18 +++++++++ src/server/auth/user.rs | 76 ++++++++++++++++++++++++++++++++++- src/server/params.rs | 18 +++++++++ src/server/unittest/auth.rs | 80 +++++++++++++++++++++++++++++++++++++ src/ugoira.rs | 4 +- 7 files changed, 248 insertions(+), 2 deletions(-) diff --git a/doc/api/auth.zh_CN.md b/doc/api/auth.zh_CN.md index b6443de..176e5a7 100644 --- a/doc/api/auth.zh_CN.md +++ b/doc/api/auth.zh_CN.md @@ -35,6 +35,10 @@ * 方法: `GET` 或 `POST` * RESTful: `GET /api/auth/user` 或 `GET /auth/user` * 鉴权: 需要(其他用户信息仅管理员) +## 获取用户列表 +* 路径: `/api/auth/user/list`、 `/auth/user/list` +* 方法: `GET` 或 `POST` +* 鉴权: 需要(仅管理员) ## 获取Token * 路径: `/api/auth/token/add`、 `/auth/token/add` * 方法: `GET` 或 `POST` diff --git a/src/db/sqlite/db.rs b/src/db/sqlite/db.rs index ce2081d..3a36df5 100644 --- a/src/db/sqlite/db.rs +++ b/src/db/sqlite/db.rs @@ -319,6 +319,38 @@ impl PixivDownloaderSqlite { .optional()?) } + #[cfg(feature = "server")] + async fn _list_users(&self, offset: u64, limit: u64) -> Result, SqliteError> { + let con = self.db.lock().await; + let mut stmt = con.prepare("SELECT * FROM users LIMIT ?, ?;")?; + let mut rows = stmt.query([offset, limit])?; + let mut users = Vec::new(); + while let Some(row) = rows.next()? { + let password: Vec = row.get(3)?; + let password: &[u8] = &password; + users.push(User { + id: row.get(0)?, + name: row.get(1)?, + username: row.get(2)?, + password: BytesMut::from(password), + is_admin: row.get(4)?, + }); + } + Ok(users) + } + + #[cfg(feature = "server")] + async fn _list_users_id(&self, offset: u64, count: u64) -> Result, SqliteError> { + let con = self.db.lock().await; + let mut stmt = con.prepare("SELECT id FROM users LIMIT ?, ?;")?; + let mut rows = stmt.query([offset, count])?; + let mut ids = Vec::new(); + while let Some(row) = rows.next()? { + ids.push(row.get(0)?); + } + Ok(ids) + } + async fn _read_version(&self) -> Result, SqliteError> { let con = self.db.lock().await; let mut stmt = con.prepare("SELECT v1, v2, v3, v4 FROM version WHERE id='main';")?; @@ -596,6 +628,24 @@ impl PixivDownloaderDb for PixivDownloaderSqlite { Ok(()) } + #[cfg(feature = "server")] + async fn list_users( + &self, + offset: u64, + limit: u64, + ) -> Result, PixivDownloaderDbError> { + Ok(self._list_users(offset, limit).await?) + } + + #[cfg(feature = "server")] + async fn list_users_id( + &self, + offset: u64, + count: u64, + ) -> Result, PixivDownloaderDbError> { + Ok(self._list_users_id(offset, count).await?) + } + #[cfg(feature = "server")] async fn revoke_expired_tokens(&self) -> Result { let mut db = self.db.lock().await; diff --git a/src/db/traits.rs b/src/db/traits.rs index a2cc521..a0ad655 100644 --- a/src/db/traits.rs +++ b/src/db/traits.rs @@ -78,6 +78,24 @@ pub trait PixivDownloaderDb { /// Initialize the database (create tables, migrate data, etc.) async fn init(&self) -> Result<(), PixivDownloaderDbError>; #[cfg(feature = "server")] + /// List users + /// * `offset` - The offset of the first user + /// * `limit` - The maximum number of users to return + async fn list_users( + &self, + offset: u64, + limit: u64, + ) -> Result, PixivDownloaderDbError>; + #[cfg(feature = "server")] + /// List users' id + /// * `offset` - The offset of the list + /// * `count` - The maximum count of the list + async fn list_users_id( + &self, + offset: u64, + count: u64, + ) -> Result, PixivDownloaderDbError>; + #[cfg(feature = "server")] /// Remove all expired tokens /// Return the number of removed tokens async fn revoke_expired_tokens(&self) -> Result; diff --git a/src/server/auth/user.rs b/src/server/auth/user.rs index 69dd57a..e0944fe 100644 --- a/src/server/auth/user.rs +++ b/src/server/auth/user.rs @@ -19,6 +19,8 @@ pub enum AuthUserAction { Delete, /// Get a user's information. GetInfo, + /// List users. + List, /// Update a existed user. Update, } @@ -276,6 +278,77 @@ impl AuthUserContext { } Ok(nuser.to_json2()) } + AuthUserAction::List => { + if root_user.is_some() { + if !user.as_ref().expect("User not found:").is_admin { + return Err((9, gettext("Admin privileges required.")).into()); + } + } + let page = params + .get_u64_mult(&["page", "p"]) + .try_err3( + 20, + &gettext("Failed to parse :") + .replace("", gettext("page number")), + )? + .unwrap_or(1); + let page_count = params + .get_u64_mult(&["page_count", "pc"]) + .try_err3( + 22, + &gettext("Failed to parse :") + .replace("", gettext("page count")), + )? + .unwrap_or(10); + if page == 0 { + return Err(( + 21, + &gettext(" should be greater than .") + .replace("", gettext("Page number")) + .replace("", "0"), + ) + .into()); + } + if page_count == 0 { + return Err(( + 23, + &gettext(" should be greater than .") + .replace("", gettext("Page count")) + .replace("", "0"), + ) + .into()); + } + let id_only = params + .get_bool("id_only") + .try_err3( + 24, + &gettext("Failed to parse :").replace("", "id_only"), + )? + .unwrap_or(false); + let offset = (page - 1) * page_count; + let data = if id_only { + let users = self + .ctx + .db + .list_users_id(offset, page_count) + .await + .try_err3(-1001, gettext("Failed to operate the database:"))?; + json::from(users) + } else { + let users = self + .ctx + .db + .list_users(offset, page_count) + .await + .try_err3(-1001, gettext("Failed to operate the database:"))?; + let mut tmp = Vec::with_capacity(users.len()); + for user in users { + tmp.push(user.to_json2()); + } + json::from(tmp) + }; + Ok(json::object! { "page": page, "page_count": page_count, "data": data }) + } AuthUserAction::Update => { if root_user.is_some() { if !user.as_ref().expect("User not found:").is_admin { @@ -423,7 +496,7 @@ impl AuthUserRoute { pub fn new() -> Self { Self { regex: Regex::new( - r"^(/+api)?/+auth/+user(/+(add|update|delete|info|change/+(name|password)))?$", + r"^(/+api)?/+auth/+user(/+(add|update|delete|info|list|change/+(name|password)))?$", ) .unwrap(), } @@ -456,6 +529,7 @@ impl MatchRoute for AuthUserRoute { "add" => Some(AuthUserAction::Add), "delete" => Some(AuthUserAction::Delete), "info" => Some(AuthUserAction::GetInfo), + "list" => Some(AuthUserAction::List), "update" => Some(AuthUserAction::Update), _ => { if m.starts_with("change/") { diff --git a/src/server/params.rs b/src/server/params.rs index 300852f..dfc4e6e 100644 --- a/src/server/params.rs +++ b/src/server/params.rs @@ -74,6 +74,24 @@ impl RequestParams { } } + /// Get parameter and return it as [u64]. + /// * `name` - A list of the parameter name. + /// # Note + /// It will return the first existed parameter's value. + pub fn get_u64_mult + ?Sized>( + &self, + name: &[&S], + ) -> Result, PixivDownloaderError> { + for name in name.iter() { + match self.get_u64(name) { + Ok(Some(v)) => return Ok(Some(v)), + Ok(None) => {} + Err(e) => return Err(e), + } + } + Ok(None) + } + /// Get all parameters with same name. /// * `name` - Parameter name. pub fn get_all + ?Sized>(&self, name: &S) -> Option<&Vec> { diff --git a/src/server/unittest/auth.rs b/src/server/unittest/auth.rs index 6103c05..b35a794 100644 --- a/src/server/unittest/auth.rs +++ b/src/server/unittest/auth.rs @@ -426,6 +426,24 @@ pub async fn test(ctx: &UnitTestContext) -> Result<[(u64, Vec); 2], PixivDow "is_admin": false, } ); + let re = ctx + .request_json2_sign( + "/auth/user/list", + &json::object! { "id_only": true }, + &token, + token_id, + ) + .await? + .unwrap(); + let result = JSONResult::from_json(re)?.unwrap(); + assert_eq!( + result, + json::object! { + "page": 1, + "page_count": 10, + "data": [0, 1], + } + ); let mut password3 = BytesMut::with_capacity(64); password3.resize(64, 0); openssl::rand::rand_bytes(&mut password3)?; @@ -489,6 +507,24 @@ pub async fn test(ctx: &UnitTestContext) -> Result<[(u64, Vec); 2], PixivDow "is_admin": false, } ); + let re = ctx + .request_json2_sign( + "/auth/user/list", + &json::object! { "id_only": true, "page_count": 2, "page": 2 }, + &token, + token_id, + ) + .await? + .unwrap(); + let result = JSONResult::from_json(re)?.unwrap(); + assert_eq!( + result, + json::object! { + "page": 2, + "page_count": 2, + "data": [2], + } + ); let re = ctx .request_json2_sign( "/auth/user/delete", @@ -511,5 +547,49 @@ pub async fn test(ctx: &UnitTestContext) -> Result<[(u64, Vec); 2], PixivDow .unwrap(); let result = JSONResult::from_json(re)?.unwrap(); assert_eq!(result.as_bool(), Some(true)); + let re = ctx + .request_json2_sign( + "/auth/user/list", + &json::object! { "id_only": true, "page_count": 2, "page": 1 }, + &token, + token_id, + ) + .await? + .unwrap(); + let result = JSONResult::from_json(re)?.unwrap(); + assert_eq!( + result, + json::object! { + "page": 1, + "page_count": 2, + "data": [0, 1], + } + ); + let re = ctx + .request_json2_sign("/auth/user/list", &json::object! {}, &token, token_id) + .await? + .unwrap(); + let result = JSONResult::from_json(re)?.unwrap(); + assert_eq!( + result, + json::object! { + "page": 1, + "page_count": 10, + "data": [ + { + "id": 0, + "name": "test", + "username": "test", + "is_admin": true, + }, + { + "id": 1, + "name": "sadiuqwed", + "username": "test1", + "is_admin": false, + }, + ], + } + ); Ok([(token_id, token), (token2_id, token2)]) } diff --git a/src/ugoira.rs b/src/ugoira.rs index 36dc99c..960d788 100644 --- a/src/ugoira.rs +++ b/src/ugoira.rs @@ -291,7 +291,9 @@ impl UgoiraFrames { pub fn append(&mut self, file: T, delay: f32) -> Result<(), UgoiraError> { let f = file.to_cstr()?; if delay <= 0f32 { - Err(gettext("Delay should be greater than 0."))?; + Err(gettext(" should be greater than .") + .replace("", gettext("Delay")) + .replace("", "0"))?; } let re = unsafe { _ugoira::new_ugoira_frame(f.as_ptr(), delay, self.tail) }; if re.is_null() {