Impl /auth/user/list

This commit is contained in:
2022-10-16 02:10:35 +00:00
committed by GitHub
parent e2a1dde4a6
commit 43f8a27172
7 changed files with 248 additions and 2 deletions

View File

@@ -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`

View File

@@ -319,6 +319,38 @@ impl PixivDownloaderSqlite {
.optional()?)
}
#[cfg(feature = "server")]
async fn _list_users(&self, offset: u64, limit: u64) -> Result<Vec<User>, 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<u8> = 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<Vec<u64>, 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<Option<[u8; 4]>, 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<Vec<User>, PixivDownloaderDbError> {
Ok(self._list_users(offset, limit).await?)
}
#[cfg(feature = "server")]
async fn list_users_id(
&self,
offset: u64,
count: u64,
) -> Result<Vec<u64>, PixivDownloaderDbError> {
Ok(self._list_users_id(offset, count).await?)
}
#[cfg(feature = "server")]
async fn revoke_expired_tokens(&self) -> Result<usize, PixivDownloaderDbError> {
let mut db = self.db.lock().await;

View File

@@ -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<Vec<User>, 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<Vec<u64>, PixivDownloaderDbError>;
#[cfg(feature = "server")]
/// Remove all expired tokens
/// Return the number of removed tokens
async fn revoke_expired_tokens(&self) -> Result<usize, PixivDownloaderDbError>;

View File

@@ -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 <opt>:")
.replace("<opt>", gettext("page number")),
)?
.unwrap_or(1);
let page_count = params
.get_u64_mult(&["page_count", "pc"])
.try_err3(
22,
&gettext("Failed to parse <opt>:")
.replace("<opt>", gettext("page count")),
)?
.unwrap_or(10);
if page == 0 {
return Err((
21,
&gettext("<sth> should be greater than <num>.")
.replace("<sth>", gettext("Page number"))
.replace("<num>", "0"),
)
.into());
}
if page_count == 0 {
return Err((
23,
&gettext("<sth> should be greater than <num>.")
.replace("<sth>", gettext("Page count"))
.replace("<num>", "0"),
)
.into());
}
let id_only = params
.get_bool("id_only")
.try_err3(
24,
&gettext("Failed to parse <opt>:").replace("<opt>", "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<Body, Body> 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/") {

View File

@@ -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<S: AsRef<str> + ?Sized>(
&self,
name: &[&S],
) -> Result<Option<u64>, 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<S: AsRef<str> + ?Sized>(&self, name: &S) -> Option<&Vec<String>> {

View File

@@ -426,6 +426,24 @@ pub async fn test(ctx: &UnitTestContext) -> Result<[(u64, Vec<u8>); 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<u8>); 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<u8>); 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)])
}

View File

@@ -291,7 +291,9 @@ impl UgoiraFrames {
pub fn append<T: ToCStr>(&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("<sth> should be greater than <num>.")
.replace("<sth>", gettext("Delay"))
.replace("<num>", "0"))?;
}
let re = unsafe { _ugoira::new_ugoira_frame(f.as_ptr(), delay, self.tail) };
if re.is_null() {