Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions app/Http/Controllers/Api/LinkPreviewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function show(Request $request)
'title' => $host ?: 'Local Link',
'description' => 'Link to local page',
'image' => null,
'favicon' => asset('favicon.ico'),
'favicon' => null,
]);
}

Expand All @@ -49,7 +49,7 @@ public function show(Request $request)
'title' => parse_url($url, PHP_URL_HOST),
'description' => 'Failed to fetch link preview',
'image' => null,
'favicon' => asset('favicon.ico'),
'favicon' => null,
];
}
});
Expand Down Expand Up @@ -77,7 +77,7 @@ private function fetchPreviewData(string $url): array
'title' => basename($url) ?: parse_url($url, PHP_URL_HOST),
'description' => 'Link to file: ' . $contentType,
'image' => null,
'favicon' => asset('favicon.ico'),
'favicon' => null,
];
}

Expand Down Expand Up @@ -129,7 +129,7 @@ private function fetchPreviewData(string $url): array
if ($favicon) {
$favicon = $this->resolveUrl($favicon, $url);
} else {
$favicon = asset('favicon.ico');
$favicon = null;
}

return [
Expand Down
49 changes: 49 additions & 0 deletions app/Http/Controllers/Api/TaskAttachmentController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\TaskAttachment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class TaskAttachmentController extends Controller
{

public function show(Request $request, string $id)
{
$attachment = TaskAttachment::findOrFail($id);

if ($attachment->team_id !== $request->user()->team_id) {
abort(403);
}

if (!Storage::disk('local')->exists($attachment->path)) {
abort(404);
}

return response()->file(Storage::disk('local')->path($attachment->path));

// if we want the browser not to cache the file, we can use the following code instead of response()->file():
// example if we sxitch teams we should not see the previous teams attachments in the browser cache
// ->path($attachment->path), [
// 'Cache-Control' => 'private, no-cache, no-store, must-revalidate',
// 'Pragma' => 'no-cache',
// 'Expires' => '0',
// ]);
}

public function destroy(Request $request, string $id)
{
$attachment = TaskAttachment::findOrFail($id);

if ($attachment->team_id !== $request->user()->team_id) {
abort(403);
}

Storage::disk('local')->delete($attachment->path);
$attachment->delete();

return response()->noContent();
}
}
1 change: 1 addition & 0 deletions app/Http/Controllers/TaskController.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function show(Request $request, Task $task): JsonResponse
'creator:id,name',
'assignee:id,name',
'tags:id,name,color',
'taskAttachments',
'comments' => fn($query) => $query
->whereNull('parent_id')
->with(['user:id,name', 'replies']),
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Requests/TaskCreateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public function rules(): array
'nullable',
Rule::exists('columns', 'id')->where(fn($query) => $query->where('team_id', $this->user()?->team_id)),
],
'attachments' => ['sometimes', 'array'],
'attachments.*' => ['file', 'max:20480'], // 20MB max per file
];
}

Expand Down
4 changes: 4 additions & 0 deletions app/Http/Requests/TaskUpdateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public function rules(): array
fn($query) => $query->where('team_id', $this->user()?->team_id)
),
],
'attachments' => ['sometimes', 'array'],
'attachments.*' => ['file', 'max:20480'], // 20MB max per file
'removed_attachment_ids' => ['sometimes', 'array'],
'removed_attachment_ids.*' => ['uuid'],
];
}
}
11 changes: 11 additions & 0 deletions app/Http/Resources/TaskResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,17 @@ public function toArray(Request $request): array
'color' => $tag->color,
])->values()
),
'attachments' => $this->whenLoaded(
'taskAttachments',
fn() => $this->taskAttachments
->map(fn($a) => [
'id' => $a->id,
'filename' => $a->filename,
'mime_type' => $a->mime_type,
'size' => $a->size,
'url' => route('attachments.show', $a->id),
])->values()
),
];
}
}
8 changes: 8 additions & 0 deletions app/Models/Task.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ public function tags(): BelongsToMany
return $this->belongsToMany(Tag::class, 'task_tag');
}

/**
* Get the file attachments for the task.
*/
public function taskAttachments(): HasMany
{
return $this->hasMany(TaskAttachment::class)->orderBy('created_at');
}

/**
* Scope a query to filter tasks based on array parameters.
*
Expand Down
38 changes: 38 additions & 0 deletions app/Models/TaskAttachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TaskAttachment extends Model
{
use HasUuids;

protected $fillable = [
'team_id',
'task_id',
'user_id',
'path',
'filename',
'mime_type',
'size',
];

public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}

public function task(): BelongsTo
{
return $this->belongsTo(Task::class);
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
52 changes: 47 additions & 5 deletions app/Services/TaskService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

use App\Models\Task;
use App\Models\Column;
use App\Models\TaskAttachment;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class TaskService
{
Expand All @@ -24,11 +26,12 @@ public function createTask(array $data, User $user): Task
Task::where('column_id', $columnId)->increment('order');
$order = 0;

unset($data['column_id'], $data['tag_ids']);
$newFiles = $data['attachments'] ?? [];
unset($data['column_id'], $data['tag_ids'], $data['attachments']);

$columnName = Column::query()->where('id', $columnId)->value('name');

return DB::transaction(function () use ($data, $user, $columnId, $order, $columnName, $tagIds) {
return DB::transaction(function () use ($data, $user, $columnId, $order, $columnName, $tagIds, $newFiles) {
$task = Task::create(array_merge($data, [
'team_id' => $user->team_id,
'created_by' => $user->id,
Expand Down Expand Up @@ -65,6 +68,20 @@ public function createTask(array $data, User $user): Task
]);
}

foreach ($newFiles as $file) {
$path = $file->store('attachments', 'local');

TaskAttachment::create([
'team_id' => $user->team_id,
'task_id' => $task->id,
'user_id' => $user->id,
'path' => $path,
'filename' => $file->getClientOriginalName(),
'mime_type' => $file->getMimeType(),
'size' => $file->getSize(),
]);
}

return $task;
});
}
Expand All @@ -75,10 +92,10 @@ public function createTask(array $data, User $user): Task
public function updateTask(Task $task, array $data, User $actor): bool
{
$oldAssignedTo = $task->assigned_to;

// Extract tag_ids if provided
$tagIds = $data['tag_ids'] ?? null;
unset($data['tag_ids']);
$newFiles = $data['attachments'] ?? [];
$removedIds = $data['removed_attachment_ids'] ?? [];
unset($data['tag_ids'], $data['attachments'], $data['removed_attachment_ids']);

$updated = $task->update($data);

Expand Down Expand Up @@ -108,6 +125,31 @@ public function updateTask(Task $task, array $data, User $actor): bool
]);
}

if (!empty($removedIds)) {
$toRemove = TaskAttachment::whereIn('id', $removedIds)
->where('team_id', $actor->team_id)
->get();

foreach ($toRemove as $attachment) {
Storage::disk('local')->delete($attachment->path);
$attachment->delete();
}
}

foreach ($newFiles as $file) {
$path = $file->store('attachments', 'local');

TaskAttachment::create([
'team_id' => $actor->team_id,
'task_id' => $task->id,
'user_id' => $actor->id,
'path' => $path,
'filename' => $file->getClientOriginalName(),
'mime_type' => $file->getMimeType(),
'size' => $file->getSize(),
]);
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('task_attachments', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->foreignId('team_id')->constrained()->cascadeOnDelete();
$table->foreignId('task_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('path');
$table->string('filename');
$table->string('mime_type');
$table->unsignedBigInteger('size');
$table->timestamps();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('task_attachments');
}
};
Loading
Loading