mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 09:34:50 +02:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0464406d3 | |||
| 134a82ecc9 | |||
| 3e71ff8ace | |||
| 39250632b2 | |||
| 3cdf824e47 | |||
| 681f07ecc9 | |||
| 9028557f4c | |||
| cea28e61f1 | |||
| fed083b5d3 | |||
| 2e97bb78ea | |||
| b320882d6d | |||
| a8801edec9 | |||
| ed33d4154a | |||
| d3a8d58cd2 |
+2
-2
@@ -3,9 +3,9 @@ root = true
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ VIDEO_UPLOAD_DISKS=
|
||||
SCRIPT_DISK=scripts
|
||||
SCRIPT_DISK_ROOT=
|
||||
SCRIPT_URL=http://localhost
|
||||
SCRIPT_PATH=
|
||||
SCRIPT_PATH=/videoscript
|
||||
|
||||
# web
|
||||
WEB_URL=http://localhost
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.1'
|
||||
php-version: '8.2'
|
||||
tools: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, phpunit
|
||||
- uses: actions/checkout@v3
|
||||
- name: Copy .env
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.1'
|
||||
php-version: '8.2'
|
||||
tools: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, phpunit
|
||||
- uses: actions/checkout@v3
|
||||
- name: Copy .env
|
||||
|
||||
@@ -2,15 +2,22 @@
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/build
|
||||
/public/audios
|
||||
/public/images
|
||||
/public/videos
|
||||
/public/dumps
|
||||
/public/scripts
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
.vscode
|
||||
.idea
|
||||
auth.json
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http\Admin\Dump;
|
||||
|
||||
use App\Actions\Http\DownloadAction;
|
||||
use App\Constants\Config\DumpConstants;
|
||||
use App\Models\Admin\Dump;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class DumpDownloadAction.
|
||||
*
|
||||
* @extends DownloadAction<Dump>
|
||||
*/
|
||||
class DumpDownloadAction extends DownloadAction
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param Dump $dump
|
||||
*/
|
||||
public function __construct(Dump $dump)
|
||||
{
|
||||
parent::__construct($dump);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the resource in storage.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function path(): string
|
||||
{
|
||||
return $this->model->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(DumpConstants::DISK_QUALIFIED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http;
|
||||
|
||||
use App\Contracts\Storage\InteractsWithDisk;
|
||||
use App\Models\BaseModel;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* Class DownloadAction.
|
||||
*
|
||||
* @template TModel of \App\Models\BaseModel
|
||||
*/
|
||||
abstract class DownloadAction implements InteractsWithDisk
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param TModel $model
|
||||
*/
|
||||
public function __construct(protected readonly BaseModel $model)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the resource.
|
||||
*
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
public function download(): StreamedResponse
|
||||
{
|
||||
/** @var FilesystemAdapter $fs */
|
||||
$fs = Storage::disk($this->disk());
|
||||
|
||||
return $fs->download($this->path());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the resource in storage.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function path(): string;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class NginxStreamAction.
|
||||
*/
|
||||
abstract class NginxStreamAction extends StreamAction
|
||||
{
|
||||
/**
|
||||
* Stream the resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function stream(): Response
|
||||
{
|
||||
$fs = Storage::disk($this->disk());
|
||||
|
||||
// Generate temporary link for the object
|
||||
$temporaryURL = $fs->temporaryUrl($this->streamable->path(), now()->addMinutes(5));
|
||||
|
||||
// Get the url information
|
||||
$url_scheme = parse_url($temporaryURL, PHP_URL_SCHEME);
|
||||
$url_host = parse_url($temporaryURL, PHP_URL_HOST);
|
||||
$url_path_query = parse_url($temporaryURL, PHP_URL_PATH).'?'.parse_url($temporaryURL, PHP_URL_QUERY);
|
||||
|
||||
// Construct the new link for the redirect
|
||||
$link = "{$this->nginxRedirect()}$url_scheme/$url_host$url_path_query";
|
||||
|
||||
$response = new Response();
|
||||
|
||||
$disposition = $response->headers->makeDisposition('inline', $this->streamable->basename());
|
||||
|
||||
return $response->withHeaders([
|
||||
'Accept-Ranges' => 'bytes',
|
||||
'Content-Type' => $this->streamable->mimetype(),
|
||||
'Content-Length' => $this->streamable->size(),
|
||||
'Content-Disposition' => $disposition,
|
||||
'X-Accel-Redirect' => $link,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the nginx internal redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function nginxRedirect(): string;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* Class ResponseStreamAction.
|
||||
*/
|
||||
abstract class ResponseStreamAction extends StreamAction
|
||||
{
|
||||
/**
|
||||
* Stream the resource.
|
||||
*
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
public function stream(): StreamedResponse
|
||||
{
|
||||
$response = new StreamedResponse();
|
||||
|
||||
$disposition = $response->headers->makeDisposition('inline', $this->streamable->basename());
|
||||
|
||||
$response->headers->replace([
|
||||
'Accept-Ranges' => 'bytes',
|
||||
'Content-Type' => $this->streamable->mimetype(),
|
||||
'Content-Length' => $this->streamable->size(),
|
||||
'Content-Disposition' => $disposition,
|
||||
]);
|
||||
|
||||
$fs = Storage::disk($this->disk());
|
||||
|
||||
$response->setCallback(function () use ($fs) {
|
||||
$stream = $fs->readStream($this->streamable->path());
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
});
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http;
|
||||
|
||||
use App\Contracts\Models\Streamable;
|
||||
use App\Contracts\Storage\InteractsWithDisk;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Class StreamAction.
|
||||
*/
|
||||
abstract class StreamAction implements InteractsWithDisk
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param Streamable $streamable
|
||||
*/
|
||||
public function __construct(protected readonly Streamable $streamable)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream the resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
abstract public function stream(): Response;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http\Wiki\Audio;
|
||||
|
||||
use App\Actions\Http\NginxStreamAction;
|
||||
use App\Constants\Config\AudioConstants;
|
||||
use App\Models\Wiki\Audio;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class AudioNginxStreamAction.
|
||||
*/
|
||||
class AudioNginxStreamAction extends NginxStreamAction
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param Audio $audio
|
||||
*/
|
||||
public function __construct(Audio $audio)
|
||||
{
|
||||
parent::__construct($audio);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(AudioConstants::DEFAULT_DISK_QUALIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the nginx internal redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function nginxRedirect(): string
|
||||
{
|
||||
return Config::get(AudioConstants::NGINX_REDIRECT_QUALIFIED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http\Wiki\Audio;
|
||||
|
||||
use App\Actions\Http\ResponseStreamAction;
|
||||
use App\Constants\Config\AudioConstants;
|
||||
use App\Models\Wiki\Audio;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class AudioResponseStreamAction.
|
||||
*/
|
||||
class AudioResponseStreamAction extends ResponseStreamAction
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param Audio $audio
|
||||
*/
|
||||
public function __construct(Audio $audio)
|
||||
{
|
||||
parent::__construct($audio);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(AudioConstants::DEFAULT_DISK_QUALIFIED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http\Wiki\Video\Script;
|
||||
|
||||
use App\Actions\Http\DownloadAction;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Models\Wiki\Video\VideoScript;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class ScriptDownloadAction.
|
||||
*
|
||||
* @extends DownloadAction<VideoScript>
|
||||
*/
|
||||
class ScriptDownloadAction extends DownloadAction
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param VideoScript $script
|
||||
*/
|
||||
public function __construct(VideoScript $script)
|
||||
{
|
||||
parent::__construct($script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the resource in storage.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function path(): string
|
||||
{
|
||||
return $this->model->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(VideoConstants::SCRIPT_DISK_QUALIFIED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http\Wiki\Video;
|
||||
|
||||
use App\Actions\Http\NginxStreamAction;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class VideoNginxStreamAction.
|
||||
*/
|
||||
class VideoNginxStreamAction extends NginxStreamAction
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param Video $video
|
||||
*/
|
||||
public function __construct(Video $video)
|
||||
{
|
||||
parent::__construct($video);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(VideoConstants::DEFAULT_DISK_QUALIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the nginx internal redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function nginxRedirect(): string
|
||||
{
|
||||
return Config::get(VideoConstants::NGINX_REDIRECT_QUALIFIED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Http\Wiki\Video;
|
||||
|
||||
use App\Actions\Http\ResponseStreamAction;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class VideoResponseStreamAction.
|
||||
*/
|
||||
class VideoResponseStreamAction extends ResponseStreamAction
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param Video $video
|
||||
*/
|
||||
public function __construct(Video $video)
|
||||
{
|
||||
parent::__construct($video);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(VideoConstants::DEFAULT_DISK_QUALIFIED);
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,6 @@ class BackfillLargeCoverImageAction extends BackfillStudioImageAction
|
||||
*/
|
||||
protected function getMalImage(ExternalResource $malResource): ?Image
|
||||
{
|
||||
return $this->createImage("https://cdn.myanimelist.net/img/common/companies/$malResource->external_id.png");
|
||||
return $this->createImage("https://cdn.myanimelist.net/images/company/$malResource->external_id.png");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ class DumpDocumentAction extends DumpAction
|
||||
{
|
||||
use ReconcilesDumpRepositories;
|
||||
|
||||
final public const FILENAME_PREFIX = 'animethemes-db-dump-document-';
|
||||
|
||||
/**
|
||||
* The list of tables to include in the dump.
|
||||
*
|
||||
@@ -41,7 +43,7 @@ class DumpDocumentAction extends DumpAction
|
||||
$filesystem = Storage::disk('local');
|
||||
|
||||
return Str::of($filesystem->path(''))
|
||||
->append('animethemes-db-dump-document-')
|
||||
->append(DumpDocumentAction::FILENAME_PREFIX)
|
||||
->append(intval(Date::now()->valueOf()))
|
||||
->append('.sql')
|
||||
->__toString();
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Models\Wiki\Series;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Studio;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Models\Wiki\Video\VideoScript;
|
||||
use App\Pivots\AnimeImage;
|
||||
use App\Pivots\AnimeResource;
|
||||
use App\Pivots\AnimeSeries;
|
||||
@@ -39,6 +40,8 @@ class DumpWikiAction extends DumpAction
|
||||
{
|
||||
use ReconcilesDumpRepositories;
|
||||
|
||||
final public const FILENAME_PREFIX = 'animethemes-db-dump-wiki-';
|
||||
|
||||
/**
|
||||
* The list of tables to include in the dump.
|
||||
*
|
||||
@@ -70,6 +73,7 @@ class DumpWikiAction extends DumpAction
|
||||
StudioImage::TABLE,
|
||||
StudioResource::TABLE,
|
||||
Video::TABLE,
|
||||
VideoScript::TABLE,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -85,7 +89,7 @@ class DumpWikiAction extends DumpAction
|
||||
$filesystem = Storage::disk('local');
|
||||
|
||||
return Str::of($filesystem->path(''))
|
||||
->append('animethemes-db-dump-wiki-')
|
||||
->append(DumpWikiAction::FILENAME_PREFIX)
|
||||
->append(intval(Date::now()->valueOf()))
|
||||
->append('.sql')
|
||||
->__toString();
|
||||
|
||||
@@ -4,10 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Storage\Base;
|
||||
|
||||
use App\Concerns\Repositories\ReconcilesRepositories;
|
||||
use App\Contracts\Actions\Storage\StorageAction;
|
||||
use App\Contracts\Actions\Storage\StorageResults;
|
||||
use App\Contracts\Repositories\RepositoryInterface;
|
||||
use App\Contracts\Storage\InteractsWithDisks;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
@@ -18,8 +16,6 @@ use Illuminate\Support\Facades\Storage;
|
||||
*/
|
||||
abstract class UploadAction implements InteractsWithDisks, StorageAction
|
||||
{
|
||||
use ReconcilesRepositories;
|
||||
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
@@ -50,34 +46,4 @@ abstract class UploadAction implements InteractsWithDisks, StorageAction
|
||||
|
||||
return new UploadResults($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters to repositories before reconciliation.
|
||||
*
|
||||
* @param RepositoryInterface $sourceRepository
|
||||
* @param RepositoryInterface $destinationRepository
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function handleFilters(
|
||||
RepositoryInterface $sourceRepository,
|
||||
RepositoryInterface $destinationRepository,
|
||||
array $data = []
|
||||
): void {
|
||||
$sourceRepository->handleFilter('path', $this->path);
|
||||
$destinationRepository->handleFilter('path', $this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes to be completed after handling action.
|
||||
*
|
||||
* @param StorageResults $storageResults
|
||||
* @return void
|
||||
*/
|
||||
public function then(StorageResults $storageResults): void
|
||||
{
|
||||
$reconcileResults = $this->reconcileRepositories();
|
||||
|
||||
$reconcileResults->toLog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,59 @@ declare(strict_types=1);
|
||||
namespace App\Actions\Storage\Wiki\Audio;
|
||||
|
||||
use App\Actions\Storage\Base\UploadAction;
|
||||
use App\Concerns\Repositories\Wiki\ReconcilesAudioRepositories;
|
||||
use App\Constants\Config\AudioConstants;
|
||||
use App\Contracts\Actions\Storage\StorageResults;
|
||||
use App\Models\Wiki\Audio;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class UploadAudioAction.
|
||||
*/
|
||||
class UploadAudioAction extends UploadAction
|
||||
{
|
||||
use ReconcilesAudioRepositories;
|
||||
/**
|
||||
* Processes to be completed after handling action.
|
||||
*
|
||||
* @param StorageResults $storageResults
|
||||
* @return void
|
||||
*/
|
||||
public function then(StorageResults $storageResults): void
|
||||
{
|
||||
if ($storageResults->toActionResult()->hasFailed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getOrCreateAudio();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get existing or create new audio for file upload.
|
||||
*
|
||||
* @return Audio
|
||||
*/
|
||||
protected function getOrCreateAudio(): Audio
|
||||
{
|
||||
$path = Str::of($this->path)
|
||||
->finish(DIRECTORY_SEPARATOR)
|
||||
->append($this->file->getClientOriginalName())
|
||||
->__toString();
|
||||
|
||||
$attributes = [
|
||||
Audio::ATTRIBUTE_FILENAME => File::name($this->file->getClientOriginalName()),
|
||||
Audio::ATTRIBUTE_MIMETYPE => $this->file->getMimeType(),
|
||||
Audio::ATTRIBUTE_PATH => $path,
|
||||
Audio::ATTRIBUTE_SIZE => $this->file->getSize(),
|
||||
];
|
||||
|
||||
return Audio::updateOrCreate(
|
||||
[
|
||||
Audio::ATTRIBUTE_BASENAME => $this->file->getClientOriginalName(),
|
||||
],
|
||||
$attributes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of disk names.
|
||||
|
||||
@@ -4,9 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Storage\Wiki\Video\Script;
|
||||
|
||||
use App\Actions\Repositories\ReconcileResults;
|
||||
use App\Actions\Storage\Base\UploadAction;
|
||||
use App\Concerns\Repositories\Wiki\Video\ReconcilesScriptRepositories;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Contracts\Actions\Storage\StorageResults;
|
||||
use App\Models\Wiki\Video;
|
||||
@@ -21,8 +19,6 @@ use Illuminate\Support\Str;
|
||||
*/
|
||||
class UploadScriptAction extends UploadAction
|
||||
{
|
||||
use ReconcilesScriptRepositories;
|
||||
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
@@ -40,39 +36,42 @@ class UploadScriptAction extends UploadAction
|
||||
*
|
||||
* @param StorageResults $storageResults
|
||||
* @return void
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public function then(StorageResults $storageResults): void
|
||||
{
|
||||
$reconcileResults = $this->reconcileRepositories();
|
||||
|
||||
$reconcileResults->toLog();
|
||||
|
||||
// The script was successfully uploaded and reconciled into the database, so we can attempt further actions
|
||||
if ($reconcileResults instanceof ReconcileResults) {
|
||||
$this->attachVideo($reconcileResults);
|
||||
if ($storageResults->toActionResult()->hasFailed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getOrCreateScript();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach video if uploaded from Upload Video Action.
|
||||
* Get existing or create new script for file upload.
|
||||
*
|
||||
* @param ReconcileResults $reconcileResults
|
||||
* @return void
|
||||
* @return VideoScript
|
||||
*/
|
||||
protected function attachVideo(ReconcileResults $reconcileResults): void
|
||||
protected function getOrCreateScript(): VideoScript
|
||||
{
|
||||
$path = Str::of($this->path)
|
||||
->finish('/')
|
||||
->finish(DIRECTORY_SEPARATOR)
|
||||
->append($this->file->getClientOriginalName())
|
||||
->__toString();
|
||||
|
||||
$script = $reconcileResults->getCreated()->firstWhere(VideoScript::ATTRIBUTE_PATH, $path);
|
||||
$attributes = [
|
||||
VideoScript::ATTRIBUTE_PATH => $path,
|
||||
];
|
||||
|
||||
if ($script instanceof VideoScript && $this->video !== null) {
|
||||
$script->video()->associate($this->video)->save();
|
||||
if ($this->video !== null) {
|
||||
$attributes[VideoScript::ATTRIBUTE_VIDEO] = $this->video->getKey();
|
||||
}
|
||||
|
||||
return VideoScript::updateOrCreate(
|
||||
[
|
||||
VideoScript::ATTRIBUTE_PATH => $path,
|
||||
],
|
||||
$attributes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,35 +4,38 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Storage\Wiki\Video;
|
||||
|
||||
use App\Actions\Repositories\ReconcileResults;
|
||||
use App\Actions\Storage\Base\UploadAction;
|
||||
use App\Actions\Storage\Wiki\Video\Script\UploadScriptAction;
|
||||
use App\Concerns\Repositories\Wiki\ReconcilesVideoRepositories;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Contracts\Actions\Storage\StorageResults;
|
||||
use App\Enums\Models\Wiki\VideoOverlap;
|
||||
use App\Enums\Models\Wiki\VideoSource;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class UploadVideoAction.
|
||||
*/
|
||||
class UploadVideoAction extends UploadAction
|
||||
{
|
||||
use ReconcilesVideoRepositories;
|
||||
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @param UploadedFile $file
|
||||
* @param string $path
|
||||
* @param array $attributes
|
||||
* @param AnimeThemeEntry|null $entry
|
||||
* @param UploadedFile|null $script
|
||||
*/
|
||||
public function __construct(
|
||||
UploadedFile $file,
|
||||
string $path,
|
||||
protected array $attributes = [],
|
||||
protected ?AnimeThemeEntry $entry = null,
|
||||
protected readonly ?UploadedFile $script = null
|
||||
) {
|
||||
@@ -44,33 +47,81 @@ class UploadVideoAction extends UploadAction
|
||||
*
|
||||
* @param StorageResults $storageResults
|
||||
* @return void
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public function then(StorageResults $storageResults): void
|
||||
{
|
||||
$reconcileResults = $this->reconcileRepositories();
|
||||
|
||||
$reconcileResults->toLog();
|
||||
|
||||
// The video was successfully uploaded and reconciled into the database, so we can attempt further actions
|
||||
if ($reconcileResults instanceof ReconcileResults) {
|
||||
$this->attachEntry($reconcileResults);
|
||||
$this->uploadScript($reconcileResults);
|
||||
if ($storageResults->toActionResult()->hasFailed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$video = $this->getOrCreateVideo();
|
||||
|
||||
$this->attachEntry($video);
|
||||
|
||||
$this->uploadScript($video);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get existing or create new video for file upload.
|
||||
*
|
||||
* @return Video
|
||||
*/
|
||||
protected function getOrCreateVideo(): Video
|
||||
{
|
||||
$path = Str::of($this->path)
|
||||
->finish(DIRECTORY_SEPARATOR)
|
||||
->append($this->file->getClientOriginalName())
|
||||
->__toString();
|
||||
|
||||
$attributes = [
|
||||
Video::ATTRIBUTE_FILENAME => File::name($this->file->getClientOriginalName()),
|
||||
Video::ATTRIBUTE_MIMETYPE => $this->file->getMimeType(),
|
||||
Video::ATTRIBUTE_PATH => $path,
|
||||
Video::ATTRIBUTE_SIZE => $this->file->getSize(),
|
||||
];
|
||||
|
||||
if (Arr::has($this->attributes, Video::ATTRIBUTE_RESOLUTION)) {
|
||||
$attributes[Video::ATTRIBUTE_RESOLUTION] = Arr::get($this->attributes, Video::ATTRIBUTE_RESOLUTION);
|
||||
}
|
||||
if (Arr::has($this->attributes, Video::ATTRIBUTE_NC)) {
|
||||
$attributes[Video::ATTRIBUTE_NC] = Arr::get($this->attributes, Video::ATTRIBUTE_NC);
|
||||
}
|
||||
if (Arr::has($this->attributes, Video::ATTRIBUTE_SUBBED)) {
|
||||
$attributes[Video::ATTRIBUTE_SUBBED] = Arr::get($this->attributes, Video::ATTRIBUTE_SUBBED);
|
||||
}
|
||||
if (Arr::has($this->attributes, Video::ATTRIBUTE_LYRICS)) {
|
||||
$attributes[Video::ATTRIBUTE_LYRICS] = Arr::get($this->attributes, Video::ATTRIBUTE_LYRICS);
|
||||
}
|
||||
if (Arr::has($this->attributes, Video::ATTRIBUTE_UNCEN)) {
|
||||
$attributes[Video::ATTRIBUTE_UNCEN] = Arr::get($this->attributes, Video::ATTRIBUTE_UNCEN);
|
||||
}
|
||||
if (Arr::has($this->attributes, Video::ATTRIBUTE_UNCEN)) {
|
||||
$overlap = VideoOverlap::unstrictCoerce(Arr::get($this->attributes, Video::ATTRIBUTE_OVERLAP));
|
||||
if ($overlap !== null) {
|
||||
$attributes[Video::ATTRIBUTE_OVERLAP] = $overlap;
|
||||
}
|
||||
}
|
||||
if (Arr::has($this->attributes, Video::ATTRIBUTE_SOURCE)) {
|
||||
$attributes[Video::ATTRIBUTE_SOURCE] = VideoSource::unstrictCoerce(Arr::get($this->attributes, Video::ATTRIBUTE_SOURCE));
|
||||
}
|
||||
|
||||
return Video::updateOrCreate(
|
||||
[
|
||||
Video::ATTRIBUTE_BASENAME => $this->file->getClientOriginalName(),
|
||||
],
|
||||
$attributes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach entry to created video if uploaded from entry detail screen.
|
||||
*
|
||||
* @param ReconcileResults $reconcileResults
|
||||
* @param Video $video
|
||||
* @return void
|
||||
*/
|
||||
protected function attachEntry(ReconcileResults $reconcileResults): void
|
||||
protected function attachEntry(Video $video): void
|
||||
{
|
||||
$video = $reconcileResults->getCreated()->firstWhere(Video::ATTRIBUTE_BASENAME, $this->file->getClientOriginalName());
|
||||
|
||||
if ($video instanceof Video && $this->entry !== null) {
|
||||
if ($this->entry !== null && $video->wasRecentlyCreated) {
|
||||
$video->animethemeentries()->attach($this->entry);
|
||||
}
|
||||
}
|
||||
@@ -78,18 +129,12 @@ class UploadVideoAction extends UploadAction
|
||||
/**
|
||||
* Upload & Associate Script if video upload was successful.
|
||||
*
|
||||
* @param ReconcileResults $reconcileResults
|
||||
* @param Video $video
|
||||
* @return void
|
||||
*/
|
||||
protected function uploadScript(ReconcileResults $reconcileResults): void
|
||||
protected function uploadScript(Video $video): void
|
||||
{
|
||||
$video = $reconcileResults->getCreated()->firstWhere(Video::ATTRIBUTE_BASENAME, $this->file->getClientOriginalName());
|
||||
|
||||
if ($video === null) {
|
||||
$video = $reconcileResults->getUpdated()->firstWhere(Video::ATTRIBUTE_BASENAME, $this->file->getClientOriginalName());
|
||||
}
|
||||
|
||||
if ($video instanceof Video && $this->script !== null) {
|
||||
if ($this->script !== null) {
|
||||
$uploadScript = new UploadScriptAction($this->script, $this->path, $video);
|
||||
|
||||
$scriptResult = $uploadScript->handle();
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Isolatable;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Class BaseCommand.
|
||||
*/
|
||||
abstract class BaseCommand extends Command implements Isolatable
|
||||
{
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
abstract public function handle(): int;
|
||||
|
||||
/**
|
||||
* Get the validator for options.
|
||||
*
|
||||
* @return Validator
|
||||
*/
|
||||
abstract protected function validator(): Validator;
|
||||
|
||||
/**
|
||||
* Configure the console command for isolation.
|
||||
* Note: Overrides default framework behavior which disables isolation by default.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
protected function configureIsolation(): void
|
||||
{
|
||||
$this->getDefinition()->addOption(new InputOption(
|
||||
'isolated',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Do not run the command if another instance of the command is already running',
|
||||
true
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,14 @@ declare(strict_types=1);
|
||||
namespace App\Console\Commands\Repositories;
|
||||
|
||||
use App\Concerns\Repositories\ReconcilesRepositories;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Console\Commands\BaseCommand;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Class ReconcileCommand.
|
||||
*/
|
||||
abstract class ReconcileCommand extends Command
|
||||
abstract class ReconcileCommand extends BaseCommand
|
||||
{
|
||||
use ReconcilesRepositories;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Console\Commands\Storage\Admin;
|
||||
|
||||
use App\Actions\Storage\Admin\Dump\DumpAction;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Console\Commands\BaseCommand;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator as ValidatorFacade;
|
||||
@@ -14,7 +14,7 @@ use Illuminate\Validation\Rule;
|
||||
/**
|
||||
* Class DatabaseDumpCommand.
|
||||
*/
|
||||
abstract class DumpCommand extends Command
|
||||
abstract class DumpCommand extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* Execute the console command.
|
||||
|
||||
@@ -4,15 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Storage;
|
||||
|
||||
use App\Console\Commands\BaseCommand;
|
||||
use App\Contracts\Actions\Storage\StorageAction;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class StorageCommand.
|
||||
*/
|
||||
abstract class StorageCommand extends Command
|
||||
abstract class StorageCommand extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* Execute the console command.
|
||||
|
||||
@@ -21,6 +21,8 @@ class VideoConstants
|
||||
|
||||
final public const PATH_QUALIFIED = 'video.path';
|
||||
|
||||
final public const RATE_LIMITER_QUALIFIED = 'video.rate_limiter';
|
||||
|
||||
final public const STREAMING_METHOD_QUALIFIED = 'video.streaming_method';
|
||||
|
||||
final public const URL_QUALIFIED = 'video.url';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class StreamingMethod.
|
||||
*/
|
||||
class StreamingMethod extends BaseEnum
|
||||
{
|
||||
public const NGINX = 'nginx';
|
||||
public const RESPONSE = 'response';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Wiki\Video;
|
||||
|
||||
use App\Events\BaseEvent;
|
||||
use App\Models\Wiki\Video;
|
||||
|
||||
/**
|
||||
* Class VideoCreating.
|
||||
*
|
||||
* @extends BaseEvent<Video>
|
||||
*/
|
||||
class VideoCreating extends BaseEvent
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Video $video
|
||||
*/
|
||||
public function __construct(Video $video)
|
||||
{
|
||||
parent::__construct($video);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model that has fired this event.
|
||||
*
|
||||
* @return Video
|
||||
*/
|
||||
public function getModel(): Video
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Wiki\Video;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Contracts\Events\DiscordMessageEvent;
|
||||
use App\Enums\Discord\EmbedColor;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
/**
|
||||
* Class VideoThrottled.
|
||||
*/
|
||||
class VideoThrottled implements DiscordMessageEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create new event instance.
|
||||
*
|
||||
* @param Video $video
|
||||
* @param string $user
|
||||
*/
|
||||
public function __construct(protected Video $video, protected string $user)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Discord message payload.
|
||||
*
|
||||
* @return DiscordMessage
|
||||
*/
|
||||
public function getDiscordMessage(): DiscordMessage
|
||||
{
|
||||
return DiscordMessage::create('', [
|
||||
'description' => "Video '**{$this->video->getName()}**' throttled for user '**$this->user**'",
|
||||
'color' => EmbedColor::YELLOW,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Discord channel the message will be sent to.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDiscordChannel(): string
|
||||
{
|
||||
return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class BalanceMonthToDateField extends FloatField implements CreatableField, Upda
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
'regex:/^\-?\d+(\.\d{1,2})?$/',
|
||||
'decimal:0,2',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class BalanceMonthToDateField extends FloatField implements CreatableField, Upda
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'regex:/^\-?\d+(\.\d{1,2})?$/',
|
||||
'decimal:0,2',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class BalanceUsageField extends FloatField implements CreatableField, UpdatableF
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
'regex:/^\-?\d+(\.\d{1,2})?$/',
|
||||
'decimal:0,2',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class BalanceUsageField extends FloatField implements CreatableField, UpdatableF
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'regex:/^\-?\d+(\.\d{1,2})?$/',
|
||||
'decimal:0,2',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class TransactionAmountField extends FloatField implements CreatableField, Updat
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
'regex:/^\-?\d+(\.\d{1,2})?$/',
|
||||
'decimal:0,2',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class TransactionAmountField extends FloatField implements CreatableField, Updat
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'regex:/^\-?\d+(\.\d{1,2})?$/',
|
||||
'decimal:0,2',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Http\Api\Schema\EloquentSchema;
|
||||
use App\Http\Api\Schema\Wiki\Anime\SynonymSchema;
|
||||
use App\Http\Api\Schema\Wiki\Anime\Theme\EntrySchema;
|
||||
use App\Http\Api\Schema\Wiki\Anime\ThemeSchema;
|
||||
use App\Http\Api\Schema\Wiki\Video\ScriptSchema;
|
||||
use App\Http\Resources\Wiki\Resource\AnimeResource;
|
||||
use App\Models\Wiki\Anime;
|
||||
|
||||
@@ -58,6 +59,7 @@ class AnimeSchema extends EloquentSchema
|
||||
new AllowedInclude(new EntrySchema(), Anime::RELATION_ENTRIES),
|
||||
new AllowedInclude(new ExternalResourceSchema(), Anime::RELATION_RESOURCES),
|
||||
new AllowedInclude(new ImageSchema(), Anime::RELATION_IMAGES),
|
||||
new AllowedInclude(new ScriptSchema(), Anime::RELATION_SCRIPTS),
|
||||
new AllowedInclude(new SeriesSchema(), Anime::RELATION_SERIES),
|
||||
new AllowedInclude(new SongSchema(), Anime::RELATION_SONG),
|
||||
new AllowedInclude(new StudioSchema(), Anime::RELATION_STUDIOS),
|
||||
|
||||
@@ -4,12 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Constants\Config\DumpConstants;
|
||||
use App\Actions\Http\Admin\Dump\DumpDownloadAction;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Admin\Dump;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
@@ -25,9 +22,8 @@ class DumpController extends Controller
|
||||
*/
|
||||
public function show(Dump $dump): StreamedResponse
|
||||
{
|
||||
/** @var FilesystemAdapter $fs */
|
||||
$fs = Storage::disk(Config::get(DumpConstants::DISK_QUALIFIED));
|
||||
$action = new DumpDownloadAction($dump);
|
||||
|
||||
return $fs->download($dump->path);
|
||||
return $action->download();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Actions\Http\Admin\Dump\DumpDownloadAction;
|
||||
use App\Actions\Storage\Admin\Dump\DumpDocumentAction;
|
||||
use App\Enums\Http\Api\Filter\ComparisonOperator;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Admin\Dump;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* Class LatestDocumentDumpController.
|
||||
*/
|
||||
class LatestDocumentDumpController extends Controller
|
||||
{
|
||||
/**
|
||||
* Download dump.
|
||||
*
|
||||
* @return StreamedResponse
|
||||
*
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function show(): StreamedResponse
|
||||
{
|
||||
/** @var Dump $dump */
|
||||
$dump = Dump::query()
|
||||
->where(Dump::ATTRIBUTE_PATH, ComparisonOperator::LIKE, DumpDocumentAction::FILENAME_PREFIX.'%')
|
||||
->latest()
|
||||
->firstOrFail();
|
||||
|
||||
$action = new DumpDownloadAction($dump);
|
||||
|
||||
return $action->download();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Actions\Http\Admin\Dump\DumpDownloadAction;
|
||||
use App\Actions\Storage\Admin\Dump\DumpWikiAction;
|
||||
use App\Enums\Http\Api\Filter\ComparisonOperator;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Admin\Dump;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* Class LatestWikiDumpController.
|
||||
*/
|
||||
class LatestWikiDumpController extends Controller
|
||||
{
|
||||
/**
|
||||
* Download dump.
|
||||
*
|
||||
* @return StreamedResponse
|
||||
*
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function show(): StreamedResponse
|
||||
{
|
||||
/** @var Dump $dump */
|
||||
$dump = Dump::query()
|
||||
->where(Dump::ATTRIBUTE_PATH, ComparisonOperator::LIKE, DumpWikiAction::FILENAME_PREFIX.'%')
|
||||
->latest()
|
||||
->firstOrFail();
|
||||
|
||||
$action = new DumpDownloadAction($dump);
|
||||
|
||||
return $action->download();
|
||||
}
|
||||
}
|
||||
@@ -4,53 +4,39 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Wiki\Audio;
|
||||
|
||||
use App\Actions\Http\StreamAction;
|
||||
use App\Actions\Http\Wiki\Audio\AudioNginxStreamAction;
|
||||
use App\Actions\Http\Wiki\Audio\AudioResponseStreamAction;
|
||||
use App\Constants\Config\AudioConstants;
|
||||
use App\Http\Controllers\Wiki\StreamableController;
|
||||
use App\Enums\Http\StreamingMethod;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Wiki\Audio;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Class AudioController.
|
||||
*/
|
||||
class AudioController extends StreamableController
|
||||
class AudioController extends Controller
|
||||
{
|
||||
/**
|
||||
* Stream audio through configured streaming method.
|
||||
*
|
||||
* @param Audio $audio
|
||||
* @return Response|StreamedResponse
|
||||
* @return Response
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function show(Audio $audio): Response|StreamedResponse
|
||||
public function show(Audio $audio): Response
|
||||
{
|
||||
return match (Config::get(AudioConstants::STREAMING_METHOD_QUALIFIED)) {
|
||||
'response' => $this->throughResponse($audio),
|
||||
'nginx' => $this->throughNginx($audio),
|
||||
/** @var StreamAction $action */
|
||||
$action = match (Config::get(AudioConstants::STREAMING_METHOD_QUALIFIED)) {
|
||||
StreamingMethod::RESPONSE => new AudioResponseStreamAction($audio),
|
||||
StreamingMethod::NGINX => new AudioNginxStreamAction($audio),
|
||||
default => throw new RuntimeException('AUDIO_STREAMING_METHOD must be specified in your .env file'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(AudioConstants::DEFAULT_DISK_QUALIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the nginx internal redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function nginxRedirect(): string
|
||||
{
|
||||
return Config::get(AudioConstants::NGINX_REDIRECT_QUALIFIED);
|
||||
return $action->stream();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Wiki;
|
||||
|
||||
use App\Contracts\Models\Streamable;
|
||||
use App\Contracts\Storage\InteractsWithDisk;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* Class StreamableController.
|
||||
*/
|
||||
abstract class StreamableController extends Controller implements InteractsWithDisk
|
||||
{
|
||||
/**
|
||||
* Stream model through optimized framework streamed response.
|
||||
*
|
||||
* @param Streamable $streamable
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
protected function throughResponse(Streamable $streamable): StreamedResponse
|
||||
{
|
||||
$response = new StreamedResponse();
|
||||
|
||||
$disposition = $response->headers->makeDisposition('inline', $streamable->basename());
|
||||
|
||||
$response->headers->replace([
|
||||
'Accept-Ranges' => 'bytes',
|
||||
'Content-Type' => $streamable->mimetype(),
|
||||
'Content-Length' => $streamable->size(),
|
||||
'Content-Disposition' => $disposition,
|
||||
]);
|
||||
|
||||
$fs = Storage::disk($this->disk());
|
||||
|
||||
$response->setCallback(function () use ($fs, $streamable) {
|
||||
$stream = $fs->readStream($streamable->path());
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
});
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream model through configured nginx internal redirect.
|
||||
*
|
||||
* @param Streamable $streamable
|
||||
* @return Response
|
||||
*/
|
||||
protected function throughNginx(Streamable $streamable): Response
|
||||
{
|
||||
$fs = Storage::disk($this->disk());
|
||||
|
||||
// Generate temporary link for the object
|
||||
$temporaryURL = $fs->temporaryUrl($streamable->path(), now()->addMinutes(5));
|
||||
|
||||
// Get the url information
|
||||
$url_scheme = parse_url($temporaryURL, PHP_URL_SCHEME);
|
||||
$url_host = parse_url($temporaryURL, PHP_URL_HOST);
|
||||
$url_path_query = parse_url($temporaryURL, PHP_URL_PATH).'?'.parse_url($temporaryURL, PHP_URL_QUERY);
|
||||
|
||||
// Construct the new link for the redirect
|
||||
$link = "{$this->nginxRedirect()}$url_scheme/$url_host$url_path_query";
|
||||
|
||||
$response = new Response();
|
||||
|
||||
$disposition = $response->headers->makeDisposition('inline', $streamable->basename());
|
||||
|
||||
return $response->withHeaders([
|
||||
'Accept-Ranges' => 'bytes',
|
||||
'Content-Type' => $streamable->mimetype(),
|
||||
'Content-Length' => $streamable->size(),
|
||||
'Content-Disposition' => $disposition,
|
||||
'X-Accel-Redirect' => $link,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the nginx internal redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function nginxRedirect(): string;
|
||||
}
|
||||
@@ -4,12 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Wiki\Video\Script;
|
||||
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Actions\Http\Wiki\Video\Script\ScriptDownloadAction;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Wiki\Video\VideoScript;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
@@ -25,9 +22,8 @@ class ScriptController extends Controller
|
||||
*/
|
||||
public function show(VideoScript $script): StreamedResponse
|
||||
{
|
||||
/** @var FilesystemAdapter $fs */
|
||||
$fs = Storage::disk(Config::get(VideoConstants::SCRIPT_DISK_QUALIFIED));
|
||||
$action = new ScriptDownloadAction($script);
|
||||
|
||||
return $fs->download($script->path);
|
||||
return $action->download();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,51 +4,39 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Wiki\Video;
|
||||
|
||||
use App\Actions\Http\StreamAction;
|
||||
use App\Actions\Http\Wiki\Video\VideoNginxStreamAction;
|
||||
use App\Actions\Http\Wiki\Video\VideoResponseStreamAction;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Http\Controllers\Wiki\StreamableController;
|
||||
use App\Enums\Http\StreamingMethod;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Class VideoController.
|
||||
*/
|
||||
class VideoController extends StreamableController
|
||||
class VideoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Stream video through configured streaming method.
|
||||
*
|
||||
* @param Video $video
|
||||
* @return Response|StreamedResponse
|
||||
* @return Response
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function show(Video $video): Response|StreamedResponse
|
||||
public function show(Video $video): Response
|
||||
{
|
||||
return match (Config::get(VideoConstants::STREAMING_METHOD_QUALIFIED)) {
|
||||
'response' => $this->throughResponse($video),
|
||||
'nginx' => $this->throughNginx($video),
|
||||
/** @var StreamAction $action */
|
||||
$action = match (Config::get(VideoConstants::STREAMING_METHOD_QUALIFIED)) {
|
||||
StreamingMethod::RESPONSE => new VideoResponseStreamAction($video),
|
||||
StreamingMethod::NGINX => new VideoNginxStreamAction($video),
|
||||
default => throw new RuntimeException('VIDEO_STREAMING_METHOD must be specified in your .env file'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disk(): string
|
||||
{
|
||||
return Config::get(VideoConstants::DEFAULT_DISK_QUALIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the nginx internal redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function nginxRedirect(): string
|
||||
{
|
||||
return Config::get(VideoConstants::NGINX_REDIRECT_QUALIFIED);
|
||||
return $action->stream();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Listeners\Wiki\Video;
|
||||
|
||||
use App\Enums\Models\Wiki\VideoSource;
|
||||
use App\Events\Wiki\Video\VideoCreating;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class InitializeVideoTags.
|
||||
*/
|
||||
class InitializeVideoTags
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param VideoCreating $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(VideoCreating $event): void
|
||||
{
|
||||
$video = $event->getModel();
|
||||
|
||||
try {
|
||||
// Match Tags of filename
|
||||
// Format: "{Base Name}-{OP|ED}{Sequence}v{Version}-{Tags}"
|
||||
preg_match('/^.*-(?:OP|ED).*-(.*)$/', $video->filename, $tagsMatch);
|
||||
|
||||
// Check if the filename has tags, which is not guaranteed
|
||||
if (! empty($tagsMatch)) {
|
||||
$tags = $tagsMatch[1];
|
||||
|
||||
// Set true/false if tag is included/excluded
|
||||
$video->nc = Str::contains($tags, 'NC');
|
||||
$video->subbed = Str::contains($tags, 'Subbed');
|
||||
$video->lyrics = Str::contains($tags, 'Lyrics');
|
||||
// Note: Our naming convention does not include "Uncen"
|
||||
|
||||
// Set resolution to numeric tag if included
|
||||
preg_match('/\d+/', $tags, $resolution);
|
||||
if (! empty($resolution)) {
|
||||
$video->resolution = intval($resolution[0]);
|
||||
}
|
||||
|
||||
// Special cases for implicit resolution
|
||||
if (in_array($tags, ['NCBD', 'NCBDLyrics'])) {
|
||||
$video->resolution = 720;
|
||||
}
|
||||
|
||||
// Set source type for first matching tag to key
|
||||
foreach (VideoSource::getKeys() as $sourceKey) {
|
||||
if (Str::contains($tags, $sourceKey)) {
|
||||
$video->source = VideoSource::getValue($sourceKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Our naming convention does not include Overlap type
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,7 @@ class Anime extends BaseModel
|
||||
final public const RELATION_ENTRIES = 'animethemes.animethemeentries';
|
||||
final public const RELATION_IMAGES = 'images';
|
||||
final public const RELATION_RESOURCES = 'resources';
|
||||
final public const RELATION_SCRIPTS = 'animethemes.animethemeentries.videos.videoscript';
|
||||
final public const RELATION_SERIES = 'series';
|
||||
final public const RELATION_SONG = 'animethemes.song';
|
||||
final public const RELATION_STUDIOS = 'studios';
|
||||
|
||||
@@ -8,7 +8,6 @@ use App\Contracts\Models\Streamable;
|
||||
use App\Enums\Models\Wiki\VideoOverlap;
|
||||
use App\Enums\Models\Wiki\VideoSource;
|
||||
use App\Events\Wiki\Video\VideoCreated;
|
||||
use App\Events\Wiki\Video\VideoCreating;
|
||||
use App\Events\Wiki\Video\VideoDeleted;
|
||||
use App\Events\Wiki\Video\VideoRestored;
|
||||
use App\Events\Wiki\Video\VideoUpdated;
|
||||
@@ -114,7 +113,6 @@ class Video extends BaseModel implements Streamable, Viewable
|
||||
*/
|
||||
protected $dispatchesEvents = [
|
||||
'created' => VideoCreated::class,
|
||||
'creating' => VideoCreating::class,
|
||||
'deleted' => VideoDeleted::class,
|
||||
'restored' => VideoRestored::class,
|
||||
'updated' => VideoUpdated::class,
|
||||
|
||||
@@ -43,6 +43,7 @@ class VideoScript extends BaseModel
|
||||
*/
|
||||
protected $fillable = [
|
||||
VideoScript::ATTRIBUTE_PATH,
|
||||
VideoScript::ATTRIBUTE_VIDEO,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,10 @@ namespace App\Nova\Actions\Storage\Wiki\Video;
|
||||
|
||||
use App\Actions\Storage\Wiki\Video\UploadVideoAction as UploadVideo;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Enums\Models\Wiki\VideoOverlap;
|
||||
use App\Enums\Models\Wiki\VideoSource;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Nova\Actions\Storage\Base\UploadAction;
|
||||
use App\Rules\Wiki\Submission\Audio\AudioChannelLayoutStreamRule;
|
||||
use App\Rules\Wiki\Submission\Audio\AudioChannelsStreamRule;
|
||||
@@ -28,14 +31,19 @@ use App\Rules\Wiki\Submission\Video\VideoColorSpaceStreamRule;
|
||||
use App\Rules\Wiki\Submission\Video\VideoColorTransferStreamRule;
|
||||
use App\Rules\Wiki\Submission\Video\VideoIndexStreamRule;
|
||||
use App\Rules\Wiki\Submission\Video\VideoPixelFormatStreamRule;
|
||||
use BenSampo\Enum\Enum;
|
||||
use BenSampo\Enum\Rules\EnumValue;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Validation\Rules\File as FileRule;
|
||||
use Laravel\Nova\Fields\ActionFields;
|
||||
use Laravel\Nova\Fields\Boolean;
|
||||
use Laravel\Nova\Fields\File;
|
||||
use Laravel\Nova\Fields\Heading;
|
||||
use Laravel\Nova\Fields\Hidden;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Select;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
/**
|
||||
@@ -74,6 +82,47 @@ class UploadVideoAction extends UploadAction
|
||||
Hidden::make(__('nova.resources.singularLabel.anime_theme_entry'), AnimeThemeEntry::ATTRIBUTE_ID)
|
||||
->default(fn () => $parent instanceof AnimeThemeEntry ? $parent->getKey() : null),
|
||||
|
||||
Number::make(__('nova.fields.video.resolution.name'), Video::ATTRIBUTE_RESOLUTION)
|
||||
->min(360)
|
||||
->max(1080)
|
||||
->nullable()
|
||||
->rules(['nullable', 'integer'])
|
||||
->help(__('nova.fields.video.resolution.help')),
|
||||
|
||||
Boolean::make(__('nova.fields.video.nc.name'), Video::ATTRIBUTE_NC)
|
||||
->nullable()
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.nc.help')),
|
||||
|
||||
Boolean::make(__('nova.fields.video.subbed.name'), Video::ATTRIBUTE_SUBBED)
|
||||
->nullable()
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.subbed.help')),
|
||||
|
||||
Boolean::make(__('nova.fields.video.lyrics.name'), Video::ATTRIBUTE_LYRICS)
|
||||
->nullable()
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.lyrics.help')),
|
||||
|
||||
Boolean::make(__('nova.fields.video.uncen.name'), Video::ATTRIBUTE_UNCEN)
|
||||
->nullable()
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.uncen.help')),
|
||||
|
||||
Select::make(__('nova.fields.video.overlap.name'), Video::ATTRIBUTE_OVERLAP)
|
||||
->options(VideoOverlap::asSelectArray())
|
||||
->displayUsing(fn (?Enum $enum) => $enum?->description)
|
||||
->nullable()
|
||||
->rules(['nullable', new EnumValue(VideoOverlap::class, false)])
|
||||
->help(__('nova.fields.video.overlap.help')),
|
||||
|
||||
Select::make(__('nova.fields.video.source.name'), Video::ATTRIBUTE_SOURCE)
|
||||
->options(VideoSource::asSelectArray())
|
||||
->displayUsing(fn (?Enum $enum) => $enum?->description)
|
||||
->nullable()
|
||||
->rules(['nullable', new EnumValue(VideoSource::class, false)])
|
||||
->help(__('nova.fields.video.source.help')),
|
||||
|
||||
Heading::make(__('nova.resources.singularLabel.video_script')),
|
||||
|
||||
File::make(__('nova.resources.singularLabel.video_script'), 'script')
|
||||
@@ -93,6 +142,7 @@ class UploadVideoAction extends UploadAction
|
||||
*/
|
||||
protected function action(ActionFields $fields, Collection $models): UploadVideo
|
||||
{
|
||||
/** @var string $path */
|
||||
$path = $fields->get('path');
|
||||
|
||||
/** @var UploadedFile $file */
|
||||
@@ -104,7 +154,17 @@ class UploadVideoAction extends UploadAction
|
||||
/** @var UploadedFile|null $script */
|
||||
$script = $fields->get('script');
|
||||
|
||||
return new UploadVideo($file, $path, $entry, $script);
|
||||
$attributes = [
|
||||
Video::ATTRIBUTE_RESOLUTION => $fields->get(Video::ATTRIBUTE_RESOLUTION),
|
||||
Video::ATTRIBUTE_NC => $fields->get(Video::ATTRIBUTE_NC),
|
||||
Video::ATTRIBUTE_SUBBED => $fields->get(Video::ATTRIBUTE_SUBBED),
|
||||
Video::ATTRIBUTE_LYRICS => $fields->get(Video::ATTRIBUTE_LYRICS),
|
||||
Video::ATTRIBUTE_UNCEN => $fields->get(Video::ATTRIBUTE_UNCEN),
|
||||
Video::ATTRIBUTE_OVERLAP => $fields->get(Video::ATTRIBUTE_OVERLAP),
|
||||
Video::ATTRIBUTE_SOURCE => $fields->get(Video::ATTRIBUTE_SOURCE),
|
||||
];
|
||||
|
||||
return new UploadVideo($file, $path, $attributes, $entry, $script);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,12 +5,10 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Lenses\Anime;
|
||||
|
||||
use App\Enums\Models\Wiki\ImageFacet;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Image;
|
||||
use App\Nova\Actions\Models\Wiki\Anime\BackfillAnimeAction;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
/**
|
||||
@@ -65,11 +63,7 @@ abstract class AnimeImageLens extends AnimeLens
|
||||
->confirmButtonText(__('nova.actions.anime.backfill.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('update anime');
|
||||
}),
|
||||
->canSeeWhen('update', $this->resource),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Lenses\Anime;
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Nova\Actions\Models\Wiki\Anime\AttachAnimeResourceAction;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
/**
|
||||
@@ -65,11 +63,7 @@ abstract class AnimeResourceLens extends AnimeLens
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create external resource');
|
||||
}),
|
||||
->canSeeWhen('create', ExternalResource::class),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Nova\Lenses\Anime\Studio;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Nova\Actions\Models\Wiki\Anime\BackfillAnimeAction;
|
||||
use App\Nova\Lenses\Anime\AnimeLens;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
/**
|
||||
@@ -55,11 +53,7 @@ class AnimeStudioLens extends AnimeLens
|
||||
->confirmButtonText(__('nova.actions.anime.backfill.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('update anime');
|
||||
}),
|
||||
->canSeeWhen('update', $this->resource),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,10 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Lenses\Artist;
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Nova\Actions\Models\Wiki\Artist\AttachArtistResourceAction;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
/**
|
||||
@@ -65,11 +63,7 @@ abstract class ArtistResourceLens extends ArtistLens
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create external resource');
|
||||
}),
|
||||
->canSeeWhen('create', ExternalResource::class),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Nova\Lenses\Audio;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Audio;
|
||||
use App\Nova\Actions\Storage\Wiki\Audio\DeleteAudioAction;
|
||||
use App\Nova\Lenses\BaseLens;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\DateTime;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
@@ -106,11 +104,7 @@ class AudioVideoLens extends BaseLens
|
||||
->confirmButtonText(__('nova.actions.audio.delete.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('delete audio');
|
||||
}),
|
||||
->canSeeWhen('delete', $this->resource),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,11 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Lenses\Studio\Image;
|
||||
|
||||
use App\Enums\Models\Wiki\ImageFacet;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Image;
|
||||
use App\Models\Wiki\Studio;
|
||||
use App\Nova\Actions\Models\Wiki\Studio\BackfillStudioAction;
|
||||
use App\Nova\Lenses\Studio\StudioLens;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
/**
|
||||
@@ -59,11 +57,7 @@ class StudioCoverLargeLens extends StudioLens
|
||||
->confirmButtonText(__('nova.actions.studio.backfill.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('update studio');
|
||||
}),
|
||||
->canSeeWhen('update', $this->resource),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Lenses\Studio;
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Models\Wiki\Studio;
|
||||
use App\Nova\Actions\Models\Wiki\Studio\AttachStudioResourceAction;
|
||||
@@ -64,11 +63,7 @@ abstract class StudioResourceLens extends StudioLens
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create external resource');
|
||||
}),
|
||||
->canSeeWhen('create', ExternalResource::class),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace App\Nova\Lenses\Video;
|
||||
use App\Enums\Http\Api\Filter\ComparisonOperator;
|
||||
use App\Enums\Models\Wiki\VideoOverlap;
|
||||
use App\Enums\Models\Wiki\VideoSource;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Nova\Actions\Models\Wiki\Video\BackfillAudioAction;
|
||||
@@ -15,7 +14,6 @@ use App\Nova\Lenses\BaseLens;
|
||||
use BenSampo\Enum\Enum;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\Boolean;
|
||||
use Laravel\Nova\Fields\DateTime;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
@@ -137,11 +135,7 @@ class VideoAudioLens extends BaseLens
|
||||
->showOnIndex()
|
||||
->showOnDetail()
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('update video');
|
||||
}),
|
||||
->canSeeWhen('update', $this->resource),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,8 @@ class Announcement extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), AnnouncementModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Code::make(__('nova.fields.announcement.content'), AnnouncementModel::ATTRIBUTE_CONTENT)
|
||||
->rules(['required', 'max:65535'])
|
||||
|
||||
@@ -5,14 +5,12 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Resources\Admin;
|
||||
|
||||
use App\Models\Admin\Dump as DumpModel;
|
||||
use App\Models\Auth\User;
|
||||
use App\Nova\Actions\Repositories\Storage\Admin\Dump\ReconcileDumpAction;
|
||||
use App\Nova\Actions\Storage\Admin\DumpDocumentAction;
|
||||
use App\Nova\Actions\Storage\Admin\DumpWikiAction;
|
||||
use App\Nova\Actions\Storage\Admin\PruneDumpAction;
|
||||
use App\Nova\Resources\BaseResource;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
@@ -120,12 +118,16 @@ class Dump extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), DumpModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.dump.path'), DumpModel::ATTRIBUTE_PATH)
|
||||
->copyable()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Panel::make(__('nova.fields.base.timestamps'), $this->timestamps()),
|
||||
];
|
||||
@@ -147,44 +149,28 @@ class Dump extends BaseResource
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create dump');
|
||||
}),
|
||||
->canSeeWhen('create', DumpModel::class),
|
||||
|
||||
(new DumpDocumentAction())
|
||||
->confirmButtonText(__('nova.actions.dump.dump.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create dump');
|
||||
}),
|
||||
->canSeeWhen('create', DumpModel::class),
|
||||
|
||||
(new PruneDumpAction())
|
||||
->confirmButtonText(__('nova.actions.storage.prune.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('delete dump');
|
||||
}),
|
||||
->canSeeWhen('delete', $this),
|
||||
|
||||
(new ReconcileDumpAction())
|
||||
->confirmButtonText(__('nova.actions.repositories.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create dump');
|
||||
}),
|
||||
->canSeeWhen('create', DumpModel::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,21 +113,28 @@ class Setting extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), SettingModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.setting.key'), SettingModel::ATTRIBUTE_KEY)
|
||||
->sortable()
|
||||
->copyable()
|
||||
->rules(['required', 'max:192'])
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.setting.value'), SettingModel::ATTRIBUTE_VALUE)
|
||||
->sortable()
|
||||
->copyable()
|
||||
->rules(['required', 'max:65535'])
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(65535)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,14 +116,18 @@ class Permission extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), PermissionModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.permission.name'), PermissionModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
->copyable()
|
||||
->rules(['required', 'max:192'])
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.roles'), PermissionModel::RELATION_ROLES, Role::class)
|
||||
->filterable(),
|
||||
@@ -149,16 +153,14 @@ class Permission extends BaseResource
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showOnIndex()
|
||||
->showOnDetail()
|
||||
->showInline()
|
||||
->canRun(fn (NovaRequest $novaRequest) => $novaRequest->user()->can('view permission')),
|
||||
->showInline(),
|
||||
|
||||
(new RevokeRoleAction())
|
||||
->confirmButtonText(__('nova.actions.base.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->showOnIndex()
|
||||
->showOnDetail()
|
||||
->showInline()
|
||||
->canRun(fn (NovaRequest $novaRequest) => $novaRequest->user()->can('view permission')),
|
||||
->showInline(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,14 +116,18 @@ class Role extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), RoleModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.role.name'), RoleModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
->copyable()
|
||||
->rules(['required', 'max:192'])
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.permissions'), RoleModel::RELATION_PERMISSIONS, Permission::class)
|
||||
->filterable(),
|
||||
|
||||
@@ -128,17 +128,22 @@ class User extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), UserModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Gravatar::make()->maxWidth(50)
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.user.name'), UserModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
->copyable()
|
||||
->rules(['required', 'max:192', 'alpha_dash'])
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Email::make(__('nova.fields.user.email'), UserModel::ATTRIBUTE_EMAIL)
|
||||
->sortable()
|
||||
@@ -150,7 +155,8 @@ class User extends BaseResource
|
||||
->__toString()
|
||||
)
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.roles'), UserModel::RELATION_ROLES, Role::class)
|
||||
->filterable(),
|
||||
|
||||
@@ -54,21 +54,24 @@ abstract class BaseResource extends NovaResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
DateTime::make(__('nova.fields.base.updated_at'), BaseModel::ATTRIBUTE_UPDATED_AT)
|
||||
->hideFromIndex()
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
DateTime::make(__('nova.fields.base.deleted_at'), BaseModel::ATTRIBUTE_DELETED_AT)
|
||||
->hideFromIndex()
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,11 @@ namespace App\Nova\Resources\Billing;
|
||||
|
||||
use App\Enums\Models\Billing\BalanceFrequency;
|
||||
use App\Enums\Models\Billing\Service;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Billing\Balance as BalanceModel;
|
||||
use App\Nova\Actions\Repositories\Billing\Balance\ReconcileBalanceAction;
|
||||
use App\Nova\Resources\BaseResource;
|
||||
use BenSampo\Enum\Enum;
|
||||
use BenSampo\Enum\Rules\EnumValue;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\Currency;
|
||||
use Laravel\Nova\Fields\Date;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
@@ -120,14 +118,16 @@ class Balance extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), BalanceModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Date::make(__('nova.fields.balance.date.name'), BalanceModel::ATTRIBUTE_DATE)
|
||||
->sortable()
|
||||
->rules('required')
|
||||
->help(__('nova.fields.balance.date.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.balance.service.name'), BalanceModel::ATTRIBUTE_SERVICE)
|
||||
->options(Service::asSelectArray())
|
||||
@@ -136,7 +136,8 @@ class Balance extends BaseResource
|
||||
->rules(['required', new EnumValue(Service::class, false)])
|
||||
->help(__('nova.fields.balance.service.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.balance.frequency.name'), BalanceModel::ATTRIBUTE_FREQUENCY)
|
||||
->options(BalanceFrequency::asSelectArray())
|
||||
@@ -145,21 +146,24 @@ class Balance extends BaseResource
|
||||
->rules(['required', new EnumValue(BalanceFrequency::class, false)])
|
||||
->help(__('nova.fields.balance.frequency.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Currency::make(__('nova.fields.balance.usage.name'), BalanceModel::ATTRIBUTE_USAGE)
|
||||
->sortable()
|
||||
->rules('required')
|
||||
->help(__('nova.fields.balance.usage.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Currency::make(__('nova.fields.balance.balance.name'), BalanceModel::ATTRIBUTE_BALANCE)
|
||||
->sortable()
|
||||
->rules('required')
|
||||
->help(__('nova.fields.balance.balance.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Panel::make(__('nova.fields.base.timestamps'), $this->timestamps())
|
||||
->collapsable(),
|
||||
@@ -182,11 +186,7 @@ class Balance extends BaseResource
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create balance');
|
||||
}),
|
||||
->canSeeWhen('create', BalanceModel::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Resources\Billing;
|
||||
|
||||
use App\Enums\Models\Billing\Service;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Billing\Transaction as TransactionModel;
|
||||
use App\Nova\Actions\Repositories\Billing\Transaction\ReconcileTransactionAction;
|
||||
use App\Nova\Resources\BaseResource;
|
||||
use BenSampo\Enum\Enum;
|
||||
use BenSampo\Enum\Rules\EnumValue;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\Currency;
|
||||
use Laravel\Nova\Fields\Date;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
@@ -123,14 +121,16 @@ class Transaction extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), TransactionModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Date::make(__('nova.fields.transaction.date.name'), TransactionModel::ATTRIBUTE_DATE)
|
||||
->sortable()
|
||||
->rules('required')
|
||||
->help(__('nova.fields.transaction.date.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.transaction.service.name'), TransactionModel::ATTRIBUTE_SERVICE)
|
||||
->options(Service::asSelectArray())
|
||||
@@ -139,7 +139,8 @@ class Transaction extends BaseResource
|
||||
->rules(['required', new EnumValue(Service::class, false)])
|
||||
->help(__('nova.fields.transaction.service.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.transaction.description.name'), TransactionModel::ATTRIBUTE_DESCRIPTION)
|
||||
->sortable()
|
||||
@@ -147,14 +148,18 @@ class Transaction extends BaseResource
|
||||
->rules(['required', 'max:192'])
|
||||
->help(__('nova.fields.transaction.description.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Currency::make(__('nova.fields.transaction.amount.name'), TransactionModel::ATTRIBUTE_AMOUNT)
|
||||
->sortable()
|
||||
->rules('required')
|
||||
->help(__('nova.fields.transaction.amount.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.transaction.external_id.name'), TransactionModel::ATTRIBUTE_EXTERNAL_ID)
|
||||
->nullable()
|
||||
@@ -163,7 +168,10 @@ class Transaction extends BaseResource
|
||||
->rules(['nullable', 'max:192'])
|
||||
->help(__('nova.fields.transaction.external_id.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Panel::make(__('nova.fields.base.timestamps'), $this->timestamps())
|
||||
->collapsable(),
|
||||
@@ -186,11 +194,7 @@ class Transaction extends BaseResource
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create transaction');
|
||||
}),
|
||||
->canSeeWhen('create', TransactionModel::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,8 @@ class Page extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), PageModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.page.name.name'), PageModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
@@ -131,7 +132,10 @@ class Page extends BaseResource
|
||||
->rules(['required', 'max:192'])
|
||||
->help(__('nova.fields.page.name.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Slug::make(__('nova.fields.page.slug.name'), PageModel::ATTRIBUTE_SLUG)
|
||||
->from(PageModel::ATTRIBUTE_NAME)
|
||||
@@ -145,7 +149,8 @@ class Page extends BaseResource
|
||||
->__toString()
|
||||
)
|
||||
->help(__('nova.fields.page.slug.help'))
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Markdown::make(__('nova.fields.page.body.name'), PageModel::ATTRIBUTE_BODY)
|
||||
->rules(['required', 'max:16777215'])
|
||||
|
||||
@@ -5,8 +5,10 @@ declare(strict_types=1);
|
||||
namespace App\Nova\Resources\Wiki;
|
||||
|
||||
use App\Enums\Models\Wiki\AnimeSeason;
|
||||
use App\Models\Auth\User;
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\Anime as AnimeModel;
|
||||
use App\Models\Wiki\ExternalResource as ExternalResourceModel;
|
||||
use App\Nova\Actions\Models\Wiki\Anime\AttachAnimeResourceAction;
|
||||
use App\Nova\Actions\Models\Wiki\Anime\BackfillAnimeAction;
|
||||
use App\Nova\Lenses\Anime\Image\AnimeCoverLargeLens;
|
||||
use App\Nova\Lenses\Anime\Image\AnimeCoverSmallLens;
|
||||
@@ -27,7 +29,6 @@ use App\Pivots\BasePivot;
|
||||
use BenSampo\Enum\Enum;
|
||||
use BenSampo\Enum\Rules\EnumValue;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Nova\Card;
|
||||
use Laravel\Nova\Fields\BelongsToMany;
|
||||
@@ -149,7 +150,8 @@ class Anime extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), AnimeModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.anime.name.name'), AnimeModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
@@ -157,7 +159,10 @@ class Anime extends BaseResource
|
||||
->rules(['required', 'max:192'])
|
||||
->help(__('nova.fields.anime.name.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Slug::make(__('nova.fields.anime.slug.name'), AnimeModel::ATTRIBUTE_SLUG)
|
||||
->from(AnimeModel::ATTRIBUTE_NAME)
|
||||
@@ -170,7 +175,8 @@ class Anime extends BaseResource
|
||||
->__toString()
|
||||
)
|
||||
->help(__('nova.fields.anime.slug.help'))
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Number::make(__('nova.fields.anime.year.name'), AnimeModel::ATTRIBUTE_YEAR)
|
||||
->sortable()
|
||||
@@ -179,7 +185,8 @@ class Anime extends BaseResource
|
||||
->rules(['required', 'digits:4', 'integer'])
|
||||
->help(__('nova.fields.anime.year.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.anime.season.name'), AnimeModel::ATTRIBUTE_SEASON)
|
||||
->options(AnimeSeason::asSelectArray())
|
||||
@@ -188,13 +195,17 @@ class Anime extends BaseResource
|
||||
->rules(['required', new EnumValue(AnimeSeason::class, false)])
|
||||
->help(__('nova.fields.anime.season.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Textarea::make(__('nova.fields.anime.synopsis.name'), AnimeModel::ATTRIBUTE_SYNOPSIS)
|
||||
->rules('max:65535')
|
||||
->nullable()
|
||||
->help(__('nova.fields.anime.synopsis.help'))
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->maxlength(65535)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
HasMany::make(__('nova.resources.label.anime_synonyms'), AnimeModel::RELATION_SYNONYMS, Synonym::class),
|
||||
|
||||
@@ -282,11 +293,43 @@ class Anime extends BaseResource
|
||||
->showOnIndex()
|
||||
->showOnDetail()
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
->canSeeWhen('update', $this),
|
||||
|
||||
return $user instanceof User && $user->can('update anime');
|
||||
}),
|
||||
(new AttachAnimeResourceAction(ResourceSite::ANIDB()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachAnimeResourceAction(ResourceSite::ANILIST()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachAnimeResourceAction(ResourceSite::ANIME_PLANET()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachAnimeResourceAction(ResourceSite::ANN()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachAnimeResourceAction(ResourceSite::KITSU()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachAnimeResourceAction(ResourceSite::MAL()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,14 +133,18 @@ class Synonym extends BaseResource
|
||||
|
||||
ID::make(__('nova.fields.base.id'), AnimeSynonym::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.anime_synonym.text.name'), AnimeSynonym::ATTRIBUTE_TEXT)
|
||||
->sortable()
|
||||
->rules(['required', 'max:192'])
|
||||
->help(__('nova.fields.anime_synonym.text.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Panel::make(__('nova.fields.base.timestamps'), $this->timestamps())
|
||||
->collapsable(),
|
||||
|
||||
@@ -166,7 +166,8 @@ class Theme extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), AnimeTheme::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsTo::make(__('nova.resources.singularLabel.anime'), AnimeTheme::RELATION_ANIME, Anime::class)
|
||||
->sortable()
|
||||
@@ -185,7 +186,8 @@ class Theme extends BaseResource
|
||||
->rules(['required', new EnumValue(ThemeType::class, false)])
|
||||
->help(__('nova.fields.anime_theme.type.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Number::make(__('nova.fields.anime_theme.sequence.name'), AnimeTheme::ATTRIBUTE_SEQUENCE)
|
||||
->sortable()
|
||||
@@ -193,7 +195,8 @@ class Theme extends BaseResource
|
||||
->rules(['nullable', 'integer'])
|
||||
->help(__('nova.fields.anime_theme.sequence.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.anime_theme.group.name'), AnimeTheme::ATTRIBUTE_GROUP)
|
||||
->sortable()
|
||||
@@ -201,7 +204,10 @@ class Theme extends BaseResource
|
||||
->rules(['nullable', 'max:192'])
|
||||
->help(__('nova.fields.anime_theme.group.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.anime_theme.slug.name'), AnimeTheme::ATTRIBUTE_SLUG)
|
||||
->sortable()
|
||||
@@ -209,6 +215,9 @@ class Theme extends BaseResource
|
||||
->help(__('nova.fields.anime_theme.slug.help'))
|
||||
->showOnPreview()
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking()
|
||||
->dependsOn(
|
||||
[AnimeTheme::ATTRIBUTE_TYPE, AnimeTheme::ATTRIBUTE_SEQUENCE],
|
||||
function (Text $field, NovaRequest $novaRequest, FormData $formData) {
|
||||
|
||||
@@ -193,7 +193,8 @@ class Entry extends BaseResource
|
||||
|
||||
ID::make(__('nova.fields.base.id'), AnimeThemeEntry::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Number::make(__('nova.fields.anime_theme_entry.version.name'), AnimeThemeEntry::ATTRIBUTE_VERSION)
|
||||
->sortable()
|
||||
@@ -201,7 +202,8 @@ class Entry extends BaseResource
|
||||
->rules(['nullable', 'integer'])
|
||||
->help(__('nova.fields.anime_theme_entry.version.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.anime_theme_entry.episodes.name'), AnimeThemeEntry::ATTRIBUTE_EPISODES)
|
||||
->sortable()
|
||||
@@ -209,7 +211,10 @@ class Entry extends BaseResource
|
||||
->rules(['nullable', 'max:192'])
|
||||
->help(__('nova.fields.anime_theme_entry.episodes.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Boolean::make(__('nova.fields.anime_theme_entry.nsfw.name'), AnimeThemeEntry::ATTRIBUTE_NSFW)
|
||||
->sortable()
|
||||
@@ -217,7 +222,8 @@ class Entry extends BaseResource
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.anime_theme_entry.nsfw.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Boolean::make(__('nova.fields.anime_theme_entry.spoiler.name'), AnimeThemeEntry::ATTRIBUTE_SPOILER)
|
||||
->sortable()
|
||||
@@ -225,7 +231,8 @@ class Entry extends BaseResource
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.anime_theme_entry.spoiler.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.anime_theme_entry.notes.name'), AnimeThemeEntry::ATTRIBUTE_NOTES)
|
||||
->sortable()
|
||||
@@ -233,7 +240,10 @@ class Entry extends BaseResource
|
||||
->rules(['nullable', 'max:192'])
|
||||
->help(__('nova.fields.anime_theme_entry.notes.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.videos'), AnimeThemeEntry::RELATION_VIDEOS, Video::class)
|
||||
->searchable()
|
||||
|
||||
@@ -4,7 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Nova\Resources\Wiki;
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\Artist as ArtistModel;
|
||||
use App\Models\Wiki\ExternalResource as ExternalResourceModel;
|
||||
use App\Nova\Actions\Models\Wiki\Artist\AttachArtistResourceAction;
|
||||
use App\Nova\Lenses\Artist\Image\ArtistCoverLargeLens;
|
||||
use App\Nova\Lenses\Artist\Image\ArtistCoverSmallLens;
|
||||
use App\Nova\Lenses\Artist\Resource\ArtistAniDbResourceLens;
|
||||
@@ -126,7 +129,8 @@ class Artist extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), ArtistModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.artist.name.name'), ArtistModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
@@ -134,7 +138,10 @@ class Artist extends BaseResource
|
||||
->rules(['required', 'max:192'])
|
||||
->help(__('nova.fields.artist.name.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Slug::make(__('nova.fields.artist.slug.name'), ArtistModel::ATTRIBUTE_SLUG)
|
||||
->from(ArtistModel::ATTRIBUTE_NAME)
|
||||
@@ -147,7 +154,8 @@ class Artist extends BaseResource
|
||||
->__toString()
|
||||
)
|
||||
->help(__('nova.fields.artist.slug.help'))
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.songs'), ArtistModel::RELATION_SONGS, Song::class)
|
||||
->searchable()
|
||||
@@ -249,6 +257,44 @@ class Artist extends BaseResource
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actions available for the resource.
|
||||
*
|
||||
* @param NovaRequest $request
|
||||
* @return array
|
||||
*/
|
||||
public function actions(NovaRequest $request): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::actions($request),
|
||||
[
|
||||
(new AttachArtistResourceAction(ResourceSite::ANIDB()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachArtistResourceAction(ResourceSite::ANILIST()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachArtistResourceAction(ResourceSite::ANN()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachArtistResourceAction(ResourceSite::MAL()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cards available for the request.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Nova\Resources\Wiki;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Audio as AudioModel;
|
||||
use App\Nova\Actions\Repositories\Storage\Wiki\Audio\ReconcileAudioAction;
|
||||
use App\Nova\Actions\Storage\Wiki\Audio\DeleteAudioAction;
|
||||
@@ -13,7 +12,6 @@ use App\Nova\Actions\Storage\Wiki\Audio\UploadAudioAction;
|
||||
use App\Nova\Lenses\Audio\AudioVideoLens;
|
||||
use App\Nova\Resources\BaseResource;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\HasMany;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
@@ -104,7 +102,8 @@ class Audio extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), AudioModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
HasMany::make(__('nova.resources.label.videos'), AudioModel::RELATION_VIDEOS, Video::class),
|
||||
|
||||
@@ -132,7 +131,8 @@ class Audio extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.audio.filename.name'), AudioModel::ATTRIBUTE_FILENAME)
|
||||
->sortable()
|
||||
@@ -140,7 +140,8 @@ class Audio extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.audio.path.name'), AudioModel::ATTRIBUTE_PATH)
|
||||
->copyable()
|
||||
@@ -148,14 +149,16 @@ class Audio extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Number::make(__('nova.fields.audio.size.name'), AudioModel::ATTRIBUTE_SIZE)
|
||||
->hideFromIndex()
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.audio.mimetype.name'), AudioModel::ATTRIBUTE_MIMETYPE)
|
||||
->copyable()
|
||||
@@ -163,7 +166,8 @@ class Audio extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -183,43 +187,27 @@ class Audio extends BaseResource
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create audio');
|
||||
}),
|
||||
->canSeeWhen('create', AudioModel::class),
|
||||
|
||||
(new MoveAudioAction())
|
||||
->confirmButtonText(__('nova.actions.storage.move.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create audio');
|
||||
}),
|
||||
->canSeeWhen('create', AudioModel::class),
|
||||
|
||||
(new DeleteAudioAction())
|
||||
->confirmText(__('nova.actions.audio.delete.confirmText'))
|
||||
->confirmButtonText(__('nova.actions.storage.delete.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('delete audio');
|
||||
}),
|
||||
->canSeeWhen('delete', $this),
|
||||
|
||||
(new ReconcileAudioAction())
|
||||
->confirmButtonText(__('nova.actions.repositories.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create audio');
|
||||
}),
|
||||
->canSeeWhen('create', AudioModel::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,8 @@ class ExternalResource extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), ExternalResourceModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.external_resource.site.name'), ExternalResourceModel::ATTRIBUTE_SITE)
|
||||
->options(ResourceSite::asSelectArray())
|
||||
@@ -143,6 +144,7 @@ class ExternalResource extends BaseResource
|
||||
->help(__('nova.fields.external_resource.site.help'))
|
||||
->showOnPreview()
|
||||
->filterable()
|
||||
->showWhenPeeking()
|
||||
->dependsOn(
|
||||
[ExternalResourceModel::ATTRIBUTE_LINK],
|
||||
function (Select $field, NovaRequest $novaRequest, FormData $formData) {
|
||||
@@ -166,6 +168,7 @@ class ExternalResource extends BaseResource
|
||||
->displayUsing(fn (mixed $value, mixed $resource, string $attribute) => $value)
|
||||
->help(__('nova.fields.external_resource.link.help'))
|
||||
->showOnPreview()
|
||||
->showWhenPeeking()
|
||||
->filterable(),
|
||||
|
||||
Number::make(__('nova.fields.external_resource.external_id.name'), ExternalResourceModel::ATTRIBUTE_EXTERNAL_ID)
|
||||
@@ -175,6 +178,7 @@ class ExternalResource extends BaseResource
|
||||
->help(__('nova.fields.external_resource.external_id.help'))
|
||||
->showOnPreview()
|
||||
->filterable()
|
||||
->showWhenPeeking()
|
||||
->dependsOn(
|
||||
[ExternalResourceModel::ATTRIBUTE_LINK],
|
||||
function (Text $field, NovaRequest $novaRequest, FormData $formData) {
|
||||
|
||||
@@ -130,7 +130,8 @@ class Image extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), ImageModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.image.facet.name'), ImageModel::ATTRIBUTE_FACET)
|
||||
->options(ImageFacet::asSelectArray())
|
||||
@@ -139,11 +140,13 @@ class Image extends BaseResource
|
||||
->rules(['required', new EnumValue(ImageFacet::class, false)])
|
||||
->help(__('nova.fields.image.facet.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
NovaImage::make(__('nova.fields.image.image.name'), ImageModel::ATTRIBUTE_PATH, Config::get('image.disk'))
|
||||
->creationRules('required')
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.anime'), ImageModel::RELATION_ANIME, Anime::class)
|
||||
->searchable()
|
||||
@@ -206,7 +209,8 @@ class Image extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,8 @@ class Series extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), SeriesModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.series.name.name'), SeriesModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
@@ -124,7 +125,10 @@ class Series extends BaseResource
|
||||
->rules(['required', 'max:192'])
|
||||
->help(__('nova.fields.series.name.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Slug::make(__('nova.fields.series.slug.name'), SeriesModel::ATTRIBUTE_SLUG)
|
||||
->from(SeriesModel::ATTRIBUTE_NAME)
|
||||
@@ -137,7 +141,8 @@ class Series extends BaseResource
|
||||
->__toString()
|
||||
)
|
||||
->help(__('nova.fields.series.slug.help'))
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.anime'), SeriesModel::RELATION_ANIME, Anime::class)
|
||||
->searchable()
|
||||
|
||||
@@ -154,7 +154,8 @@ class Song extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), SongModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.song.title.name'), SongModel::ATTRIBUTE_TITLE)
|
||||
->sortable()
|
||||
@@ -163,7 +164,10 @@ class Song extends BaseResource
|
||||
->rules(['nullable', 'max:192'])
|
||||
->help(__('nova.fields.song.title.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.artists'), SongModel::RELATION_ARTISTS, Artist::class)
|
||||
->searchable()
|
||||
|
||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Nova\Resources\Wiki;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\ExternalResource as ExternalResourceModel;
|
||||
use App\Models\Wiki\Studio as StudioModel;
|
||||
use App\Nova\Actions\Models\Wiki\Studio\AttachStudioResourceAction;
|
||||
use App\Nova\Actions\Models\Wiki\Studio\BackfillStudioAction;
|
||||
use App\Nova\Lenses\Studio\Image\StudioCoverLargeLens;
|
||||
use App\Nova\Lenses\Studio\Resource\StudioAniDbResourceLens;
|
||||
@@ -18,7 +20,6 @@ use App\Nova\Resources\BaseResource;
|
||||
use App\Pivots\BasePivot;
|
||||
use App\Pivots\StudioResource;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Nova\Fields\BelongsToMany;
|
||||
use Laravel\Nova\Fields\DateTime;
|
||||
@@ -124,7 +125,8 @@ class Studio extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), StudioModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.studio.name.name'), StudioModel::ATTRIBUTE_NAME)
|
||||
->sortable()
|
||||
@@ -132,7 +134,10 @@ class Studio extends BaseResource
|
||||
->rules(['required', 'max:192'])
|
||||
->help(__('nova.fields.studio.name.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Slug::make(__('nova.fields.studio.slug.name'), StudioModel::ATTRIBUTE_SLUG)
|
||||
->from(StudioModel::ATTRIBUTE_NAME)
|
||||
@@ -145,7 +150,8 @@ class Studio extends BaseResource
|
||||
->__toString()
|
||||
)
|
||||
->help(__('nova.fields.studio.slug.help'))
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsToMany::make(__('nova.resources.label.anime'), StudioModel::RELATION_ANIME, Anime::class)
|
||||
->searchable()
|
||||
@@ -215,11 +221,37 @@ class Studio extends BaseResource
|
||||
->showOnIndex()
|
||||
->showOnDetail()
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
->canSeeWhen('update', $this),
|
||||
|
||||
return $user instanceof User && $user->can('update studio');
|
||||
}),
|
||||
(new AttachStudioResourceAction(ResourceSite::ANIDB()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachStudioResourceAction(ResourceSite::ANILIST()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachStudioResourceAction(ResourceSite::ANIME_PLANET()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachStudioResourceAction(ResourceSite::ANN()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
|
||||
(new AttachStudioResourceAction(ResourceSite::MAL()))
|
||||
->confirmButtonText(__('nova.actions.models.wiki.attach_resource.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSeeWhen('create', ExternalResourceModel::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace App\Nova\Resources\Wiki;
|
||||
|
||||
use App\Enums\Models\Wiki\VideoOverlap;
|
||||
use App\Enums\Models\Wiki\VideoSource;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Video as VideoModel;
|
||||
use App\Models\Wiki\Video\VideoScript;
|
||||
use App\Nova\Actions\Models\Wiki\Video\BackfillAudioAction;
|
||||
use App\Nova\Actions\Repositories\Storage\Wiki\Video\ReconcileVideoAction;
|
||||
use App\Nova\Actions\Storage\Wiki\Video\DeleteVideoAction;
|
||||
@@ -28,7 +28,6 @@ use App\Pivots\BasePivot;
|
||||
use BenSampo\Enum\Enum;
|
||||
use BenSampo\Enum\Rules\EnumValue;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Card;
|
||||
use Laravel\Nova\Fields\BelongsTo;
|
||||
use Laravel\Nova\Fields\BelongsToMany;
|
||||
@@ -125,7 +124,8 @@ class Video extends BaseResource
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), VideoModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Number::make(__('nova.fields.video.resolution.name'), VideoModel::ATTRIBUTE_RESOLUTION)
|
||||
->sortable()
|
||||
@@ -135,7 +135,8 @@ class Video extends BaseResource
|
||||
->rules(['nullable', 'integer'])
|
||||
->help(__('nova.fields.video.resolution.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Boolean::make(__('nova.fields.video.nc.name'), VideoModel::ATTRIBUTE_NC)
|
||||
->sortable()
|
||||
@@ -143,7 +144,8 @@ class Video extends BaseResource
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.nc.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Boolean::make(__('nova.fields.video.subbed.name'), VideoModel::ATTRIBUTE_SUBBED)
|
||||
->sortable()
|
||||
@@ -151,7 +153,8 @@ class Video extends BaseResource
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.subbed.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Boolean::make(__('nova.fields.video.lyrics.name'), VideoModel::ATTRIBUTE_LYRICS)
|
||||
->sortable()
|
||||
@@ -159,7 +162,8 @@ class Video extends BaseResource
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.lyrics.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Boolean::make(__('nova.fields.video.uncen.name'), VideoModel::ATTRIBUTE_UNCEN)
|
||||
->sortable()
|
||||
@@ -167,7 +171,8 @@ class Video extends BaseResource
|
||||
->rules(['nullable', 'boolean'])
|
||||
->help(__('nova.fields.video.uncen.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.video.overlap.name'), VideoModel::ATTRIBUTE_OVERLAP)
|
||||
->options(VideoOverlap::asSelectArray())
|
||||
@@ -177,7 +182,8 @@ class Video extends BaseResource
|
||||
->rules(['nullable', new EnumValue(VideoOverlap::class, false)])
|
||||
->help(__('nova.fields.video.overlap.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Select::make(__('nova.fields.video.source.name'), VideoModel::ATTRIBUTE_SOURCE)
|
||||
->options(VideoSource::asSelectArray())
|
||||
@@ -187,7 +193,8 @@ class Video extends BaseResource
|
||||
->rules(['nullable', new EnumValue(VideoSource::class, false)])
|
||||
->help(__('nova.fields.video.source.help'))
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsTo::make(__('nova.resources.singularLabel.audio'), VideoModel::RELATION_AUDIO, Audio::class)
|
||||
->hideFromIndex()
|
||||
@@ -237,7 +244,10 @@ class Video extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.video.filename.name'), VideoModel::ATTRIBUTE_FILENAME)
|
||||
->sortable()
|
||||
@@ -245,7 +255,10 @@ class Video extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.video.path.name'), VideoModel::ATTRIBUTE_PATH)
|
||||
->copyable()
|
||||
@@ -253,14 +266,18 @@ class Video extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Number::make(__('nova.fields.video.size.name'), VideoModel::ATTRIBUTE_SIZE)
|
||||
->hideFromIndex()
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.video.mimetype.name'), VideoModel::ATTRIBUTE_MIMETYPE)
|
||||
->copyable()
|
||||
@@ -268,7 +285,10 @@ class Video extends BaseResource
|
||||
->hideWhenCreating()
|
||||
->hideWhenUpdating()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -289,64 +309,40 @@ class Video extends BaseResource
|
||||
->showOnIndex()
|
||||
->showOnDetail()
|
||||
->showInline()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('update video');
|
||||
}),
|
||||
->canSeeWhen('update', $this),
|
||||
|
||||
(new UploadVideoAction())
|
||||
->confirmButtonText(__('nova.actions.storage.upload.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create video');
|
||||
}),
|
||||
->canSeeWhen('create', VideoModel::class),
|
||||
|
||||
(new MoveVideoAction())
|
||||
->confirmButtonText(__('nova.actions.storage.move.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create audio');
|
||||
}),
|
||||
->canSeeWhen('create', VideoModel::class),
|
||||
|
||||
(new DeleteVideoAction())
|
||||
->confirmText(__('nova.actions.video.delete.confirmText'))
|
||||
->confirmButtonText(__('nova.actions.storage.delete.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('delete video');
|
||||
}),
|
||||
->canSeeWhen('delete', $this),
|
||||
|
||||
(new ReconcileVideoAction())
|
||||
->confirmButtonText(__('nova.actions.repositories.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create video');
|
||||
}),
|
||||
->canSeeWhen('create', VideoModel::class),
|
||||
|
||||
(new UploadScriptAction())
|
||||
->confirmButtonText(__('nova.actions.storage.upload.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnDetail()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create video script');
|
||||
}),
|
||||
->canSeeWhen('create', VideoScript::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Nova\Resources\Wiki\Video;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Video\VideoScript;
|
||||
use App\Nova\Actions\Repositories\Storage\Wiki\Video\Script\ReconcileScriptAction;
|
||||
use App\Nova\Actions\Storage\Wiki\Video\Script\DeleteScriptAction;
|
||||
@@ -14,7 +13,6 @@ use App\Nova\Resources\BaseResource;
|
||||
use App\Nova\Resources\Wiki\Video;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\BelongsTo;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
@@ -139,12 +137,16 @@ class Script extends BaseResource
|
||||
|
||||
ID::make(__('nova.fields.base.id'), VideoScript::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview(),
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Text::make(__('nova.fields.video_script.path'), VideoScript::ATTRIBUTE_PATH)
|
||||
->copyable()
|
||||
->showOnPreview()
|
||||
->filterable(),
|
||||
->filterable()
|
||||
->maxlength(192)
|
||||
->enforceMaxlength()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Panel::make(__('nova.fields.base.timestamps'), $this->timestamps()),
|
||||
];
|
||||
@@ -166,43 +168,27 @@ class Script extends BaseResource
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create video script');
|
||||
}),
|
||||
->canSeeWhen('create', VideoScript::class),
|
||||
|
||||
(new MoveScriptAction())
|
||||
->confirmButtonText(__('nova.actions.storage.move.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create video script');
|
||||
}),
|
||||
->canSeeWhen('create', VideoScript::class),
|
||||
|
||||
(new DeleteScriptAction())
|
||||
->confirmText(__('nova.actions.video_script.delete.confirmText'))
|
||||
->confirmButtonText(__('nova.actions.storage.delete.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->exceptOnIndex()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('delete video script');
|
||||
}),
|
||||
->canSeeWhen('delete', $this),
|
||||
|
||||
(new ReconcileScriptAction())
|
||||
->confirmButtonText(__('nova.actions.repositories.confirmButtonText'))
|
||||
->cancelButtonText(__('nova.actions.base.cancelButtonText'))
|
||||
->onlyOnIndex()
|
||||
->standalone()
|
||||
->canSee(function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
return $user instanceof User && $user->can('create video script');
|
||||
}),
|
||||
->canSeeWhen('create', VideoScript::class),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,15 @@ class AppServiceProvider extends ServiceProvider
|
||||
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
|
||||
$class = get_class($model);
|
||||
|
||||
Log::info("Attempted to lazy load [$relation] on model [$class]");
|
||||
Log::error("Attempted to lazy load [$relation] on model [$class]");
|
||||
});
|
||||
|
||||
Model::preventsAccessingMissingAttributes();
|
||||
|
||||
Model::handleMissingAttributeViolationUsing(function (Model $model, string $key) {
|
||||
$class = get_class($model);
|
||||
|
||||
Log::error("Attribute $key does not exist or was not retrieved for model [$class]");
|
||||
});
|
||||
|
||||
AboutCommand::add('Audios', [
|
||||
|
||||
@@ -67,6 +67,9 @@ class NovaServiceProvider extends NovaApplicationServiceProvider
|
||||
return $menu;
|
||||
});
|
||||
|
||||
// Enable breadcrumbs
|
||||
Nova::withBreadcrumbs();
|
||||
|
||||
// Disable the footer
|
||||
Nova::footer(fn () => '');
|
||||
}
|
||||
|
||||
@@ -7,16 +7,19 @@ namespace App\Providers;
|
||||
use App\Constants\Config\AudioConstants;
|
||||
use App\Constants\Config\DumpConstants;
|
||||
use App\Constants\Config\VideoConstants;
|
||||
use App\Events\Wiki\Video\VideoThrottled;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime\AnimeSynonym;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Models\Wiki\Video\VideoScript;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@@ -105,5 +108,20 @@ class RouteServiceProvider extends ServiceProvider
|
||||
|
||||
return Limit::perMinute(90)->by(Auth::check() ? Auth::id() : $request->ip());
|
||||
});
|
||||
|
||||
RateLimiter::for('video', function (Request $request) {
|
||||
$limit = Config::get(VideoConstants::RATE_LIMITER_QUALIFIED);
|
||||
|
||||
if ($limit <= 0) {
|
||||
return Limit::none();
|
||||
}
|
||||
|
||||
return Limit::perMinute($limit)->by($request->ip())->response(function (Request $request) {
|
||||
/** @var Video $video */
|
||||
$video = $request->route('video');
|
||||
|
||||
VideoThrottled::dispatch($video, Crypt::encryptString($request->ip()));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Http\Api\Include\AllowedInclude;
|
||||
use App\Http\Api\Schema\Wiki\AudioSchema;
|
||||
use App\Http\Api\Schema\Wiki\ExternalResourceSchema;
|
||||
use App\Http\Api\Schema\Wiki\ImageSchema;
|
||||
use App\Http\Api\Schema\Wiki\Video\ScriptSchema;
|
||||
use App\Http\Resources\Wiki\Resource\AnimeResource;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Scout\Elasticsearch\Api\Field\Base\IdField;
|
||||
@@ -60,6 +61,7 @@ class AnimeSchema extends Schema
|
||||
new AllowedInclude(new EntrySchema(), Anime::RELATION_ENTRIES),
|
||||
new AllowedInclude(new ExternalResourceSchema(), Anime::RELATION_RESOURCES),
|
||||
new AllowedInclude(new ImageSchema(), Anime::RELATION_IMAGES),
|
||||
new AllowedInclude(new ScriptSchema(), Anime::RELATION_SCRIPTS),
|
||||
new AllowedInclude(new SeriesSchema(), Anime::RELATION_SERIES),
|
||||
new AllowedInclude(new SongSchema(), Anime::RELATION_SONG),
|
||||
new AllowedInclude(new StudioSchema(), Anime::RELATION_STUDIOS),
|
||||
|
||||
+10
-9
@@ -23,7 +23,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.2",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-intl": "*",
|
||||
@@ -34,13 +34,14 @@
|
||||
"bensampo/laravel-enum": "^6.0",
|
||||
"bepsvpt/secure-headers": "^7.2",
|
||||
"cyrildewit/eloquent-viewable": "^6.1",
|
||||
"fakerphp/faker": "^1.17",
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"fakerphp/faker": "^1.21",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"laravel-notification-channels/discord": "^1.3",
|
||||
"laravel/framework": "^9.24",
|
||||
"laravel/fortify": "^1.13",
|
||||
"laravel/framework": "^9.45",
|
||||
"laravel/horizon": "^5.8",
|
||||
"laravel/jetstream": "^2.8",
|
||||
"laravel/nova": "^4.7",
|
||||
"laravel/jetstream": "^2.13",
|
||||
"laravel/nova": "^4.19",
|
||||
"laravel/sanctum": "^3.0",
|
||||
"laravel/scout": "^9.4",
|
||||
"laravel/telescope": "^4.7",
|
||||
@@ -48,7 +49,7 @@
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"livewire/livewire": "^2.10",
|
||||
"olssonm/l5-zxcvbn": "^5.0",
|
||||
"pbmedia/laravel-ffmpeg": "^8.1",
|
||||
"pbmedia/laravel-ffmpeg": "^8.2",
|
||||
"spatie/db-dumper": "^3.1.1",
|
||||
"spatie/laravel-permission": "^5.5",
|
||||
"spatie/laravel-route-discovery": "^1.0",
|
||||
@@ -57,9 +58,9 @@
|
||||
"symfony/mailgun-mailer": "^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^6.4",
|
||||
"brianium/paratest": "^6.7",
|
||||
"laravel/pint": "^1.0",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"mockery/mockery": "^1.5.1",
|
||||
"nunomaduro/collision": "^6.1",
|
||||
"nunomaduro/larastan": "^2.0",
|
||||
"phpunit/phpunit": "^9.5.10",
|
||||
|
||||
Generated
+921
-714
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -2,6 +2,8 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Http\StreamingMethod;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@@ -48,7 +50,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'streaming_method' => env('AUDIO_STREAMING_METHOD', 'response'),
|
||||
'streaming_method' => env('AUDIO_STREAMING_METHOD', StreamingMethod::RESPONSE),
|
||||
|
||||
'nginx_redirect' => env('AUDIO_NGINX_REDIRECT', '/audio_redirect/'),
|
||||
];
|
||||
|
||||
@@ -38,7 +38,7 @@ return [
|
||||
'secret' => env('PUSHER_APP_SECRET'),
|
||||
'app_id' => env('PUSHER_APP_ID'),
|
||||
'options' => [
|
||||
'host' => env('PUSHER_HOST', 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||
'port' => env('PUSHER_PORT', 443),
|
||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||
'encrypted' => true,
|
||||
|
||||
+17
-2
@@ -2,6 +2,8 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Http\StreamingMethod;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@@ -48,7 +50,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'streaming_method' => env('VIDEO_STREAMING_METHOD', 'response'),
|
||||
'streaming_method' => env('VIDEO_STREAMING_METHOD', StreamingMethod::RESPONSE),
|
||||
|
||||
'nginx_redirect' => env('VIDEO_NGINX_REDIRECT', '/video_redirect/'),
|
||||
|
||||
@@ -65,6 +67,19 @@ return [
|
||||
|
||||
'encoder_version' => env('VIDEO_ENCODER_VERSION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Video Rate Limiter
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value represents the number of requests permitted to stream video per minute.
|
||||
| If set to a value less than or equal to zero, the limiter shall be unlimited.
|
||||
| If set to a value greater than 0, the limiter shall restrict by that value.
|
||||
|
|
||||
*/
|
||||
|
||||
'rate_limiter' => (int) env('VIDEO_RATE_LIMITER', -1),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Video Scripts
|
||||
@@ -100,7 +115,7 @@ return [
|
||||
| If scripts are downloaded from a subdomain, set SCRIPT_URL and leave SCRIPT_PATH null.
|
||||
| Ex: script.animethemes.test
|
||||
| If scripts are NOT downloaded from a subdomain, set SCRIPT_PATH and leave SCRIPT_URL null.
|
||||
| Ex: animethemes.test/script
|
||||
| Ex: animethemes.test/videoscript
|
||||
|
|
||||
*/
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ class BalanceFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
Balance::ATTRIBUTE_BALANCE => fake()->randomFloat(2),
|
||||
Balance::ATTRIBUTE_BALANCE => fake()->randomFloat(nbMaxDecimals: 2, max: 999999.99),
|
||||
Balance::ATTRIBUTE_FREQUENCY => BalanceFrequency::getRandomValue(),
|
||||
Balance::ATTRIBUTE_DATE => fake()->date(),
|
||||
Balance::ATTRIBUTE_SERVICE => Service::getRandomValue(),
|
||||
Balance::ATTRIBUTE_USAGE => fake()->randomFloat(2),
|
||||
Balance::ATTRIBUTE_USAGE => fake()->randomFloat(nbMaxDecimals: 2, max: 999999.99),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class TransactionFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
Transaction::ATTRIBUTE_AMOUNT => fake()->randomFloat(2),
|
||||
Transaction::ATTRIBUTE_AMOUNT => fake()->randomFloat(nbMaxDecimals: 2, max: 999999.99),
|
||||
Transaction::ATTRIBUTE_DATE => fake()->date(),
|
||||
Transaction::ATTRIBUTE_DESCRIPTION => fake()->sentence(),
|
||||
Transaction::ATTRIBUTE_EXTERNAL_ID => fake()->uuid(),
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Models\Wiki\Image;
|
||||
use App\Models\Wiki\Series;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Models\Wiki\Video\VideoScript;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -70,7 +71,9 @@ class AnimeFactory extends Factory
|
||||
->has(
|
||||
AnimeThemeEntry::factory()
|
||||
->count(fake()->numberBetween(1, 3))
|
||||
->has(Video::factory()->count(fake()->numberBetween(1, 3)))
|
||||
->has(Video::factory()->count(fake()->numberBetween(1, 3))
|
||||
->has(VideoScript::factory(), Video::RELATION_SCRIPT)
|
||||
)
|
||||
)
|
||||
->count(fake()->numberBetween(1, 3))
|
||||
->create();
|
||||
|
||||
Generated
+485
-1400
File diff suppressed because it is too large
Load Diff
+9
-8
@@ -6,14 +6,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"alpinejs": "^3.10.3",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"axios": "^0.27.2",
|
||||
"laravel-vite-plugin": "^0.6.1",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"alpinejs": "^3.10.5",
|
||||
"@alpinejs/focus": "^3.10.5",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"axios": "^1.2.1",
|
||||
"laravel-vite-plugin": "^0.7.3",
|
||||
"lodash": "^4.17.21",
|
||||
"postcss": "8.4.16",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"vite": "^3.1.3"
|
||||
"postcss": "8.4.20",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"vite": "^4.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ parameters:
|
||||
- '#Strict comparison using === between class-string<Laravel\\Nova\\Resource> and null will always evaluate to false.#'
|
||||
- '#Call to an undefined method ProtoneMedia\\LaravelFFMpeg\\Drivers\\PHPFFMpeg::export\(\).#'
|
||||
- '#Call to an undefined method ProtoneMedia\\LaravelFFMpeg\\Drivers\\PHPFFMpeg::getProcessOutput\(\).#'
|
||||
- '#Call to an undefined static method Illuminate\\Support\\Facades\\Config::bool\(\).#'
|
||||
-
|
||||
message: '#Unreachable statement - code above always terminates.#'
|
||||
path: app/Http/Middleware/ThrottleRequestsWithService.php
|
||||
|
||||
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+6
-4
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"/app.js": "/app.js?id=6caa5316e06082f6a22b0687b88b3ab0",
|
||||
"/app-dark.css": "/app-dark.css?id=ff172044c4efc9f08f12c0eb824b0226",
|
||||
"/app.css": "/app.css?id=a38514598173eedd6b8575a77bc1ead4",
|
||||
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f"
|
||||
"/app.js": "/app.js?id=0f8def17167a0224d5704239fefc69ea",
|
||||
"/app-dark.css": "/app-dark.css?id=23ca8adc130382f74688c6e36ce89407",
|
||||
"/app.css": "/app.css?id=7357f6239c73ee903eba42be0458d3ab",
|
||||
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f",
|
||||
"/img/horizon.svg": "/img/horizon.svg?id=904d5b5185fefb09035384e15bfca765",
|
||||
"/img/sprite.svg": "/img/sprite.svg?id=afc4952b74895bdef3ab4ebe9adb746f"
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user