Skip to content

Commit 5ba992a

Browse files
feat: Add record track management (#6)
* Prepare tracks * Update tracks * Finalize track management * Finalize track management * Small formatting
1 parent c1fa873 commit 5ba992a

File tree

14 files changed

+440
-28
lines changed

14 files changed

+440
-28
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\Tracks\StoreTrackRequest;
6+
use App\Http\Requests\Tracks\UpdateTrackRequest;
7+
use App\Models\Record;
8+
use App\Models\Track;
9+
use Illuminate\Contracts\View\View;
10+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
11+
use Illuminate\Http\RedirectResponse;
12+
13+
final class TrackController extends Controller
14+
{
15+
use AuthorizesRequests;
16+
17+
public function create(Record $record): View
18+
{
19+
$this->authorize('create', [Track::class, $record]);
20+
21+
return view('tracks.create', [
22+
'record' => $record,
23+
]);
24+
}
25+
26+
public function store(StoreTrackRequest $request, Record $record): RedirectResponse
27+
{
28+
$track = new Track();
29+
$track->record()->associate($record);
30+
$track->title = $request->validated('title');
31+
$track->duration = $request->validated('duration');
32+
$track->save();
33+
34+
return redirect()->route('records.show', $record);
35+
}
36+
37+
public function edit(Track $track): View
38+
{
39+
$this->authorize('update', $track);
40+
41+
return view('tracks.edit', [
42+
'track' => $track,
43+
]);
44+
}
45+
46+
public function update(UpdateTrackRequest $request, Track $track): RedirectResponse
47+
{
48+
$track->title = $request->validated('title');
49+
$track->duration = $request->validated('duration');
50+
$track->save();
51+
52+
return redirect()->route('records.show', $track->record);
53+
}
54+
55+
public function destroy(Track $track): RedirectResponse
56+
{
57+
$this->authorize('delete', $track);
58+
59+
$track->delete();
60+
61+
return redirect()->route('records.show', $track->record);
62+
}
63+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace App\Http\Requests\Tracks;
4+
5+
use App\Models\Track;
6+
use Illuminate\Foundation\Http\FormRequest;
7+
8+
final class StoreTrackRequest extends FormRequest
9+
{
10+
public function authorize(): bool
11+
{
12+
/** @var \App\Models\Record $record */
13+
$record = $this->route('record');
14+
15+
return $this->user()->can('create', [Track::class, $record]);
16+
}
17+
18+
public function rules(): array
19+
{
20+
return [
21+
'title' => ['required', 'string', 'max:255'],
22+
'duration' => ['required', 'integer', 'min:1'],
23+
];
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace App\Http\Requests\Tracks;
4+
5+
use App\Models\Track;
6+
use Illuminate\Foundation\Http\FormRequest;
7+
8+
class UpdateTrackRequest extends FormRequest
9+
{
10+
public function authorize(): bool
11+
{
12+
/** @var \App\Models\Track $track */
13+
$track = $this->route('track');
14+
15+
return $this->user()->can('update', $track);
16+
}
17+
18+
public function rules(): array
19+
{
20+
return [
21+
'title' => ['required', 'string', 'max:255'],
22+
'duration' => ['required', 'integer', 'min:1'],
23+
];
24+
}
25+
}

app/Models/Record.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @property \Carbon\Carbon $created_at
3535
* @property \Carbon\Carbon $updated_at
3636
* @property-read \App\Models\User $user
37+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Track> $tracks
3738
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecordCategory> $recordCategories
3839
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecordImage> $recordImages
3940
*/
@@ -65,6 +66,16 @@ public function user(): BelongsTo
6566
return $this->belongsTo(User::class);
6667
}
6768

69+
/**
70+
* Query the tracks.
71+
*
72+
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\Track, $this>
73+
*/
74+
public function tracks(): HasMany
75+
{
76+
return $this->hasMany(Track::class);
77+
}
78+
6879
/**
6980
* Query the record images.
7081
*

app/Models/Taps/ApplyRecordFilters.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ public function __construct(
1515
public function __invoke(Builder $query): void
1616
{
1717
if ($this->filters->search !== null) {
18-
$query->where('name', 'like', '%' . $this->filters->search . '%');
18+
$query
19+
->where(function (Builder $query) {
20+
$query
21+
->where('name', 'like', '%' . $this->filters->search . '%')
22+
->orWhereHas('tracks', function (Builder $query) {
23+
$query->where('title', 'LIKE', "%{$this->filters->search}%");
24+
});
25+
});
26+
1927
}
2028
}
2129
}

app/Models/Track.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Factories\HasFactory;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
8+
use Illuminate\Support\Str;
9+
10+
/**
11+
* @property-read int $id
12+
* @property int $record_id
13+
* @property string $title
14+
* @property int $duration The tracks duration in seconds.
15+
* @property \Illuminate\Support\Carbon $created_at
16+
* @property \Illuminate\Support\Carbon $updated_at
17+
* @property-read \App\Models\Record $record
18+
*/
19+
final class Track extends Model
20+
{
21+
/** @use HasFactory<\Database\Factories\TrackFactory> */
22+
use HasFactory;
23+
24+
/**
25+
* Query the record.
26+
*
27+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\Record, $this>
28+
*/
29+
public function record(): BelongsTo
30+
{
31+
return $this->belongsTo(Record::class);
32+
}
33+
34+
/**
35+
* Get the track's duration formatted as `mm:ss`
36+
*
37+
* @return string
38+
*/
39+
public function getFormattedDuration(): string
40+
{
41+
$minutes = floor($this->duration / 60);
42+
$seconds = $this->duration - $minutes * 60;
43+
44+
$minutes = Str::padLeft($minutes, 2, '0');
45+
$seconds = Str::padLeft($seconds, 2, '0');
46+
47+
return "{$minutes}:{$seconds}";
48+
}
49+
}

app/Policies/TrackPolicy.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace App\Policies;
4+
5+
use App\Models\Record;
6+
use App\Models\Track;
7+
use App\Models\User;
8+
9+
class TrackPolicy
10+
{
11+
/**
12+
* Determine whether the use can create a track.
13+
*
14+
* @param User $user
15+
* @param Record $record
16+
* @return bool
17+
*/
18+
public function create(User $user, Record $record): bool
19+
{
20+
return $user->can('view', $record);
21+
}
22+
23+
/**
24+
* Determine whether the user can update the specified track.
25+
*
26+
* @param User $user
27+
* @param Track $track
28+
* @return bool
29+
*/
30+
public function update(User $user, Track $track): bool
31+
{
32+
return $user->can('update', $track->record);
33+
}
34+
35+
/**
36+
* Determine whether the user can delete the specified track.
37+
*
38+
* @param User $user
39+
* @param Track $track
40+
* @return bool
41+
*/
42+
public function delete(User $user, Track $track): bool
43+
{
44+
return $user->can('update', $track->record);
45+
}
46+
}

database/factories/TrackFactory.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Database\Factories;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
7+
/**
8+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Track>
9+
*/
10+
class TrackFactory extends Factory
11+
{
12+
/**
13+
* Define the model's default state.
14+
*
15+
* @return array<string, mixed>
16+
*/
17+
public function definition(): array
18+
{
19+
return [
20+
//
21+
];
22+
}
23+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('tracks', function (Blueprint $table) {
15+
$table->id();
16+
$table->foreignId('record_id')->constrained()->cascadeOnDelete();
17+
$table->string('title');
18+
$table->integer('duration');
19+
$table->timestamps();
20+
});
21+
}
22+
23+
/**
24+
* Reverse the migrations.
25+
*/
26+
public function down(): void
27+
{
28+
Schema::dropIfExists('tracks');
29+
}
30+
};

resources/views/records/index.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
type="text"
1919
class="grow"
2020
name="search"
21-
placeholder="Search ..."
21+
placeholder="Search for record, track name..."
2222
value="{{ request()->query('search') }}"
2323
maxlength="255"
2424
>

0 commit comments

Comments
 (0)