Adds latest episode api endpoint and some other minor modifications.

This commit is contained in:
2025-07-27 16:44:50 +00:00
parent 97b018f2bc
commit 667f0acd83
30 changed files with 3007 additions and 3336 deletions

View File

@@ -72,6 +72,7 @@ class ArtistController extends Controller
$artworks = Artwork::where('artist_id', $artist->id)
->with('episode')
->with('podcast')
->whereNotNull('approved_by')
->orderBy('artworks.created_at', 'desc')
->paginate($perPage = 92, $columns = ['*'], $pageName = 'artworks');
$podcasts = Podcast::where('published', true)->with('episodes')->get();

View File

@@ -26,7 +26,7 @@ class PasswordResetLinkController extends Controller
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => ['required', 'email'],
'email' => ['required', 'email', 'exists:users,email'],
]);
// We will send the password reset link to this user. Once we have attempted

View File

@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\DB;
use App\Models\Podcast;
use App\Models\Artworks;
use App\Models\Episode;
use App\Http\Resources\LatestEpisodeResource;
class PodcastController extends Controller
@@ -33,4 +34,26 @@ class PodcastController extends Controller
'podcasts' => $podcasts,
]);
}
/**
* Display the latest episode's chosen artwork for third party tools.
*
* @param $slug
* @return \Illuminate\Http\Response
*/
public function latest_artwork(Request $request, $slug)
{
$podcast = Podcast::with('latestArtwork.artist')
->where('slug', $slug)
->where('published', true)
->firstOrFail();
$art = $podcast->latestArtwork;
return new LatestEpisodeResource($podcast);
return response()->json([
'episode_number' => optional($podcast->latestEpisode)->episode_number,
'artwork' => $art,
'artist' => optional($art)->artist,
]);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class LatestEpisodeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
$podcast = $this->resource;
$artwork = $podcast->latestArtwork;
$episode = $artwork->episode;
$artist = $artwork->artist;
$static = config('app.static_asset_url');
$full = $static . '/artworks/' . $artwork->filename;
$thumb = $static . '/thumbnails/' . $artwork->filename;
$raw = (float) $episode->episode_number;
if (fmod($raw, 1) === 0.0) {
// no fractional part
$episodeNumber = (int) $raw;
} else {
// keep one decimal place
$episodeNumber = round($raw, 1);
}
return [
'artist_name' => $artist->name,
'artist_profile' => url('/artist/' . $artist->slug),
'artist_avatar' => $artist->avatar(),
'artwork_full' => $full,
'artwork_thumb' => $thumb,
'episode_title' => $episode->title,
'episode_number' => $episodeNumber,
'episode_date' => $episode->episode_date->format('Y-m-d'),
'episode_mp3' => $episode->mp3,
'podcast_title' => $podcast->name,
'podcast_archive' => url('/podcasts/' . $podcast->slug),
];
}
}

View File

@@ -33,4 +33,27 @@ class Podcast extends Model
return $this->hasManyThrough(Artist::class, Episode::class);
}
public function latestEpisode()
{
return $this->hasOne(Episode::class)
->where('published', true)
->orderBy('episode_number', 'desc');
}
public function latestArtwork()
{
// this follows the hasOneThrough from Episode → Artwork
return $this->hasOneThrough(
Artwork::class, // final model
Episode::class, // intermediate
'podcast_id', // FK on episodes
'id', // PK on artworks
'id', // PK on podcasts
'artwork_id' // FK on episodes → artworks
)
->where('episodes.published', true)
->orderBy('episodes.episode_number', 'desc');
}
}

View File

@@ -28,6 +28,11 @@ class RouteServiceProvider extends ServiceProvider
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('password-reset', function (Request $request) {
$key = Str::lower($request->input('email')).'|'.$request->ip();
return Limit::perHour(5)->by($key);
});
$this->routes(function () {
Route::middleware('api')
->prefix('api')