Tweaks for the desktop client

This commit is contained in:
StNicolay 2024-08-09 17:02:02 +03:00
parent 8eb5be96b3
commit 1c9bd104e0
Signed by: StNicolay
GPG Key ID: 9693D04DCD962B0D
33 changed files with 217 additions and 144 deletions

View File

@ -1,12 +1,12 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "SELECT\n username,\n permission_type as \"permission_type: PermissionRaw\"\nFROM\n permissions\n INNER JOIN users ON permissions.user_id = users.user_id\nWHERE\n folder_id = $1", "query": "SELECT\n users.user_id,\n permission_type as \"permission_type: PermissionRaw\"\nFROM\n permissions\n INNER JOIN users ON permissions.user_id = users.user_id\nWHERE\n folder_id = $1",
"describe": { "describe": {
"columns": [ "columns": [
{ {
"ordinal": 0, "ordinal": 0,
"name": "username", "name": "user_id",
"type_info": "Varchar" "type_info": "Int4"
}, },
{ {
"ordinal": 1, "ordinal": 1,
@ -35,5 +35,5 @@
false false
] ]
}, },
"hash": "39b78c7f3266bea5e3e44aa372574319cb74dea6b3d0bc16d25e29ca28803317" "hash": "003349bc951a935fdfb285f99a726c221e3d1d02cb9e47b4c385545298b27217"
} }

View File

@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "WITH\n permitted as (\n SELECT\n folder_id\n FROM\n permissions\n WHERE\n user_id = $1\n )\nSELECT\n folder_id, owner_id, folder_name, created_at\nFROM\n folders\nWHERE\n folder_id IN (\n SELECT\n folder_id\n FROM\n permitted\n )\n AND parent_folder_id NOT IN (\n SELECT\n folder_id\n FROM\n permitted\n )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "folder_id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "owner_id",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "folder_name",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "1c5dda0e613ee57819d4c9534f3bcd8809f313026a187a2eff66fa4f7ba888a5"
}

View File

@ -21,7 +21,7 @@
{ {
"ordinal": 3, "ordinal": 3,
"name": "created_at", "name": "created_at",
"type_info": "Timestamp" "type_info": "Timestamptz"
} }
], ],
"parameters": { "parameters": {

View File

@ -1,24 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folders(parent_folder_id, owner_id, folder_name) VALUES ($1, $2, $3) RETURNING folder_id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "folder_id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Uuid",
"Int4",
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "3dd4a65d3106d742c2221c0589ac68d4621c6e351f9fbb7aa58629ff2d829234"
}

View File

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folders(parent_folder_id, owner_id, folder_name, folder_id) VALUES ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Int4",
"Varchar",
"Uuid"
]
},
"nullable": []
},
"hash": "3faa32dd95822ae8687784817f68e48e726eedd2b7af7e52712974b4f04a8f80"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "SELECT\n user_id, username, email, \n GREATEST (\n similarity (email, $1),\n similarity (username, $1)\n ) as \"similarity!\"\nFROM\n users\nORDER BY\n \"similarity!\" DESC", "query": "SELECT\n user_id, username, email, \n GREATEST (\n similarity (email, $1),\n similarity (username, $1)\n ) as \"similarity!\"\nFROM\n users\nORDER BY\n \"similarity!\" DESC\nLIMIT 20",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -36,5 +36,5 @@
null null
] ]
}, },
"hash": "8d8bf29e632723ba48a19ea52a8466424f7628e84df17178bf26eaca1cc99aca" "hash": "e0d415b13ccf7aa865558395eb6997bfff50762d36cf3742470a897f4588c802"
} }

View File

@ -26,12 +26,12 @@
{ {
"ordinal": 4, "ordinal": 4,
"name": "created_at", "name": "created_at",
"type_info": "Timestamp" "type_info": "Timestamptz"
}, },
{ {
"ordinal": 5, "ordinal": 5,
"name": "updated_at", "name": "updated_at",
"type_info": "Timestamp" "type_info": "Timestamptz"
} }
], ],
"parameters": { "parameters": {

View File

@ -21,7 +21,7 @@
{ {
"ordinal": 3, "ordinal": 3,
"name": "created_at", "name": "created_at",
"type_info": "Timestamp" "type_info": "Timestamptz"
} }
], ],
"parameters": { "parameters": {

View File

@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "WITH\n permitted as (\n SELECT\n folder_id\n FROM\n permissions\n WHERE\n user_id = $1\n )\nSELECT\n folder_id\nFROM\n folders\nWHERE\n folder_id IN (\n SELECT\n folder_id\n FROM\n permitted\n )\n AND parent_folder_id NOT IN (\n SELECT\n folder_id\n FROM\n permitted\n )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "folder_id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false
]
},
"hash": "f9e36f45f25dd2439a7a0b16b6df356a0a2a47e70b6e031ea5a0442adc86725b"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "INSERT INTO users(username, email, hashed_password) VALUES ($1, $2, $3) RETURNING user_id", "query": "INSERT INTO users(username, email, hashed_password) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING RETURNING user_id",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -20,5 +20,5 @@
false false
] ]
}, },
"hash": "879bdad048c3151e54ee70a2c316d90dcc1a0f50a1df2c7681917a2890d082cf" "hash": "fb94ebf44aff9c5c56cc43ef47f571b4dc1fcdcbc595aef4d245ee2454b0a458"
} }

45
Cargo.lock generated
View File

@ -321,9 +321,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.7" version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -920,9 +920,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-util", "futures-util",
@ -1259,9 +1259,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.2" version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1731,18 +1731,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.204" version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.204" version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1814,6 +1814,15 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "signature" name = "signature"
version = "2.2.0" version = "2.2.0"
@ -2156,15 +2165,15 @@ checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.11.0" version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -2255,6 +2264,7 @@ dependencies = [
"mio", "mio",
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -2653,6 +2663,15 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.48.5"

View File

@ -38,7 +38,7 @@ sqlx = { version = "0.8", features = [
"uuid", "uuid",
] } ] }
subtle = "2" subtle = "2"
tokio = { version = "1", features = ["parking_lot", "rt-multi-thread"] } tokio = { version = "1", features = ["parking_lot", "rt-multi-thread", "signal"] }
tokio-util = { version = "0.7" } tokio-util = { version = "0.7" }
tower = { version = "0.4" } tower = { version = "0.4" }
tower-http = { version = "0.5", features = [ tower-http = { version = "0.5", features = [

View File

@ -1,3 +1 @@
INSERT INTO folders(parent_folder_id, owner_id, folder_name) INSERT INTO folders(parent_folder_id, owner_id, folder_name, folder_id) VALUES ($1, $2, $3, $4)
SELECT $1, owner_id, $2 FROM folders WHERE parent_folder_id = $1
RETURNING folder_id

View File

@ -1,5 +1,5 @@
SELECT SELECT
username, users.user_id,
permission_type as "permission_type: PermissionRaw" permission_type as "permission_type: PermissionRaw"
FROM FROM
permissions permissions

View File

@ -8,7 +8,7 @@ WITH
user_id = $1 user_id = $1
) )
SELECT SELECT
folder_id folder_id, owner_id, folder_name, created_at
FROM FROM
folders folders
WHERE WHERE

View File

@ -8,3 +8,4 @@ FROM
users users
ORDER BY ORDER BY
"similarity!" DESC "similarity!" DESC
LIMIT 20

View File

@ -67,10 +67,11 @@ pub async fn get_permissions(
} }
pub async fn get_name(file_id: Uuid, pool: &Pool) -> sqlx::Result<Option<String>> { pub async fn get_name(file_id: Uuid, pool: &Pool) -> sqlx::Result<Option<String>> {
let record = sqlx::query!("SELECT file_name FROM files WHERE file_id = $1", file_id) let name = sqlx::query!("SELECT file_name FROM files WHERE file_id = $1", file_id)
.fetch_optional(pool) .fetch_optional(pool)
.await?; .await?
Ok(record.map(|record| record.file_name)) .map(|record| record.file_name);
Ok(name)
} }
pub async fn delete(file_id: Uuid, pool: &Pool) -> sqlx::Result<bool> { pub async fn delete(file_id: Uuid, pool: &Pool) -> sqlx::Result<bool> {

View File

@ -89,10 +89,24 @@ pub async fn name_exists(parent_folder_id: Uuid, name: &str, pool: &Pool) -> sql
/// Creates a folder in the database. Do not use this function to create the ROOT folder /// Creates a folder in the database. Do not use this function to create the ROOT folder
pub async fn insert(parent_folder_id: Uuid, folder_name: &str, pool: &Pool) -> sqlx::Result<Uuid> { pub async fn insert(parent_folder_id: Uuid, folder_name: &str, pool: &Pool) -> sqlx::Result<Uuid> {
sqlx::query_file!("sql/create_folder.sql", parent_folder_id, folder_name) let folder_id = Uuid::now_v7();
.fetch_one(pool) let owner_id = get_by_id(parent_folder_id, pool)
.await .await?
.map(|record| record.folder_id) .ok_or(sqlx::Error::RowNotFound)?
.owner_id;
let result = sqlx::query_file!(
"sql/create_folder.sql",
parent_folder_id,
owner_id,
folder_name,
folder_id
)
.execute(pool)
.await?;
if result.rows_affected() == 0 {
return Err(sqlx::Error::RowNotFound);
}
Ok(folder_id)
} }
pub fn delete(folder_id: Uuid, pool: &Pool) -> impl Stream<Item = sqlx::Result<Uuid>> + '_ { pub fn delete(folder_id: Uuid, pool: &Pool) -> impl Stream<Item = sqlx::Result<Uuid>> + '_ {

View File

@ -1,8 +1,11 @@
use std::{borrow::Cow, collections::HashMap}; use std::{borrow::Cow, collections::HashMap};
use db::folder::FolderWithoutParentId;
use crate::prelude::*; use crate::prelude::*;
#[derive(sqlx::Type, Debug, Serialize, Deserialize)] #[derive(sqlx::Type, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[sqlx(type_name = "permission")] #[sqlx(type_name = "permission")]
#[sqlx(rename_all = "lowercase")] #[sqlx(rename_all = "lowercase")]
pub enum PermissionRaw { pub enum PermissionRaw {
@ -120,10 +123,10 @@ pub async fn insert(
pub async fn get_all_for_folder( pub async fn get_all_for_folder(
folder_id: Uuid, folder_id: Uuid,
pool: &Pool, pool: &Pool,
) -> sqlx::Result<HashMap<String, PermissionRaw>> { ) -> sqlx::Result<HashMap<i32, PermissionRaw>> {
sqlx::query_file!("sql/get_all_permissions_for_folder.sql", folder_id) sqlx::query_file!("sql/get_all_permissions_for_folder.sql", folder_id)
.fetch(pool) .fetch(pool)
.map_ok(|record| (record.username, record.permission_type)) .map_ok(|record| (record.user_id, record.permission_type))
.try_collect() .try_collect()
.await .await
} }
@ -135,10 +138,16 @@ pub async fn delete_for_folder(folder_id: Uuid, user_id: i32, pool: &Pool) -> sq
.map(|_| ()) .map(|_| ())
} }
pub async fn get_top_level_permitted_folders(user_id: i32, pool: &Pool) -> sqlx::Result<Vec<Uuid>> { pub async fn get_top_level_permitted_folders(
sqlx::query_file!("sql/get_top_level_folder.sql", user_id) user_id: i32,
pool: &Pool,
) -> sqlx::Result<Vec<FolderWithoutParentId>> {
sqlx::query_file_as!(
FolderWithoutParentId,
"sql/get_top_level_folder.sql",
user_id
)
.fetch(pool) .fetch(pool)
.map_ok(|record| record.folder_id)
.try_collect() .try_collect()
.await .await
} }

View File

@ -8,7 +8,7 @@ pub async fn create_user(
pool: &Pool, pool: &Pool,
) -> sqlx::Result<Option<i32>> { ) -> sqlx::Result<Option<i32>> {
let Some(record) = sqlx::query!( let Some(record) = sqlx::query!(
"INSERT INTO users(username, email, hashed_password) VALUES ($1, $2, $3) RETURNING user_id", "INSERT INTO users(username, email, hashed_password) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING RETURNING user_id",
user_name, user_name,
user_email, user_email,
hashed_password hashed_password
@ -80,7 +80,7 @@ pub async fn get(user_id: i32, pool: &Pool) -> sqlx::Result<Option<UserInfo>> {
.await .await
} }
/// Gets the hashed password field by either the email or th username /// Gets the hashed password field by either the email or the username
pub async fn get_hash(search_string: &str, pool: &Pool) -> sqlx::Result<Option<(i32, Vec<u8>)>> { pub async fn get_hash(search_string: &str, pool: &Pool) -> sqlx::Result<Option<(i32, Vec<u8>)>> {
let record = sqlx::query!( let record = sqlx::query!(
"SELECT user_id, hashed_password FROM users WHERE username = $1 OR email = $1", "SELECT user_id, hashed_password FROM users WHERE username = $1 OR email = $1",

View File

@ -14,15 +14,9 @@ pub async fn delete(
.await .await
.can_write_guard()?; .can_write_guard()?;
let deleted = db::file::delete(params.file_id, &state.pool) db::file::delete(params.file_id, &state.pool)
.await .await
.handle_internal("Error deleting the file")?; .handle_internal("Error deleting the file")?;
if !deleted {
return Err(GeneralError::message(
StatusCode::NOT_FOUND,
"Item not found",
)); // Will not happen most of the time due to can write guard
}
state state
.storage .storage

View File

@ -8,7 +8,7 @@ pub struct Params {
pub async fn delete( pub async fn delete(
State(state): State<AppState>, State(state): State<AppState>,
claims: Claims, claims: Claims,
Json(params): Json<Params>, Query(params): Query<Params>,
) -> GeneralResult<()> { ) -> GeneralResult<()> {
let root = db::folder::get_root(claims.user_id, &state.pool) let root = db::folder::get_root(claims.user_id, &state.pool)
.await .await

View File

@ -1,3 +1,4 @@
use db::{file::FileWithoutParentId, folder::FolderWithoutParentId};
use tokio::try_join; use tokio::try_join;
use super::list::Params; use super::list::Params;
@ -6,13 +7,13 @@ use crate::prelude::*;
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct FolderStructure { pub struct FolderStructure {
#[serde(flatten)] #[serde(flatten)]
folder_base: db::folder::FolderWithoutParentId, folder_base: FolderWithoutParentId,
folders: Vec<FolderStructure>, folders: Vec<FolderStructure>,
files: Vec<db::file::FileWithoutParentId>, files: Vec<FileWithoutParentId>,
} }
impl From<db::folder::FolderWithoutParentId> for FolderStructure { impl From<FolderWithoutParentId> for FolderStructure {
fn from(value: db::folder::FolderWithoutParentId) -> Self { fn from(value: FolderWithoutParentId) -> Self {
FolderStructure { FolderStructure {
folder_base: value, folder_base: value,
folders: Vec::new(), folders: Vec::new(),

View File

@ -9,7 +9,7 @@ pub struct Params {
pub async fn delete( pub async fn delete(
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
Json(params): Json<Params>, Query(params): Query<Params>,
) -> GeneralResult<StatusCode> { ) -> GeneralResult<StatusCode> {
if params.user_id != claims.user_id { if params.user_id != claims.user_id {
db::folder::get_permissions(params.folder_id, claims.user_id, &pool) db::folder::get_permissions(params.folder_id, claims.user_id, &pool)

View File

@ -13,13 +13,13 @@ pub async fn get(
State(pool): State<Pool>, State(pool): State<Pool>,
Query(params): Query<Params>, Query(params): Query<Params>,
claims: Claims, claims: Claims,
) -> GeneralResult<Json<HashMap<String, PermissionRaw>>> { ) -> GeneralResult<Json<HashMap<i32, PermissionRaw>>> {
db::folder::get_permissions(params.folder_id, claims.user_id, &pool) db::folder::get_permissions(params.folder_id, claims.user_id, &pool)
.await .await
.can_manage_guard()?; .can_manage_guard()?;
let permissions = db::permissions::get_all_for_folder(params.folder_id, &pool) db::permissions::get_all_for_folder(params.folder_id, &pool)
.await .await
.handle_internal("Error getting permissions")?; .handle_internal("Error getting permissions")
Ok(Json(permissions)) .map(Json)
} }

View File

@ -1,11 +1,13 @@
use db::folder::FolderWithoutParentId;
use crate::prelude::*; use crate::prelude::*;
pub async fn get_top_level( pub async fn get_top_level(
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
) -> GeneralResult<Json<Vec<Uuid>>> { ) -> GeneralResult<Json<Vec<FolderWithoutParentId>>> {
let folders = db::permissions::get_top_level_permitted_folders(claims.user_id, &pool) db::permissions::get_top_level_permitted_folders(claims.user_id, &pool)
.await .await
.handle_internal("Error reading from the database")?; .handle_internal("Error reading from the database")
Ok(Json(folders)) .map(Json)
} }

View File

@ -18,7 +18,7 @@ pub async fn set(
if params.folder_id == root { if params.folder_id == root {
return Err(GeneralError::message( return Err(GeneralError::message(
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
"Cannot delete the root folder", "Cannot set permissions for the root folder",
)); ));
} }
@ -26,6 +26,13 @@ pub async fn set(
.await .await
.can_manage_guard()?; .can_manage_guard()?;
if params.user_id == claims.user_id {
return Err(GeneralError::message(
StatusCode::BAD_REQUEST,
"Cannot set your own permissions",
));
}
let folder_info = db::folder::get_by_id(params.folder_id, &pool) let folder_info = db::folder::get_by_id(params.folder_id, &pool)
.await .await
.handle_internal("Error getting folder info")? .handle_internal("Error getting folder info")?
@ -33,7 +40,7 @@ pub async fn set(
if folder_info.owner_id == params.user_id { if folder_info.owner_id == params.user_id {
return Err(GeneralError::message( return Err(GeneralError::message(
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
"Cannot set permissions of the folder owner", "Cannot set permissions of the folder's owner",
)); ));
} }

View File

@ -8,11 +8,11 @@ pub struct Params {
type Response = GeneralResult<Json<db::users::UserInfo>>; type Response = GeneralResult<Json<db::users::UserInfo>>;
pub async fn get(State(pool): State<Pool>, Query(params): Query<Params>) -> Response { pub async fn get(State(pool): State<Pool>, Query(params): Query<Params>) -> Response {
let info = db::users::get(params.user_id, &pool) db::users::get(params.user_id, &pool)
.await .await
.handle_internal("Error getting the user")? .handle_internal("Error getting the user")?
.handle(StatusCode::NOT_FOUND, "User not found")?; .handle(StatusCode::NOT_FOUND, "User not found")
Ok(Json(info)) .map(Json)
} }
pub async fn current(state: State<Pool>, claims: Claims) -> Response { pub async fn current(state: State<Pool>, claims: Claims) -> Response {

View File

@ -4,7 +4,7 @@ use crate::prelude::*;
#[derive(Deserialize, Debug, Validate)] #[derive(Deserialize, Debug, Validate)]
pub struct Params { pub struct Params {
#[validate(email)] #[validate(length(min = 3, max = 10))]
username: String, username: String,
#[validate(email)] #[validate(email)]
email: String, email: String,
@ -15,7 +15,7 @@ pub async fn put(
claims: Claims, claims: Claims,
Json(params): Json<Params>, Json(params): Json<Params>,
) -> GeneralResult<Json<db::users::UserInfo>> { ) -> GeneralResult<Json<db::users::UserInfo>> {
params.validate().handle_validation()?; params.validate()?;
db::users::update(claims.user_id, &params.username, &params.email, &pool) db::users::update(claims.user_id, &params.username, &params.email, &pool)
.await .await
.handle_internal("Error updating the user") .handle_internal("Error updating the user")

View File

@ -48,7 +48,7 @@ pub async fn register(
State(pool): State<Pool>, State(pool): State<Pool>,
Form(params): Form<Params>, Form(params): Form<Params>,
) -> GeneralResult<Json<Token>> { ) -> GeneralResult<Json<Token>> {
params.validate().handle_validation()?; params.validate()?;
let password = HashedBytes::hash_bytes(params.password.as_bytes()).as_bytes(); let password = HashedBytes::hash_bytes(params.password.as_bytes()).as_bytes();
let id = db::users::create_user(&params.username, &params.email, &password, &pool) let id = db::users::create_user(&params.username, &params.email, &password, &pool)

View File

@ -41,6 +41,12 @@ impl IntoResponse for GeneralError {
} }
} }
impl From<validator::ValidationErrors> for GeneralError {
fn from(value: validator::ValidationErrors) -> Self {
GeneralError::message(StatusCode::BAD_REQUEST, value.to_string())
}
}
pub type GeneralResult<T> = Result<T, GeneralError>; pub type GeneralResult<T> = Result<T, GeneralError>;
pub trait ErrorHandlingExt<T> pub trait ErrorHandlingExt<T>
@ -92,19 +98,6 @@ pub trait ItemNotFoundExt<T> {
impl<T> ItemNotFoundExt<T> for Option<T> { impl<T> ItemNotFoundExt<T> for Option<T> {
fn item_not_found(self) -> GeneralResult<T> { fn item_not_found(self) -> GeneralResult<T> {
self.ok_or(GeneralError::const_message( self.handle(StatusCode::NOT_FOUND, "Item not found")
StatusCode::NOT_FOUND,
"Item not found",
))
}
}
pub trait ValidationExt<T> {
fn handle_validation(self) -> GeneralResult<T>;
}
impl<T> ValidationExt<T> for Result<T, validator::ValidationErrors> {
fn handle_validation(self) -> GeneralResult<T> {
self.map_err(|err| GeneralError::message(StatusCode::BAD_REQUEST, err.to_string()))
} }
} }

View File

@ -10,7 +10,7 @@ use std::{env, net::Ipv4Addr};
use auth::HashedBytes; use auth::HashedBytes;
use axum::{extract::FromRef, routing::post, Router}; use axum::{extract::FromRef, routing::post, Router};
use file_storage::FileStorage; use file_storage::FileStorage;
use tokio::net::TcpListener; use tokio::{net::TcpListener, signal};
type Pool = sqlx::postgres::PgPool; type Pool = sqlx::postgres::PgPool;
@ -66,11 +66,37 @@ async fn main() -> anyhow::Result<()> {
let addr = (Ipv4Addr::UNSPECIFIED, 3000); let addr = (Ipv4Addr::UNSPECIFIED, 3000);
let listener = TcpListener::bind(addr).await?; let listener = TcpListener::bind(addr).await?;
axum::serve(listener, router).await?; axum::serve(listener, router)
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(()) Ok(())
} }
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {},
() = terminate => {},
}
}
fn app(state: AppState) -> Router { fn app(state: AppState) -> Router {
use axum::{http::header, routing::get}; use axum::{http::header, routing::get};
use endpoints::{ use endpoints::{

View File

@ -1,10 +1,7 @@
pub(crate) use crate::{ pub(crate) use crate::{
auth::Claims, auth::Claims,
db::{self, permissions::PermissionExt as _}, db::{self, permissions::PermissionExt as _},
errors::{ errors::{ErrorHandlingExt as _, GeneralError, GeneralResult, ItemNotFoundExt as _},
ErrorHandlingExt as _, GeneralError, GeneralResult, ItemNotFoundExt as _,
ValidationExt as _,
},
AppState, Pool, AppState, Pool,
}; };
pub use axum::{ pub use axum::{