14 Commits

Author SHA1 Message Date
paranarimasu c0464406d3 feat: add configurable throttling to video (#536) 2023-03-01 08:29:01 -06:00
paranarimasu 134a82ecc9 test: fix value boundary in billing factories & backfill anime studios needs unique value from external api (#514) 2023-02-07 23:58:45 -06:00
paranarimasu 3e71ff8ace refactor(admin): reduce number of queries for upload actions (#504)
* refactor(admin): reduce number of queries for upload actions

* style: fix phpdoc description for upload audio action

* style: fix audio attribute constants
2023-01-15 23:13:48 -06:00
paranarimasu 39250632b2 fix(api): fix anime video script allowed include path (#501) 2022-12-30 23:52:30 -06:00
Mani 3cdf824e47 feat(api): Added videoscript include path to anime (#499) 2022-12-30 21:26:23 -06:00
paranarimasu 681f07ecc9 chore: applying framework updates (#498) 2022-12-30 16:41:43 -06:00
paranarimasu 9028557f4c chore: bump dependencies for php8.2 (#500) 2022-12-30 16:40:26 -06:00
paranarimasu cea28e61f1 feat(admin): attach resource from detail/inline (#496) 2022-12-23 16:05:57 -06:00
paranarimasu fed083b5d3 feat(admin): allow video attributes to be set on upload (#495) 2022-12-23 11:53:17 -06:00
paranarimasu 2e97bb78ea chore: bump dependencies (#486) 2022-12-20 23:20:48 -06:00
paranarimasu b320882d6d feat: add static link for latest db dumps (#485) 2022-12-20 23:05:11 -06:00
paranarimasu a8801edec9 Chore 2022 11 07 (#479)
* chore: bump dependencies

* style: fix StyleCI finding
2022-12-20 22:22:26 -06:00
paranarimasu ed33d4154a chore: updating dependencies (#475) 2022-12-19 22:57:54 -06:00
paranarimasu d3a8d58cd2 #490 fix(admin): update pattern for mal studio cover image (#492) 2022-12-12 00:58:11 -06:00
128 changed files with 3367 additions and 3044 deletions
+2 -2
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+7
View File
@@ -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);
}
}
+48
View File
@@ -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;
}
+54
View File
@@ -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;
}
+43
View File
@@ -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;
}
}
+31
View File
@@ -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();
-34
View File
@@ -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();
+52
View File
@@ -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.
+2
View File
@@ -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';
+16
View File
@@ -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';
}
-36
View File
@@ -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;
}
}
+56
View File
@@ -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',
];
}
}
+2
View File
@@ -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());
}
}
}
+1
View File
@@ -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';
-2
View File
@@ -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,
+1
View File
@@ -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);
}
/**
+1 -7
View File
@@ -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),
];
}
}
+1 -7
View File
@@ -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),
];
}
}
+1 -7
View File
@@ -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),
];
}
}
+1 -7
View File
@@ -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),
];
}
+2 -1
View File
@@ -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'])
+10 -24
View File
@@ -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),
]
);
}
+10 -3
View File
@@ -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(),
];
}
}
+8 -6
View File
@@ -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(),
]
);
}
+6 -2
View File
@@ -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(),
+10 -4
View File
@@ -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(),
+6 -3
View File
@@ -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(),
];
}
}
+13 -13
View File
@@ -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),
]
);
}
+17 -13
View File
@@ -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),
]
);
}
+8 -3
View File
@@ -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'])
+55 -12
View File
@@ -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),
]
);
}
+6 -2
View File
@@ -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(),
+13 -4
View File
@@ -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) {
+16 -6
View File
@@ -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()
+49 -3
View File
@@ -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.
*
+16 -28
View File
@@ -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),
]
);
}
+5 -1
View File
@@ -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) {
+8 -4
View File
@@ -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(),
];
}
+8 -3
View File
@@ -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()
+6 -2
View File
@@ -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()
+41 -9
View File
@@ -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),
]
);
}
+41 -45
View File
@@ -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),
]
);
}
+10 -24
View File
@@ -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),
],
);
}
+9 -1
View File
@@ -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', [
+3
View File
@@ -67,6 +67,9 @@ class NovaServiceProvider extends NovaApplicationServiceProvider
return $menu;
});
// Enable breadcrumbs
Nova::withBreadcrumbs();
// Disable the footer
Nova::footer(fn () => '');
}
+18
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -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/'),
];
+1 -1
View File
@@ -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
View File
@@ -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(),
+4 -1
View File
@@ -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();
+485 -1400
View File
File diff suppressed because it is too large Load Diff
+9 -8
View File
@@ -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"
}
}
-1
View File
@@ -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
+4 -4
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+6 -4
View File
@@ -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"
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
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