first commit
This commit is contained in:
203
app/Services/AttachmentService.php
Normal file
203
app/Services/AttachmentService.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Contracts\AttachmentAble;
|
||||
use App\Enums\AttachmentStatus;
|
||||
use App\Jobs\VerifyAttachmentHash;
|
||||
use App\Models\Attachment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskComment;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Storage;
|
||||
|
||||
class AttachmentService implements \App\Contracts\AttachmentService
|
||||
{
|
||||
public const DISK = 'attachments';
|
||||
private readonly FilesystemAdapter $storage;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->storage = Storage::disk($this::DISK);
|
||||
}
|
||||
public function storeFile(UploadedFile $file, Attachment $attachment): string|false
|
||||
{
|
||||
return $file->storeAs(
|
||||
$this->getPath($attachment),
|
||||
['disk' => $this::DISK]
|
||||
);
|
||||
}
|
||||
|
||||
public function handleProjectChange(Task $parent): void
|
||||
{
|
||||
$newProjectId = $parent->getProjectId();
|
||||
$parent->attachmentsRelation()->lazyById()
|
||||
->each(fn(Attachment $attachment) => $this->moveAttachment($attachment, $newProjectId));
|
||||
|
||||
$parent->comments()->lazyById()
|
||||
->each(fn(TaskComment $comment) => $comment->attachmentsRelation()->lazyById()
|
||||
->each(fn(Attachment $attachment) => $this->moveAttachment($attachment, $newProjectId)));
|
||||
}
|
||||
|
||||
public function moveAttachment(Attachment $attachment, $newProjectId): void
|
||||
{
|
||||
if ($attachment->status === AttachmentStatus::NOT_ATTACHED) {
|
||||
return;
|
||||
}
|
||||
$oldPath = $this->getPath($attachment);
|
||||
$oldProjectId = $attachment->project_id;
|
||||
$attachment->project_id = $newProjectId;
|
||||
$attachment->saveQuietly();
|
||||
$newPath = $this->getPath($attachment);
|
||||
|
||||
$fileExists = $this->storage->exists($oldPath);
|
||||
if (!$fileExists || !$this->storage->move($oldPath, $newPath)) {
|
||||
$attachment->project_id = $oldProjectId;
|
||||
$attachment->saveQuietly();
|
||||
\Log::error("Unable to move attachment`s file", [
|
||||
'attachment_id' => $attachment->id,
|
||||
'attachmentable_id' => $attachment->attachmentable_id,
|
||||
'attachmentable_type' => $attachment->attachmentable_type,
|
||||
'attachment_project_id' => $attachment->project_id,
|
||||
'old_attachment_project_id' => $oldProjectId,
|
||||
'old_path' => $oldPath,
|
||||
'new_path' => $newPath
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAttachment(Attachment $attachment): void
|
||||
{
|
||||
if (($fileExists = $this->fileExists($attachment)) && $this->deleteFile($attachment)) {
|
||||
$attachment->deleteQuietly();
|
||||
} elseif (!$fileExists) {
|
||||
$attachment->deleteQuietly();
|
||||
} else {
|
||||
\Log::error("Unable to delete attachment`s file", [
|
||||
'attachment_id' => $attachment->id,
|
||||
'attachmentable_id' => $attachment->attachmentable_id,
|
||||
'attachmentable_type' => $attachment->attachmentable_type,
|
||||
'attachment_project_id' => $attachment->project_id,
|
||||
'path' => $this->getPath($attachment)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAttachments(array $attachmentsIds): void
|
||||
{
|
||||
Attachment::whereIn('id', $attachmentsIds)
|
||||
->each(fn (Attachment $attachment) => $this->deleteAttachment($attachment));
|
||||
}
|
||||
|
||||
public function handleParentDeletion(AttachmentAble $parent): void
|
||||
{
|
||||
$parent->attachmentsRelation()->lazyById()
|
||||
->each(fn (Attachment $attachment) => $this->deleteAttachment($attachment));
|
||||
}
|
||||
|
||||
public function handleProjectDeletion(Project $project): void
|
||||
{
|
||||
Attachment::whereProjectId($project->id)->delete();
|
||||
|
||||
if ($this->storage->directoryExists($this->getProjectPath($project)) && !$this->storage->deleteDirectory($this->getProjectPath($project))) {
|
||||
\Log::error("Unable to delete project's attachments directory", [
|
||||
'project_id' => $project->id,
|
||||
'path' => $this->getProjectPath($project)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function fileExists(Attachment $attachment): bool
|
||||
{
|
||||
return $this->storage->exists($this->getPath($attachment));
|
||||
}
|
||||
|
||||
private function deleteFile(Attachment $attachment): bool
|
||||
{
|
||||
return $this->storage->delete($this->getPath($attachment));
|
||||
}
|
||||
|
||||
public function attach(AttachmentAble $parent, array $idsToAttach): void
|
||||
{
|
||||
$projectId = $parent->getProjectId();
|
||||
Attachment::whereIn('id', $idsToAttach)
|
||||
->each(function (Attachment $attachment) use ($parent, $projectId) {
|
||||
if ($attachment->status !== AttachmentStatus::NOT_ATTACHED) {
|
||||
return;
|
||||
}
|
||||
$tmpPath = $this->getPath($attachment);
|
||||
|
||||
if ($this->storage->exists($tmpPath)) {
|
||||
$attachment->attachmentAbleRelation()->associate($parent);
|
||||
$attachment->status = AttachmentStatus::PROCESSING;
|
||||
$attachment->project_id = $projectId;
|
||||
$attachment->saveQuietly();
|
||||
|
||||
$newPath = $this->getPath($attachment);
|
||||
$this->storage->move($tmpPath, $newPath);
|
||||
|
||||
VerifyAttachmentHash::dispatch($attachment)->afterCommit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function getProjectPath(Project $project): string
|
||||
{
|
||||
return "projects/{$project->id}";
|
||||
}
|
||||
|
||||
public function getPath(Attachment $attachment): string
|
||||
{
|
||||
return match ($attachment->status) {
|
||||
AttachmentStatus::NOT_ATTACHED => "users/{$attachment->user_id}/{$attachment->id}",
|
||||
AttachmentStatus::PROCESSING,
|
||||
AttachmentStatus::GOOD,
|
||||
AttachmentStatus::BAD => "projects/{$attachment->project_id}/{$attachment->id}",
|
||||
};
|
||||
}
|
||||
|
||||
public function getFullPath(Attachment $attachment): string
|
||||
{
|
||||
return $this->storage->path($this->getPath($attachment));
|
||||
}
|
||||
|
||||
public function readStream(Attachment $attachment)
|
||||
{
|
||||
return $this->storage->readStream($this->getPath($attachment));
|
||||
}
|
||||
|
||||
public function getHashAlgo(): string
|
||||
{
|
||||
return $this->storage->getConfig()['checksum_algo'] ?? 'sha512/256';
|
||||
}
|
||||
|
||||
public function getHashSum(Attachment|string $attachment): false|string
|
||||
{
|
||||
return $this->storage->checksum($this->callIfInstance($attachment, 'getPath'), ['checksum_algo' => $this->getHashAlgo()]);
|
||||
}
|
||||
|
||||
public function getMimeType(Attachment|string $attachment): false|string
|
||||
{
|
||||
return $this->storage->mimeType($this->callIfInstance($attachment, 'getPath'));
|
||||
}
|
||||
|
||||
private function callIfInstance(Attachment|string $attachment, $action)
|
||||
{
|
||||
return $attachment instanceof Attachment ? $this->{$action}($attachment) : $attachment;
|
||||
}
|
||||
|
||||
public function verifyHash(Attachment $attachment): void
|
||||
{
|
||||
if ($attachment->status === AttachmentStatus::PROCESSING && $hash = $this->getHashSum($attachment)) {
|
||||
$attachment->hash = $hash;
|
||||
$attachment->status = AttachmentStatus::GOOD;
|
||||
} elseif ($attachment->status === AttachmentStatus::GOOD && $attachment->hash !== $this->getHashSum($attachment)
|
||||
) {
|
||||
$attachment->status = AttachmentStatus::BAD;
|
||||
}
|
||||
|
||||
$attachment->save();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user