diff --git a/app/Constants/Config/ApiConstants.php b/app/Constants/Config/ApiConstants.php new file mode 100644 index 000000000..3c22d04f3 --- /dev/null +++ b/app/Constants/Config/ApiConstants.php @@ -0,0 +1,15 @@ +getIncludeCriteria($this->schema->type()); - if ( - $this->schema->type() === $schema->type() - && ($includeCriteria === null || $includeCriteria->getPaths()->isEmpty()) - ) { - $criteria = $query->getFieldCriteria($this->schema->type()); - - return $criteria === null || $criteria->isAllowedField($this->getKey()); - } - - return true; - } -} \ No newline at end of file diff --git a/app/Http/Api/Schema/List/External/ExternalEntrySchema.php b/app/Http/Api/Schema/List/External/ExternalEntrySchema.php index ffc4d1593..e08c84f37 100644 --- a/app/Http/Api/Schema/List/External/ExternalEntrySchema.php +++ b/app/Http/Api/Schema/List/External/ExternalEntrySchema.php @@ -4,11 +4,11 @@ namespace App\Http\Api\Schema\List\External; +use App\Http\Api\Field\Base\IdField; use App\Http\Api\Field\Field; use App\Http\Api\Field\List\ExternalProfile\ExternalEntry\ExternalEntryAnimeIdField; use App\Http\Api\Field\List\ExternalProfile\ExternalEntry\ExternalEntryExternalProfileIdField; use App\Http\Api\Field\List\ExternalProfile\ExternalEntry\ExternalEntryWatchStatusField; -use App\Http\Api\Field\List\ExternalProfile\ExternalEntry\ExternalEntryIdField; use App\Http\Api\Field\List\ExternalProfile\ExternalEntry\ExternalEntryIsFavoriteField; use App\Http\Api\Field\List\ExternalProfile\ExternalEntry\ExternalEntryScoreField; use App\Http\Api\Include\AllowedInclude; @@ -58,7 +58,7 @@ public function fields(): array return array_merge( parent::fields(), [ - new ExternalEntryIdField($this), + new IdField($this, ExternalEntry::ATTRIBUTE_ID), new ExternalEntryScoreField($this), new ExternalEntryIsFavoriteField($this), new ExternalEntryWatchStatusField($this), diff --git a/app/Http/Api/Schema/List/ExternalProfileSchema.php b/app/Http/Api/Schema/List/ExternalProfileSchema.php index 67758f1ae..e557a3c8d 100644 --- a/app/Http/Api/Schema/List/ExternalProfileSchema.php +++ b/app/Http/Api/Schema/List/ExternalProfileSchema.php @@ -5,9 +5,9 @@ namespace App\Http\Api\Schema\List; use App\Contracts\Http\Api\Schema\SearchableSchema; +use App\Http\Api\Field\Base\IdField; use App\Http\Api\Field\Field; use App\Http\Api\Field\List\ExternalProfile\ExternalProfileNameField; -use App\Http\Api\Field\List\ExternalProfile\ExternalProfileIdField; use App\Http\Api\Field\List\ExternalProfile\ExternalProfileVisibilityField; use App\Http\Api\Field\List\ExternalProfile\ExternalProfileSiteField; use App\Http\Api\Field\List\ExternalProfile\ExternalProfileUserIdField; @@ -16,6 +16,10 @@ use App\Http\Api\Schema\EloquentSchema; use App\Http\Api\Schema\List\External\ExternalEntrySchema; use App\Http\Api\Schema\Wiki\AnimeSchema; +use App\Http\Api\Schema\Wiki\GroupSchema; +use App\Http\Api\Schema\Wiki\ImageSchema; +use App\Http\Api\Schema\Wiki\SongSchema; +use App\Http\Api\Schema\Wiki\VideoSchema; use App\Http\Resources\List\Resource\ExternalProfileResource; use App\Models\List\ExternalProfile; @@ -45,6 +49,11 @@ public function allowedIncludes(): array new AllowedInclude(new AnimeSchema(), ExternalProfile::RELATION_ANIMES), new AllowedInclude(new ExternalEntrySchema(), ExternalProfile::RELATION_EXTERNAL_ENTRIES), new AllowedInclude(new UserSchema(), ExternalProfile::RELATION_USER), + + new AllowedInclude(new GroupSchema(), "externalentries.anime.animethemes.group"), + new AllowedInclude(new VideoSchema(), "externalentries.anime.animethemes.animethemeentries.videos"), + new AllowedInclude(new SongSchema(), "externalentries.anime.animethemes.song"), + new AllowedInclude(new ImageSchema(), "externalentries.anime.images"), ]; } @@ -58,7 +67,7 @@ public function fields(): array return array_merge( parent::fields(), [ - new ExternalProfileIdField($this), + new IdField($this, ExternalProfile::ATTRIBUTE_ID), new ExternalProfileNameField($this), new ExternalProfileSiteField($this), new ExternalProfileVisibilityField($this), @@ -66,4 +75,4 @@ public function fields(): array ], ); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/List/ExternalProfileController.php b/app/Http/Controllers/Api/List/ExternalProfileController.php index fb349d84b..c0c97765e 100644 --- a/app/Http/Controllers/Api/List/ExternalProfileController.php +++ b/app/Http/Controllers/Api/List/ExternalProfileController.php @@ -25,6 +25,7 @@ use App\Http\Resources\List\Resource\ExternalProfileResource; use App\Models\List\ExternalProfile; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; @@ -63,6 +64,11 @@ public function index(IndexRequest $request, IndexAction $action): ExternalProfi $builder = ExternalProfile::query()->where(ExternalProfile::ATTRIBUTE_VISIBILITY, ExternalProfileVisibility::PUBLIC->value); + $userId = Auth::id(); + if ($userId) { + $builder->orWhere(ExternalProfile::ATTRIBUTE_USER, Auth::id()); + } + $externalprofiles = $query->hasSearchCriteria() ? $action->search($query, $request->schema()) : $action->index($builder, $query, $request->schema()); diff --git a/app/Http/Controllers/List/External/ExternalTokenAuthController.php b/app/Http/Controllers/List/External/ExternalTokenAuthController.php new file mode 100644 index 000000000..cc98d8cd3 --- /dev/null +++ b/app/Http/Controllers/List/External/ExternalTokenAuthController.php @@ -0,0 +1,95 @@ +append(':') + ->append(AllowExternalProfileManagement::class) + ->__toString(); + + $this->middleware(EnabledOnlyOnLocalhost::class); + $this->middleware($isExternalProfileManagementAllowed); + } + + /** + * This will redirect the user to the appropriate auth service. + * + * @param Request $request + * @return RedirectResponse|JsonResponse + */ + public function index(Request $request): RedirectResponse|JsonResponse + { + $validated = array_merge( + $request->all(), + [ExternalProfile::ATTRIBUTE_USER => Auth::id()] + ); + + $site = Arr::get($validated, ExternalProfile::ATTRIBUTE_SITE); + $profileSite = ExternalProfileSite::fromLocalizedName($site); + + if ($profileSite instanceof ExternalProfileSite) { + $link = $this->getRedirectLink($profileSite); + + if ($link !== null) { + return Redirect::to($link); + } + } + + return new JsonResponse([ + 'error' => 'invalid site', + ], 400); + } + + /** + * Get the link of the external site to authenticate the user. + * + * @param ExternalProfileSite $site + * @return string + */ + private function getRedirectLink(ExternalProfileSite $site): ?string + { + switch ($site) { + case ExternalProfileSite::KITSU: + return null; + case ExternalProfileSite::MAL: + return null; + case ExternalProfileSite::ANILIST: + $query = [ + 'client_id' => Config::get('services.anilist.client_id'), + 'redirect_uri' => Config::get('services.anilist.redirect_uri'), + 'response_type' => 'code', + ]; + + return 'https://anilist.co/api/v2/oauth/authorize?' . http_build_query($query); + default: + return null; + } + } +} diff --git a/app/Http/Controllers/Api/List/External/ExternalTokenCallbackController.php b/app/Http/Controllers/List/External/ExternalTokenCallbackController.php similarity index 69% rename from app/Http/Controllers/Api/List/External/ExternalTokenCallbackController.php rename to app/Http/Controllers/List/External/ExternalTokenCallbackController.php index 45231cb22..d29a23aa7 100644 --- a/app/Http/Controllers/Api/List/External/ExternalTokenCallbackController.php +++ b/app/Http/Controllers/List/External/ExternalTokenCallbackController.php @@ -2,18 +2,16 @@ declare(strict_types=1); -namespace App\Http\Controllers\Api\List\External; +namespace App\Http\Controllers\List\External; use App\Actions\Models\List\ExternalTokenCallbackAction; use App\Features\AllowExternalProfileManagement; -use App\Http\Api\Schema\List\ExternalProfileSchema; -use App\Http\Api\Schema\Schema; -use App\Http\Controllers\Api\BaseController; -use App\Http\Requests\Api\IndexRequest; -use App\Models\List\External\ExternalToken; +use App\Http\Controllers\Controller; +use App\Http\Middleware\Api\EnabledOnlyOnLocalhost; use App\Models\List\ExternalProfile; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Redirect; @@ -23,30 +21,29 @@ /** * Class ExternalTokenCallbackController. */ -class ExternalTokenCallbackController extends BaseController +class ExternalTokenCallbackController extends Controller { /** * Create a new controller instance. */ public function __construct() { - parent::__construct(ExternalToken::class, 'externaltoken'); - $isExternalProfileManagementAllowed = Str::of(EnsureFeaturesAreActive::class) ->append(':') ->append(AllowExternalProfileManagement::class) ->__toString(); + $this->middleware(EnabledOnlyOnLocalhost::class); $this->middleware($isExternalProfileManagementAllowed); } /** * This is the redirect URL which is set in the external provider. * - * @param IndexRequest $request + * @param Request $request * @return RedirectResponse|JsonResponse */ - public function index(IndexRequest $request): RedirectResponse|JsonResponse + public function index(Request $request): RedirectResponse|JsonResponse { $validated = array_merge( $request->all(), @@ -71,16 +68,4 @@ public function index(IndexRequest $request): RedirectResponse|JsonResponse return Redirect::to($clientUrl); } - - /** - * Get the underlying schema. - * - * @return Schema - * - * @noinspection PhpMissingParentCallCommonInspection - */ - public function schema(): Schema - { - return new ExternalProfileSchema(); - } } diff --git a/app/Http/Controllers/Api/List/SyncExternalProfileController.php b/app/Http/Controllers/List/SyncExternalProfileController.php similarity index 84% rename from app/Http/Controllers/Api/List/SyncExternalProfileController.php rename to app/Http/Controllers/List/SyncExternalProfileController.php index 5a6835a31..b8ad4531b 100644 --- a/app/Http/Controllers/Api/List/SyncExternalProfileController.php +++ b/app/Http/Controllers/List/SyncExternalProfileController.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace App\Http\Controllers\Api\List; +namespace App\Http\Controllers\List; use App\Features\AllowExternalProfileManagement; -use App\Http\Controllers\Api\BaseController; +use App\Http\Controllers\Controller; use App\Http\Middleware\Api\EnabledOnlyOnLocalhost; use App\Http\Requests\Api\ShowRequest; use App\Models\List\ExternalProfile; @@ -15,15 +15,13 @@ /** * Class SyncExternalProfileController. */ -class SyncExternalProfileController extends BaseController +class SyncExternalProfileController extends Controller { /** * Create a new controller instance. */ public function __construct() { - parent::__construct(ExternalProfile::class, 'externalprofile'); - $isExternalProfileManagementAllowed = Str::of(EnsureFeaturesAreActive::class) ->append(':') ->append(AllowExternalProfileManagement::class) diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index c545f137d..16e88ece5 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -4,10 +4,12 @@ namespace App\Providers; +use App\Constants\Config\ApiConstants; use App\Constants\Config\AudioConstants; use App\Constants\Config\DumpConstants; use App\Constants\Config\VideoConstants; use App\Enums\Auth\SpecialPermission; +use App\Http\Middleware\Auth\Authenticate; use App\Models\Auth\User; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; @@ -75,9 +77,15 @@ public function boot(): void ->prefix(Config::get(VideoConstants::SCRIPT_PATH_QUALIFIED)) ->group(base_path('routes/script.php')); + Route::middleware(['web', Authenticate::class, 'throttle:api']) + ->domain(Config::get(ApiConstants::URL_QUALIFIED)) + ->prefix(Config::get(ApiConstants::PATH_QUALIFIED)) + ->as('api.') + ->group(base_path('routes/externallist.php')); + Route::middleware('api') - ->domain(Config::get('api.url')) - ->prefix(Config::get('api.path')) + ->domain(Config::get(ApiConstants::URL_QUALIFIED)) + ->prefix(Config::get(ApiConstants::PATH_QUALIFIED)) ->as('api.') ->group(base_path('routes/api.php')); }); diff --git a/routes/api.php b/routes/api.php index f71a3688e..56536100d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -11,16 +11,14 @@ use App\Http\Controllers\Api\Auth\User\Me\List\MyPlaylistController; use App\Http\Controllers\Api\Auth\User\Me\MyController; use App\Http\Controllers\Api\Document\PageController; +use App\Http\Controllers\Api\List\External\ExternalEntryController; +use App\Http\Controllers\Api\List\ExternalProfileController; use App\Http\Controllers\Api\List\Playlist\TrackBackwardController; use App\Http\Controllers\Api\List\Playlist\TrackController; use App\Http\Controllers\Api\List\Playlist\TrackForwardController; use App\Http\Controllers\Api\List\PlaylistBackwardController; use App\Http\Controllers\Api\List\PlaylistController; use App\Http\Controllers\Api\List\PlaylistForwardController; -use App\Http\Controllers\Api\List\External\ExternalEntryController; -use App\Http\Controllers\Api\List\External\ExternalTokenCallbackController; -use App\Http\Controllers\Api\List\ExternalProfileController; -use App\Http\Controllers\Api\List\SyncExternalProfileController; use App\Http\Controllers\Api\Pivot\List\PlaylistImageController; use App\Http\Controllers\Api\Pivot\Wiki\AnimeImageController; use App\Http\Controllers\Api\Pivot\Wiki\AnimeResourceController; @@ -246,15 +244,6 @@ function apiPivotResourceUri(string $name, string $related, string $foreign): st apiResource('externalprofile', ExternalProfileController::class); apiScopedResource('externalprofile.externalentry', ExternalEntryController::class); -Route::get('externaltoken/callback', [ExternalTokenCallbackController::class, 'index']) - ->name('externaltoken.callback'); - -Route::get('externalprofile/{externalprofile}/sync', [SyncExternalProfileController::class, 'show']) - ->name('externalprofile.sync.show'); - -Route::post('externalprofile/{externalprofile}/sync', [SyncExternalProfileController::class, 'store']) - ->name('externalprofile.sync.store'); - // Playlist Routes apiResource('playlist', PlaylistController::class); apiScopedResource('playlist.track', TrackController::class); diff --git a/routes/externallist.php b/routes/externallist.php new file mode 100644 index 000000000..9b8c2a238 --- /dev/null +++ b/routes/externallist.php @@ -0,0 +1,32 @@ +name('externaltoken.auth'); + +Route::get('externaltoken/callback', [ExternalTokenCallbackController::class, 'index']) + ->name('externaltoken.callback'); + +Route::get('externalprofile/{externalprofile}/sync', [SyncExternalProfileController::class, 'show']) + ->name('externalprofile.sync.show'); + +Route::post('externalprofile/{externalprofile}/sync', [SyncExternalProfileController::class, 'store']) + ->name('externalprofile.sync.store');