Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
2d43e53
add model s3Configure
abolfazlshs80 Jun 11, 2025
6738d8d
bind appsetting to model s3
abolfazlshs80 Jun 11, 2025
db73305
add configure s3 to appsetting
abolfazlshs80 Jun 11, 2025
8592baa
use bind model to appsetting
abolfazlshs80 Jun 11, 2025
be0c366
add migration to update Slug column type and create unique index on B…
abolfazlshs80 Jun 11, 2025
76f59f1
add duplicate PackageReference for AWSSDK.S3 in project file
abolfazlshs80 Jun 11, 2025
ec8907c
add DirectoryTypes enum to define file categories
abolfazlshs80 Jun 11, 2025
5f27d8a
refactor FolderNameStatic to use GetDirectoryName method for director…
abolfazlshs80 Jun 11, 2025
d94d695
fix: correct method name and parameter in IFileUploaderService interface
abolfazlshs80 Jun 11, 2025
52476d3
fix: correct method name from UpdloadFile to UploadFile in LocalFileU…
abolfazlshs80 Jun 11, 2025
7fe2a3a
feat: implement S3FileUploaderService for file upload and deletion us…
abolfazlshs80 Jun 11, 2025
4d5527b
fix: simplify ConvertForBookPathImage and ConvertForBookPathFile meth…
abolfazlshs80 Jun 11, 2025
b7d6b9d
fix: update IFileUploaderService to use S3FileUploaderService instead…
abolfazlshs80 Jun 11, 2025
e53459a
fix: correct method name from UpdloadFile to UploadFile and simplify …
abolfazlshs80 Jun 11, 2025
9102cb4
Fix formatting in Refhub.csproj
MehdiKh-hub Jun 12, 2025
5a7367f
Enhance BookController for S3 file downloads
MehdiKh-hub Jun 12, 2025
4e68b7f
Remove associated BookAuthors on Book deletion
MehdiKh-hub Jun 12, 2025
2fa8835
Add DownloadFileAsync method to S3FileUploaderService
MehdiKh-hub Jun 12, 2025
e4405b1
Add DownloadFileAsync method to IFileUploaderService
MehdiKh-hub Jun 12, 2025
dc3a765
Add UserSecretsId and secure AWS credentials
MehdiKh-hub Jun 12, 2025
6c8d855
Add cascade delete to BookAuthor foreign keys
MehdiKh-hub Jun 12, 2025
8f37956
Enhance BookService with file uploads and deletion logic
MehdiKh-hub Jun 12, 2025
b9f31aa
Update S3 URL generation logic in S3FileUploaderService
MehdiKh-hub Jun 12, 2025
a559496
Add S3 support and improve error handling in BookController
MehdiKh-hub Jun 24, 2025
f82eb34
Comment out Book entity configuration in BookAuthor
MehdiKh-hub Jun 24, 2025
637b8b1
Refactor S3FileUploaderService URL handling
MehdiKh-hub Jun 24, 2025
5981090
Enhance file download handling in BookController
MehdiKh-hub Jun 25, 2025
8060e86
Enhance LocalFileUploaderService functionality
MehdiKh-hub Jun 25, 2025
7658b5c
Update IFileUploaderService: Modify DownloadFileAsync
MehdiKh-hub Jun 25, 2025
546c773
Remove default case for unsupported directory types
MehdiKh-hub Jun 25, 2025
7b6aa83
Update S3Configuration for immutability and validation
MehdiKh-hub Jun 28, 2025
badbb16
Remove DownloadFileAsync method from IFileUploaderService
MehdiKh-hub Jun 28, 2025
1011198
Add cancellation support to DownloadFileAsync method
MehdiKh-hub Jun 28, 2025
a87123f
Enhance DownloadFileAsync with CancellationToken support
MehdiKh-hub Jun 28, 2025
1eb193d
Fix CI (#103)
hootanht Jul 1, 2025
e54624d
merg of develop branch on Add S3 branch
MehdiKh-hub Jul 1, 2025
3149998
Merge branch 'Add-S3' of https://github.com/Refhub-ir/Refhub into Add-S3
MehdiKh-hub Jul 1, 2025
bdda8a2
update confilict Migration IX_Books_Slug on Add UniqueIndexToBookSlug…
abolfazlshs80 Jul 12, 2025
a34d966
add namespace ExtensionMethod
abolfazlshs80 Jul 12, 2025
a85b80d
remove namespace ExtensionMethod
abolfazlshs80 Jul 12, 2025
55cf60c
move to secret.josn
abolfazlshs80 Jul 12, 2025
9028f8c
remove unuse namespace
abolfazlshs80 Jul 12, 2025
6aff3cf
convert internal to public message resource
abolfazlshs80 Jul 12, 2025
cd25eae
add SaveChangesAsync on CreateAnotherAsync
abolfazlshs80 Jul 12, 2025
109691b
merg namespace
abolfazlshs80 Jul 12, 2025
af15a97
PublicResXFileCodeGenerator
abolfazlshs80 Jul 12, 2025
1540595
remove unuse namepace
abolfazlshs80 Jul 12, 2025
3aa2a5f
fix PublicResXFileCodeGeneratorTargets
abolfazlshs80 Jul 12, 2025
166a026
add BucketNameStatic.GetName
abolfazlshs80 Jul 12, 2025
d698f08
add enum BucketNames
abolfazlshs80 Jul 12, 2025
cdd3c79
update namespace
abolfazlshs80 Jul 12, 2025
9425cf7
update IFileUploaderService
abolfazlshs80 Jul 12, 2025
7e7822e
add BucketNameStatic
abolfazlshs80 Jul 12, 2025
1745ceb
use BucketNameStatic.GetName on bookService
abolfazlshs80 Jul 12, 2025
1e7ca35
use bucketName by paramtr
abolfazlshs80 Jul 12, 2025
2d34fb9
add ILogger<BookController>
abolfazlshs80 Jul 15, 2025
e245c01
add AnyAsync book Service
abolfazlshs80 Jul 15, 2025
d5f3a3e
update RegionEndpoint for s3
abolfazlshs80 Jul 15, 2025
1331ac2
change name BindAppSettingToModelExtensionMethod
abolfazlshs80 Jul 15, 2025
9e79c58
remove pathExtensionMethod
abolfazlshs80 Jul 15, 2025
dd31d93
use _messageService
abolfazlshs80 Jul 15, 2025
1448a4c
remove comment code
abolfazlshs80 Jul 15, 2025
45838b7
refactor GetKey code on S3
abolfazlshs80 Jul 15, 2025
b0a3195
add FileDownloadException
abolfazlshs80 Jul 15, 2025
1546e77
add message DownloadError,InvalidFileName,FileNotFound
abolfazlshs80 Jul 15, 2025
778a2de
rename parameter
abolfazlshs80 Jul 15, 2025
7dbbbe5
add resource for ImagePath on updateBookvm
abolfazlshs80 Jul 15, 2025
9c07f53
add message Book_ImagePathMustBeUrl
abolfazlshs80 Jul 15, 2025
49c79bc
fix name action DownloadFile
abolfazlshs80 Jul 15, 2025
732fda8
update resource message for ImagePath updatebookVm
abolfazlshs80 Jul 15, 2025
90724af
Update Refhub/Tools/Static/BucketNameStatic.cs
hootanht Jul 15, 2025
3e22c41
use condition for imagePath
abolfazlshs80 Jul 15, 2025
caa44e7
update AWSSDK.S3 4.0.4.2
abolfazlshs80 Jul 15, 2025
e9c7d76
use FileDownloadException
abolfazlshs80 Jul 15, 2025
f762f4c
update BookAuthorConfiguration
abolfazlshs80 Jul 15, 2025
4f5c55a
add sqlQuery
abolfazlshs80 Jul 15, 2025
354aa35
convert englishMessage
abolfazlshs80 Jul 15, 2025
dbbcb9a
Merge branch 'Add-S3' of https://github.com/Refhub-ir/Refhub into Add-S3
abolfazlshs80 Jul 15, 2025
7963303
update S3FileUploaderService for download by MemoryStream
abolfazlshs80 Jul 15, 2025
e3a681a
update
abolfazlshs80 Jul 15, 2025
07c5fd5
update
abolfazlshs80 Jul 15, 2025
74cdaf0
🐛 fix(book): resolve file download issues and improve error handling
abolfazlshs80 Jul 18, 2025
69f2998
🐛 fix(book): prevent duplicate author creation
abolfazlshs80 Jul 18, 2025
1bd1c6e
🐛 fix(file): handle file not found exception correctly
abolfazlshs80 Jul 18, 2025
27fe515
🐛 fix(file): correct parameter name in DownloadFileAsync
abolfazlshs80 Jul 18, 2025
13f46ca
🐛 fix(book): fix download file url parameter
abolfazlshs80 Jul 18, 2025
1db68a4
🐛 fix(uploader): correct file path for local uploader
abolfazlshs80 Jul 18, 2025
64324dc
♻️ refactor(upload): remove local file uploader service
abolfazlshs80 Jul 18, 2025
3b0e239
🐛 fix(s3): correct file download logic
abolfazlshs80 Jul 18, 2025
7d52d82
⚡️ perf(book): optimize book retrieval query
abolfazlshs80 Jul 18, 2025
57ab437
🐛 fix(data): cascade delete behavior in BookAuthorConfiguration
abolfazlshs80 Jul 18, 2025
a89a44f
🐛 fix(data): cascade delete related books
abolfazlshs80 Jul 18, 2025
7436aff
🐛 fix(s3): enforce non-nullable bucket name for s3 operations
abolfazlshs80 Jul 18, 2025
6544595
♻️ refactor(fileUploader): standardize parameter naming for file storage
abolfazlshs80 Jul 18, 2025
075771d
🐛 fix(book): resolve download link issue in book details view
abolfazlshs80 Jul 22, 2025
041ee3d
🐛 fix(book): remove duplicate download endpoint
abolfazlshs80 Jul 22, 2025
91055dd
🐛 fix(s3): set uploaded file ACL to private
abolfazlshs80 Jul 22, 2025
824547c
🐛 fix(data): prevent cascade delete for related books
abolfazlshs80 Jul 22, 2025
34c7087
♻️ refactor(s3): remove unused bucket name assignment
abolfazlshs80 Jul 22, 2025
6a71c2c
♻️ refactor(s3): remove bucket name validation
abolfazlshs80 Jul 22, 2025
c4c9038
💄 style(managebook): improve image display logic in update view
abolfazlshs80 Jul 22, 2025
f196cb4
♻️ refactor(upload): simplify s3 file upload method
abolfazlshs80 Jul 22, 2025
73eb368
🔥 chore(static): remove FolderNameStatic class
abolfazlshs80 Jul 22, 2025
0adbe11
🐛 fix(book): validate file URL before download
abolfazlshs80 Jul 22, 2025
51873f0
✨ feat(validation): add validation scripts partial view
abolfazlshs80 Jul 22, 2025
5ccefd5
🐛 fix(BookService): handle exceptions during author creation
abolfazlshs80 Jul 22, 2025
7880d5a
✨ feat(managebook): add validation scripts partial
abolfazlshs80 Jul 22, 2025
e7a409b
🐛 fix(book): correct file URL validation logic
abolfazlshs80 Jul 22, 2025
5edf84c
🐛 fix(S3FileUploader): add validation for fileUrl and bucketName
abolfazlshs80 Jul 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 61 additions & 54 deletions Refhub/Areas/Admin/Views/ManageBook/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,77 @@
@using Refhub.Tools.ExtensionMethod
@model IEnumerable<BookVM>
@{
Layout = "_AdminLayout";
ViewBag.TitleHeaderPage = "مدیریت کتاب ها";
Layout = "_AdminLayout";
ViewBag.TitleHeaderPage = "مدیریت کتاب ها";
}
@{
var searchText = Context.Request.Query["searchtext"].ToString();
var searchText = Context.Request.Query["searchtext"].ToString();
}

<section class="content">
<div class="container-fluid">
<div class="container-fluid">

<!-- /.row -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<!-- /.row -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">

<a type="button" class="btn btn-block btn-success col-2" asp-area="Admin" asp-controller="ManageBook" asp-action="Create">ساخت کتاب جدید</a>
<div class="card-tools">
<form method="get" asp-action="Index" asp-controller="ManageBook" asp-area="Admin">
<div class="input-group input-group-sm" style="width: 200px;">
<input type="text" name="searchtext" class="form-control float-right" placeholder="جستجو"
value="@searchText" />
<div class="input-group-append">
<button type="submit" class="btn btn-default">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</form>
</div>
<a type="button" class="btn btn-block btn-success col-2" asp-area="Admin" asp-controller="ManageBook" asp-action="Create">ساخت کتاب جدید</a>
<div class="card-tools">
<form method="get" asp-action="Index" asp-controller="ManageBook" asp-area="Admin">
<div class="input-group input-group-sm" style="width: 200px;">
<input type="text" name="searchtext" class="form-control float-right" placeholder="جستجو"
value="@searchText" />
<div class="input-group-append">
<button type="submit" class="btn btn-default">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</form>
</div>

</div>
<!-- /.card-header -->
<div class="card-body table-responsive p-0">
<table class="table table-hover">
<tbody>
<tr>
<th style="width: 10px">#</th>
<th>نام کتاب</th>
<th>عملیات</th>
</tr>
</div>
<!-- /.card-header -->
<div class="card-body table-responsive p-0">
<table class="table table-hover">
<tbody>
<tr>
<th style="width: 10px">#</th>
<th>نام کتاب</th>
<th>عملیات</th>
</tr>

@foreach (var item in Model)
{
<tr>
<td><a href="#"> <img src="@item.ImagePath.ConvertForBookPathImage()" width="50" height="50" /></a></td>
<td>@item.Title</td>
<td>
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger">حذف</a>
<a asp-action="Update" asp-route-id="@item.Id" class="btn btn-info">ویرایش</a>
</td>
</tr>
}

@foreach (var item in Model)
{
<tr>
<td>
<a href="#">
@if (!string.IsNullOrEmpty(item.ImagePath))
{
<img src="@item.ImagePath" width="50" height="50" />
}
</a>
</td>
<td>@item.Title</td>
<td>
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger">حذف</a>
<a asp-action="Update" asp-route-id="@item.Id" class="btn btn-info">ویرایش</a>
</td>
</tr>
}

</tbody>
</table>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
</div><!-- /.row -->
</div><!-- /.container-fluid -->

</tbody>
</table>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
</div><!-- /.row -->
</div><!-- /.container-fluid -->
</section>
@* @section Scripts {
<partial name="_ValidationScriptsPartial" />
Expand Down
7 changes: 6 additions & 1 deletion Refhub/Areas/Admin/Views/ManageBook/Update.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@

</div>
<div class="form-group">
<img src="@Model.ImagePath.ConvertForBookPathImage()" height="100" width="100"/>
@if (!string.IsNullOrEmpty(Model.ImagePath))
{
<img src="@Model.ImagePath" height="100" width="100" />
}

<label asp-for="Image">ارسال تصویر</label>

<input type="file" class="form-control" asp-for="Image">
Expand All @@ -72,5 +76,6 @@
</div><!-- /.container-fluid -->
</section>
@section Scripts {

<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
5 changes: 4 additions & 1 deletion Refhub/Components/Views/Home/BestBookView.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
<div class="col-sm-6 col-lg-4 col-xl-3">
<div class="card shadow h-100">
<!-- Image -->
<img src="@item.ImagePath.ConvertForBookPathImage()" class="card-img-top" alt="@item.Title">
@if (!string.IsNullOrEmpty(item.ImagePath))
{
<img src="@item.ImagePath" class="card-img-top" alt="@item.Title">
}
<!-- Card body -->
<div class="card-body pb-0">
<!-- Badge and favorite -->
Expand Down
130 changes: 66 additions & 64 deletions Refhub/Components/Views/Home/LastBookView.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,72 @@


<section>
<div class="container">
<!-- Title -->
<div class="row mb-4">
<div class="col-lg-8 mx-auto text-center">
<h2 class="fs-3">اخرین کتال ها</h2>
<p class="mb-0">هر موضوعی را در هر زمان مطالعه کنید. هزاران کتاب آموزشی را با کمترین قیمت جستجو کنید!</p>
</div>
</div>
<div class="container">
<!-- Title -->
<div class="row mb-4">
<div class="col-lg-8 mx-auto text-center">
<h2 class="fs-3">اخرین کتال ها</h2>
<p class="mb-0">هر موضوعی را در هر زمان مطالعه کنید. هزاران کتاب آموزشی را با کمترین قیمت جستجو کنید!</p>
</div>
</div>


<!-- Tabs content START -->
<div class="tab-content" id="course-pills-tabContent">
<!-- Content START -->
<div class="tab-pane fade show active" id="course-pills-tabs-1" role="tabpanel" aria-labelledby="course-pills-tab-1">
<div class="row g-4">

@foreach (var item in Model)
{
<!-- Card item START -->
<div class="col-sm-6 col-lg-4 col-xl-3">
<div class="card shadow h-100">
<!-- Image -->
<img src="@item.ImagePath.ConvertForBookPathImage()" class="card-img-top" alt="@item.Title">
<!-- Card body -->
<div class="card-body pb-0">
<!-- Badge and favorite -->
<div class="d-flex justify-content-between mb-2">
<a href="#" class="badge bg-purple bg-opacity-10 text-purple">@item.CategoryName</a>
<a href="#" class="h6 mb-0"><i class="far fa-heart"></i></a>
</div>
<!-- Title -->
<h5 class="card-title fw-normal"><a href="#"> @item.Title</a></h5>
@* <p class="mb-2 text-truncate-2">با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک</p> *@
<!-- Rating star -->
<ul class="list-inline mb-0">
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="far fa-star text-warning"></i></li>
<li class="list-inline-item ms-2 h6 fw-light mb-0">4.0/5.0</li>
</ul>
</div>
<!-- Card footer -->
<div class="card-footer pt-0 pb-3">
<hr>
<div class="d-flex justify-content-between">
<span class="h6 fw-light mb-0"><i class="far fa-clock text-danger me-2"></i>@item.Authores</span>
@* <span class="h6 fw-light mb-0"><i class="fas fa-table text-orange me-2"></i>15 ویدیو</span> *@
</div>
</div>
</div>
</div>
<!-- Card item END -->
}


<!-- Card item END -->
</div> <!-- Row END -->
</div>
<!-- Content END -->

<!-- Content END -->
</div>
<!-- Tabs content END -->
</div>
<!-- Tabs content START -->
<div class="tab-content" id="course-pills-tabContent">
<!-- Content START -->
<div class="tab-pane fade show active" id="course-pills-tabs-1" role="tabpanel" aria-labelledby="course-pills-tab-1">
<div class="row g-4">

@foreach (var item in Model)
{
<!-- Card item START -->
<div class="col-sm-6 col-lg-4 col-xl-3">
<div class="card shadow h-100">
<!-- Image -->
@if (!string.IsNullOrEmpty(item.ImagePath))
{
<img src="@item.ImagePath" class="card-img-top" alt="@item.Title">
}
<!-- Card body -->
<div class="card-body pb-0">
<!-- Badge and favorite -->
<div class="d-flex justify-content-between mb-2">
<a href="#" class="badge bg-purple bg-opacity-10 text-purple">@item.CategoryName</a>
<a href="#" class="h6 mb-0"><i class="far fa-heart"></i></a>
</div>
<!-- Title -->
<h5 class="card-title fw-normal"><a href="#"> @item.Title</a></h5>
@* <p class="mb-2 text-truncate-2">با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک</p> *@
<!-- Rating star -->
<ul class="list-inline mb-0">
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="fas fa-star text-warning"></i></li>
<li class="list-inline-item me-0 small"><i class="far fa-star text-warning"></i></li>
<li class="list-inline-item ms-2 h6 fw-light mb-0">4.0/5.0</li>
</ul>
</div>
<!-- Card footer -->
<div class="card-footer pt-0 pb-3">
<hr>
<div class="d-flex justify-content-between">
<span class="h6 fw-light mb-0"><i class="far fa-clock text-danger me-2"></i>@item.Authores</span>
@* <span class="h6 fw-light mb-0"><i class="fas fa-table text-orange me-2"></i>15 ویدیو</span> *@
</div>
</div>
</div>
</div>
<!-- Card item END -->
}


<!-- Card item END -->
</div> <!-- Row END -->
</div>
<!-- Content END -->
<!-- Content END -->
</div>
<!-- Tabs content END -->
</div>
</section>
57 changes: 48 additions & 9 deletions Refhub/Controllers/BookController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Amazon.S3;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Refhub.Models.Enums;
using Refhub.Service.Implement;
using Refhub.Service.Interface;
using Refhub.Tools.Exceptions;
using Refhub.Tools.Static;

namespace Refhub.Controllers;

public class BookController(IBookService bookService) : Controller
public class BookController(IBookService bookService, IFileUploaderService s3FileUploaderService, ILogger<BookController> logger, IMessageService messageService) : Controller
{
[HttpGet("BookDetails/{slug}")]
public async Task<IActionResult> BookDetails(string slug, CancellationToken ct)
Expand All @@ -19,18 +25,49 @@ public async Task<IActionResult> BookDetails(string slug, CancellationToken ct)

return bookDetails == null ? NotFound() : View(bookDetails);
}
private bool IsValidFileUrl(string fileUrl)
{
return !string.IsNullOrWhiteSpace(fileUrl) && Uri.TryCreate(fileUrl, UriKind.Absolute, out _);
}

[Authorize]
[HttpGet]
public async Task<IActionResult> DownloadFile(string filePath, CancellationToken ct)
[HttpGet("download")]
public async Task<IActionResult> DownloadFile([FromQuery] string fileUrl, CancellationToken ct)
{
Copy link

Copilot AI Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate or sanitize the 'fileName' input to prevent path traversal or unauthorized access when fetching files from S3.

Suggested change
{
{
if (string.IsNullOrWhiteSpace(fileName) || fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
{
return BadRequest("Invalid file name.");
}

Copilot uses AI. Check for mistakes.
filePath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot{filePath}");
if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath))
try
{
return NotFound();
if (!IsValidFileUrl(fileUrl))
{
return NotFound(messageService.Get("InvalidFileName"));
}
Comment on lines +39 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Returning NotFound without a localized message makes it difficult to understand the error. Consider returning a localized message using messageService.Get("InvalidFileName")[^1] to provide more context to the user.

return NotFound(messageService.Get("InvalidFileName"));



// دریافت فایل از S3
var stream = await s3FileUploaderService.DownloadFileAsync(fileUrl, ct, BucketNameStatic.GetName(BucketNames.BookPdf));

// The file name for the user should be extracted from the URL
var fileName = Path.GetFileName(new Uri(fileUrl).AbsolutePath);

// تعیین نوع فایل با توجه به پسوند
var contentTypeProvider = new FileExtensionContentTypeProvider();
if (!contentTypeProvider.TryGetContentType(fileName, out string contentType))
{
contentType = "application/octet-stream"; // پیش‌فرض اگر پسوند ناشناس باشد
}

return File(stream, contentType, fileName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify stream disposal behavior with File() method.

The File() method should properly dispose the stream when the response ends, but it's worth confirming this behavior is working as expected in your S3 implementation.


🏁 Script executed:

#!/bin/bash
# Description: Check if the S3FileUploaderService properly handles stream disposal
# Expected: The service should return a stream that can be safely disposed by the File() method

# Search for the S3FileUploaderService implementation
ast-grep --pattern 'class S3FileUploaderService {
  $$$
  DownloadFileAsync($$$) {
    $$$
  }
  $$$
}'

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Locate the S3FileUploaderService class and its DownloadFileAsync method
rg -n "class S3FileUploaderService"
rg -n "DownloadFileAsync" -C3

Length of output: 2664


🏁 Script executed:

#!/bin/bash
# Show the DownloadFileAsync method implementation for inspection
rg -n "public async Task<Stream> DownloadFileAsync" -C20 Refhub/Service/Implement/S3FileUploaderService.cs

Length of output: 1456


Ensure S3 response stream isn’t disposed before use

The current code wraps the GetObjectResponse in a using block, which disposes the response—and its internal stream—immediately after the method returns. That means the controller receives a closed stream. You should either remove the using (letting ASP.NET Core dispose the stream via FileStreamResult) or, better yet, copy the S3 response into a fresh MemoryStream and return that.

• Location: Refhub/Service/Implement/S3FileUploaderService.cs, DownloadFileAsync (lines 96–114)
• Problematic snippet:

using var response = await _s3Client.GetObjectAsync(request, ct);
return response.ResponseStream;

• Suggested fix:

- using var response = await _s3Client.GetObjectAsync(request, ct);
- return response.ResponseStream;
+ var response = await _s3Client.GetObjectAsync(request, ct);
+ var ms = new MemoryStream();
+ await response.ResponseStream.CopyToAsync(ms, ct);
+ ms.Position = 0;
+ return ms;

This ensures the returned stream remains valid when Controller.File(stream, …) writes and disposes it.

🤖 Prompt for AI Agents
In Refhub/Service/Implement/S3FileUploaderService.cs around lines 96 to 114, the
S3 GetObjectResponse is wrapped in a using block which disposes the response and
its stream before the controller can use it. To fix this, remove the using block
and instead copy the response.ResponseStream into a new MemoryStream, then
return that MemoryStream so it remains valid when the controller writes and
disposes it.

}
Copy link

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The S3 service wraps exceptions in FileDownloadException, so catching AmazonS3Exception here will not handle those faults. Consider catching FileDownloadException before or instead of AmazonS3Exception.

Suggested change
}
}
catch (FileDownloadException fileEx)
{
_logger.LogError(fileEx, "خطا در دانلود فایل: {Message}", fileEx.Message);
return NotFound(_messageService.Get("FileNotFound"));
}

Copilot uses AI. Check for mistakes.
catch (FileDownloadException s3Ex)
{
logger.LogError(s3Ex, "Error downloading file from S3: {Message}", s3Ex.Message);

var fileBytes = await System.IO.File.ReadAllBytesAsync(filePath, ct);
return File(fileBytes, "application/octet-stream", Path.GetFileName(filePath));
return NotFound(messageService.Get("FileNotFound"));
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error occurred while downloading the file.");
return StatusCode(500,messageService.Get("DownloadError"));
}
}

private readonly int _pageSize = 3;
Expand All @@ -41,4 +78,6 @@ public async Task<IActionResult> List(string searchText, string authorFilter, st

return View(viewModel);
}


}
Loading