fix(backend): match the authenticated user into get/download/delete operations

This commit is contained in:
2026-06-03 23:34:29 +03:00
parent 41ea8b598a
commit 0f6bfe2111
11 changed files with 449 additions and 14 deletions

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM sessions WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "11e96cfd8c2736f13ce55975ea910dd68640f6f14e38a4b3342d514804e3de27"
}

View File

@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT *\n FROM users u\n WHERE u.username = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "username",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "password_hash",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "24fd2306e40be21dc82660ec3f42f95928849706ecb75fe1cb8b606aa3cf2971"
}

View File

@@ -0,0 +1,58 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT *\n FROM file_records fr\n WHERE user_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "file_type",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "size",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "uploaded_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "storage_key",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "user_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "31e1333f84168ce0ba75c474f2a1d7a0d0c4d04857130da327b07821f20cf07c"
}

View File

@@ -0,0 +1,62 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO file_records (user_id, name, file_type, size, storage_key)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING *\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "file_type",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "size",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "uploaded_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "storage_key",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "user_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8",
"Text",
"Text",
"Int8",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "638f29a940b4514c1ebd047975e1e1fb8d9b647dbe6ef025dbb13b6417b6e91e"
}

View File

@@ -0,0 +1,41 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO users (username, password_hash)\n VALUES ($1, $2)\n RETURNING *\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "username",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "password_hash",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "65d6e198b36dc758d3da0e29374e5fabc8633a63f6c213b61a7e1902baad157b"
}

View File

@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT *\n FROM sessions s\n WHERE s.id = $1\n AND s.expires_at > NOW()\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Text"
},
{
"ordinal": 1,
"name": "user_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "expires_at",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "7d78918485f2c98b302a9d4c0801ba3a0f3af9273db03691317ab5e8676d61b7"
}

View File

@@ -0,0 +1,42 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO sessions (id, user_id, expires_at)\n VALUES ($1, $2, $3)\n RETURNING *\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Text"
},
{
"ordinal": 1,
"name": "user_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "expires_at",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Timestamptz"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "89c32ebfbbdf3f7d6fe47becb7b074a737b1627bc9c4cabbf2abe145d842b8f9"
}

View File

@@ -0,0 +1,59 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT *\n FROM file_records fr\n WHERE fr.id = $1\n AND fr.user_id = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "file_type",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "size",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "uploaded_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "storage_key",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "user_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "d059130365f7e4b3a6ae49bee073897a12e58a29b0b2e5c814ff8cb392e32fa0"
}

View File

@@ -0,0 +1,59 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM file_records\n WHERE id = $1\n AND user_id = $2\n RETURNING *\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "file_type",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "size",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "uploaded_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "storage_key",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "user_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "f703be0699214db15f5d65ea7ec3186407f6a72cc1596f652fcdcd7c7f49562d"
}

View File

@@ -106,12 +106,13 @@ impl FileRepository {
pub async fn download_file(
&self,
file_id: i64,
user_id: i64,
) -> Result<impl Stream<Item = std::io::Result<Bytes>> + use<>, LoftError> {
info!(
"Fetching metadata of file \"{}\" from file_records",
file_id
);
let record = self.get_file(file_id).await?;
let record = self.get_file(file_id, user_id).await?;
info!("Downloading file \"{}\"", file_id);
let reader = self.op.reader(&record.storage_key).await.unwrap();
let stream = reader.into_bytes_stream(0..).await.unwrap();
@@ -119,7 +120,7 @@ impl FileRepository {
Ok(stream)
}
pub async fn get_file(&self, file_id: i64) -> Result<FileRecord, LoftError> {
pub async fn get_file(&self, file_id: i64, user_id: i64) -> Result<FileRecord, LoftError> {
info!(
"Fetching metadata of file \"{}\" from file_records",
file_id
@@ -130,8 +131,10 @@ impl FileRepository {
SELECT *
FROM file_records fr
WHERE fr.id = $1
AND fr.user_id = $2
"#,
file_id
file_id,
user_id
)
.fetch_optional(&self.pool)
.await
@@ -141,12 +144,12 @@ impl FileRepository {
Ok(record)
}
pub async fn delete_file(&self, file_id: i64) -> Result<FileRecord, LoftError> {
pub async fn delete_file(&self, file_id: i64, user_id: i64) -> Result<FileRecord, LoftError> {
info!(
"Fetching metadata of file \"{}\" from file_records",
file_id
);
let record = self.get_file(file_id).await?;
let record = self.get_file(file_id, user_id).await?;
info!("Deleting file bytes \"{}\"", file_id);
self.op.delete(&record.storage_key).await.unwrap();
@@ -156,9 +159,11 @@ impl FileRepository {
r#"
DELETE FROM file_records
WHERE id = $1
AND user_id = $2
RETURNING *
"#,
file_id
file_id,
user_id
)
.fetch_optional(&self.pool)
.await
@@ -407,7 +412,10 @@ mod tests {
.await
.unwrap();
let _downloaded = file_repository.download_file(file_record.id).await.unwrap();
let _downloaded = file_repository
.download_file(file_record.id, user.id)
.await
.unwrap();
//TODO: add assert
@@ -422,7 +430,7 @@ mod tests {
truncate_file_records(&file_repository.pool).await;
assert!(matches!(
file_repository.download_file(i64::MAX).await,
file_repository.download_file(i64::MAX, 1).await,
Err(LoftError::FileIdNotFound)
));
}
@@ -453,10 +461,13 @@ mod tests {
.await
.unwrap();
file_repository.delete_file(file_record.id).await.unwrap();
file_repository
.delete_file(file_record.id, user.id)
.await
.unwrap();
assert!(matches!(
file_repository.download_file(file_record.id).await,
file_repository.download_file(file_record.id, user.id).await,
Err(LoftError::FileIdNotFound)
));
truncate_file_records(&file_repository.pool).await;
@@ -469,7 +480,7 @@ mod tests {
let file_repository = file_repository().await.unwrap();
assert!(matches!(
file_repository.delete_file(99).await,
file_repository.delete_file(99, 1).await,
Err(LoftError::FileIdNotFound)
));
}

View File

@@ -57,28 +57,37 @@ async fn upload_file(
#[axum::debug_handler]
async fn get_file(
State(file_repository): State<FileRepository>,
ctx: Ctx,
Path(file_id): Path<u64>,
) -> Result<Json<FileRecord>, LoftError> {
let record = file_repository.get_file(file_id as i64).await?;
let record = file_repository
.get_file(file_id as i64, ctx.user_id())
.await?;
Ok(Json(record))
}
#[axum::debug_handler]
async fn download_file(
State(file_repository): State<FileRepository>,
ctx: Ctx,
Path(file_id): Path<u64>,
) -> Result<impl IntoResponse, LoftError> {
let stream = file_repository.download_file(file_id as i64).await?;
let stream = file_repository
.download_file(file_id as i64, ctx.user_id())
.await?;
Ok(Body::from_stream(stream))
}
async fn delete_file(
State(file_repository): State<FileRepository>,
ctx: Ctx,
Path(file_id): Path<u64>,
) -> Result<Json<FileRecord>, LoftError> {
info!("handler: delete_file");
let file = file_repository.delete_file(file_id as i64).await?;
let file = file_repository
.delete_file(file_id as i64, ctx.user_id())
.await?;
Ok(Json(file))
}