From 8c7363e95db52443a894089cf049ac37b308e347 Mon Sep 17 00:00:00 2001 From: paranarimasu <33796518+paranarimasu@users.noreply.github.com> Date: Mon, 30 May 2022 17:25:06 -0500 Subject: [PATCH] merging wiki into main (#388) * #49, #51 feat(admin): persistent app configuration, nova settings tool, video streaming killswitch * style: Fix StyleCI issues * #41 feat(admin): add sluggable fields * #40 refactor: alias fields should be named slug * #62 feat(api): implementing announcement endpoints * style: fix StyleCI finding * #43 refactor: enum name should prefix model * test: year endpoints & StyleCI findings * fix(seed): resolve bucketed anime attributes, reduce collisions * #67, #68 fix(seed): reduce theme collisions & order seasons chronologically, fix(admin): invitation & lens pruning, fix(api): year endpoint key sorting, style fixes * #45 feat(seed): create anilist resources (#73) * Update tests.yml * #45 feat(seed): create anilist resources * docs: fix shields & link to installation guide, chore: update fortify stubs (#75) * Create CONTRIBUTING.md * #46 Image Resource, seeding anime images and synopsis, fix lens queries, php8 readiness, onboarding compatibility (#76) * #46 feat(seed): image resource, lens query fix, php8 readiness & onboarding * style: StyleCI finding * fix(api): allow themes to eager load images (#78) * #49 fix(videos): use subdomain routing to stream video (#82) * #74 feat(seed): implementing database dump artisan console command (#84) * #74 feat(seed): implementing database dump artisan console command * #74 style: fix StyleCI Finding * fix(video): use int return type via spaceships for set difference callback function (#85) * fix(admin): nova slug field now allows customization on update (#86) * refactor(video): Allow video stream middleware (#87) * refactor(video): is video streaming allowed as middleware * style: StyleCI findings * #88 feat(video): add view count for videos (#90) * #88 feat(video): add view count for videos * style: fix StyleCI findings * Initial unit tests (#93) * test: initial unit tests, community docs, test runner fixes * test: fixing github actions yml * test: fixing github actions yml * test: incremental push for backfilling tests, some cleanup * test: query parser & update jetstream stubs * style: fix StyleCI findings * test: don't expose image links * test: github actions doesn't like composer 2 * test: setup explicit php-version * test: make grill directory for test runner * test: use artisan test runner * test: handle no grills * test: sitemap should test for expected routes * test: install and publish assets before testing * test: add php extensions * test: rename sitemap route * test: provide video subdomain * #81 fix(api): use scout elastic driver package & refactor queries (#94) * #81 fix(api): search resource should implement limiting * fix(api): use new elastic scout driver package for search & refactor queries * fix(api): forgot updates to dependencies * style: fix StyleCI findings * style: fix StyleCI findings * fix(seed): add 2021 pages (#95) * #77 Initial implementation of deep filtering through constrained eager loads (#96) * feat(api): initial deep filtering implementation * fix(test): valid query parser limit must be greater than 0 * fix(api): jsonapi error handler only handles exceptions * style: fix StyleCI findings * fix(api): fix relation reflection * Fixing artist relation resolution (#97) * feat(api): initial deep filtering implementation * fix(test): valid query parser limit must be greater than 0 * fix(api): jsonapi error handler only handles exceptions * style: fix StyleCI findings * fix(api): fix relation reflection * fix(api): artist endpoint relation resolution * Adding Filter Unit Tests, bumping dependencies, updating stubs, actions secret test (#99) * test: filters, chore: update fortify stubs & bump dependencies * test: fix action * test: try another format * test: try another format * test: try another format * #45 feat(seed): create kitsu anime resources (#100) * #45 feat(seed): seed kitsu anime resource * style: fix StyleCI findings * #45 feat(seed): create anidb anime resources (#101) * [WIP] Discord Notifications for database updates, migrating to event dispatcher (#102) * feat: initial draft for discord notifications on database updates, refactor: use model event dispatcher instead of observers * style: fix StyleCI findings * refactor: use interface for discord event, don't serialize models on update & delete, first attempt to clean up embedded fields * style: fix StyleCI findings * refactor: discord embed field traits * fix: circument exception for empty values in embed fields by using a default value * refactor: update related scout indices and cascade deletes * style: fix StyleCI findings * feat: add artist events * feat: incremental commit for entry events * properly cast and format attribute values for update embed fields * feat: refresh video documents when deleting entries * feat: adding external resource events * feat: add events for images * feat: adding invitation events * feat: adding series events * feat: adding series events * feat: add song events * feat: adding synonym events * style: fix StyleCI findings * feat: adding theme events * feat: adding video events * feat: add LaserDisc video source * feat: add video entry events * add anime series events * add anime resource events * add anime image events * add artist image events * add artist resource events * feat: add artist member events * feat: add artist song events * refactor: use contract for readable model naming * style: fix StyleCI findings * feat: provide discord channel id in event, refactor: contracts, concerns, structural cleanup * style: fix StyleCI findings * style: fix StyleCI findings * fix(admin): don't enforce uniqueness for slugs on creation where customization isn't available * feat: throttle discord notifications * feat: adding announcement events * feat: adding invitation events * feat: adding user events * removing TODOs, localization will be evaluated later * bump dependencies before merge * Make anime index seeder rerunnable (#105) * fix(seeder): make anime index seeder rerunnable * style: fix StyleCI findings * fix(seeder): make anime resource seeder rerunnable (#106) * fix(seeders): make anime season seeder rerunnable (#108) * fix(seeders): make mal seeder rerunnable (#110) * fix(seeders): make anime themes rerunnable (#111) * fix(seeders): make anime themes seeder rerunnable * fix(seeders): handle business fish group * fix(seeders): make artist and series seeders rerunnable (#112) * fix(seeders): make artist song seeder rerunnable (#113) * fix(seeders): make database seed action rerunnable and comprehensive (#114) * Anilist artist seeders (#115) * #45, #80 feat(seeders): add artist resource & cover seeders * style: fix StyleCI findings * test: backfilling event dispatcher tests (#116) * test: backfilling event dispatcher tests * fix: pivot factory directory name * test: discord notifications (#117) * test: policies, discord embed fields, notifications (#118) * test: policies, discord embed fields, notifications * style: fix StyleCI findings * test: models (#119) * test: models * style: fix StyleCI findings * Test: pivots, jobs, mail, json api (#120) * test: pivots, jobs, mail, providers * style: fix StyleCI findings * test: add missing traits to persistent config test for github runner * test: remove provider unit test, should be feature or acceptance test * test: json api - anime, announcement, artist * test: json api - entry endpoints * test: json api - resource endpoints * test: json api - image endpoints * test: json api - series endpoints * test: json api - song endpoints * test: json api - synonym endpoints * test: json api - theme endpoints * test: json api - video endpoints * test: json api - year endpoints * Unit testing Nova (#128) * test: nova unit * style: fix StyleCI findings * #124 chore: php 8 readiness (#129) * #88 feat(api): add views to video resource, fix(api): collection transformation (#130) * Elastic payload filters [incremental] (#131) * #127 feat(api): add top-level filtering to search endpoint * style: fix StyleCI findings * Filter conditions (#132) * feat(api): support for advanced filtering criteria * style: fix StyleCI findings * fix(api): allow elastic host configuration (#133) * fix(api): date filters shall validate allowed formats, timestamp precision (#136) * feat(api): date filters should handle high precision time (#137) * fix(api): add tags to video resource for client slug (#138) * Soft deletes (#139) * feat(api): soft deletion for gatsby incremental builds * style: fix StyleCI findings * sensible defaults for config, test performance, enlightn (#140) * fix(config): default video subdomain, test: performance, chore: enlightn * test: performance fixes * test: fake asset before streaming * fix(config): updating out of date framework stubs, enlightn findings (#141) * fix(config): addressing enlightn findings, updating framework stubs * style: fix StyleCI findings * fix(config): match framework stubs for test runner * feat(config): add horizon dashboard (#142) * fix(config): adding secure headers (#143) * Enlightn pro (#144) * fix(config): enlightn pro * fix(config): fix tests * fix(config): add telescope (#145) * Upgrading jetstream config (#146) * fix(auth): upgrading jetstream stubs, enable terms, fix telescope and tests * style: fix StyleCI findings * fix: laravel-mix 6.0 upgrade * Jetstream ux refactor (#147) * fix(ux): adopt jetstream styling for guest pages * style: fix StyleCI findings * fix(ux): add announcement margin (#148) * feat(docs): adding encoding guides and guidelines (#149) * fix(ux): proper link to encoding guides (#150) * #153 fix: an attempt to address n+1 queries (#165) * #153 fix: an attempt to address n+1 queries * style: fix StyleCI findings * style: fix StyleCI findings * fix(seed): use guzzle client to retrieve contents of external pages (#167) * fix(seed): use guzzle to retrieve external page contents * style: fix StyleCI findings * fix: workflow * fix: workflow * #152 fix: trait for content streaming to optimize image retrieval (#168) * #152 fix: trait for content streaming to optimize image retrieval * style: fix StyleCI finding * fix: hotfix for elastic video index (#169) * #164 feat: add pivot timestamps & qualify conditions (#170) * feat: add pivot timestamps & qualify conditions * fix: remove audits from pivots * #157, #158 feat(docs): add Community & Event subreddit wiki pages (#171) * #157, #158 feat(docs): add Community & Event subreddit wiki pages * refactor: generate URLs * fix(api): add condition for elastic search payload to prevent engine exception (#172) * fix(api): add condition for elastic search payload to prevent engine exception * style: fix StyleCI findings * fix(seed): fail silently if an external service raises an internal exception (#173) * #125 feat(auth): replace user role with jetstream teams (#176) * #125 feat(auth): replace user role with jetstream teams * style: fix StyleCI findings * Onboarding friendliness (#180) * fix: onboarding friendliness, video routing, config best practices, update stubs * style: address StyleCI findings * fix(discord): embed styling updates (#181) * fix(discord): do not raise events on restore save operation (#182) * fix(redis): detect redis enabled, optimize throttling, only use redis is enabled (#183) * fix(redis): named rate limiters expect a specific number of arguments (#184) * #155 fix(api): changing resource inclusion strategy defaults (#185) * fix(api): changing resource inclusion strategy defaults * style: fix StyleCI findings * fix(tests): missed an instance of docs that incorrectly state that all allowed resources are included by default * fix(api): adding missing checks for strictness of query string structure (#186) * feat(api): pagination strategies and constrained eager loads for search (#187) * feat(search): allow filter conditions in elasticsearch payload (#188) * #155 fix(api): don't default to max allowed paging results, trim allowed includes (#190) * fix(api): don't default to max allowed paging results, trim allowed includes * style: fix StyleCI findings * style: fix StyleCI findings * chore: adding favicon, new common position, bumping dependencies (#191) * chore: add favicon, bump dependencies, add new common position * fix(test): specify max page size for announcement index tests * feat(admin): add unlinked videos lens (#194) * #159 fix(admin): adding date range filters for timestamps (#196) * #159 fix(admin): adding date range filters for timestamps * style: fix StyleCI finding * chore: bump dependencies, framework updates, optimize events & policies & seeders (#198) * chore: bump dependencies, framework updates, optimize events & policies & seeders * style: fix StyleCI findings * Transparency (#197) * feat: initial commit for transparency models * style: fix StyleCI findings * feat(transparency): starting landing page * style: fix StyleCI findings * refactor: namespace for billing models * style: fix StyleCI findings * fix(transparency): adding subheadings and better styling for readability * feat(transparency): add filtering and validation * style: fix StyleCI findings * refactor: reconcile against repositories, feat(transparency): reconcile balances, test: donate, transparency * style: fix StyleCI findings * fix: transaction reconcilation, selected month * fix: balance/transaction reconcilation tests * style: fix StyleCI findings * chore: bump dependencies, update stubs, doc & naming cleanup (#199) * chore: bump dependencies, update stubs, doc & naming cleanup * fix: revert change to resend invitation messages * chore: addressing phpstorm code inspection findings (#200) * refactor: phpstorm linting * style: fix StyleCI findings * style: StyleCI should use PHP 8 * style: fix StyleCI findings * feat: enable event discovery, fix: strict typing & lazy loading errors (#201) * chore: bump dependencies (#202) * fix: transparency filtering, fix: psalm findings, chore: plural types in docs (#205) * fix: transparency filtering, fix: psalm findings, chore: plural types in docs * style: fix StyleCI findings * style: fix StyleCI findings * fix: strict type errors in seeders * fix(transparency): restore ordering & adjust reconcile job time, refactor: use lambdas in reconcile traits for comparison (#206) * fix(transparency): restore ordering & adjust reconcile job time, refactor: use lambdas in reconcile traits for comparison * style: fix StyleCI findings * Chore 2021 06 07 (#210) * chore: strict type exception, best OP IX, ordering, first-pass namespacing cleanup * style: fix StyleCI findings * style: fix StyleCI findings * chore: bump dependencies (#212) * Namespacing (#214) * refactor: model namespacing * style: fix StyleCI findings * feat(api): validate filter values for integer columns, chore: update improvement & host docs (#215) * feat(api): validate filter values for integer columns, chore: update improvement & host docs * forgot the test class * style fixes * chore: bump dependencies (#216) * #213 feat(config): support read and write connections (#217) * fix(auth): remove registration link from team invitation mail (#218) * #219 feat(queue): schedule pruned failed jobs command (#224) * fix(api): support discovery of elasticsearch query payloads by matching property for namespacing concerns (#227) * fix(admin): editors should be able to create songs for themes, resource strict type error (#228) * #44 feat(api): migrating docs from swagger to vuepress (#230) * Test nova namespacing (#235) * chore: bump dependencies, refactor: test & nova namespacing * style: fix StyleCI findings * feat(db): schedule daily animethemes-web db-plugin dump (#236) * feat(db): schedule daily animethemes-web db-plugin dump (#237) * fix(api): addressing strict type errors for top-level elasticsearch filter value (#239) * fix(api): allow sorting on searches (#241) * #208, #220 feat(api): add like operator and additional filters [incremental] (#242) * feat(api): add like and not like operators * #208, #220 feat(api): add like operator and additional filters [incremental] * fix(auth): use signed URLs for invite-only registrations (#243) * #203 fix(auth): use signed URLs for invite-only registrations * #203 style: fix StyleCI * #203 fix(auth): remove psalm annotations * #203 fix(auth): exclude sensitive data from parameter logging * #203 fix(auth): exclude sensitive data from parameter logging * #203 fix(auth): exclude sensitive data from parameter logging * #208 feat(api): adding id filters (#244) * feat(api): add id columns, fix(auth): remove fortify register route * style: fix StyleCI findings * fix(api): mismatched class comments * fix(console): we have to assume that the video disk uses the s3 driver (#245) * chore: use http facade instead of guzzle client (#246) * chore: use http facade instead of guzzle * style: fix StyleCI findings * fix(transparency): change type for transaction external id column (#247) * fix(views): hide view recording behind feature flag in middleware layer (#248) * #162 feat(console): prune stale database dumps (#249) * chore: bumping dependencies (#250) * #77 feat(api): add random sort & allow sort key-column mutation, refactor: use data facade & extract criteria / parsers from api query (#251) * feat(api): add random sort & allow sort key-column mutation, refactor: use date facade & extract criteria/parsers from api query * style: fix StyleCI findings * refactor: make criteria function names consistent * fix(api): enum filters should use description instead of key (#252) * chore: fix namespace of tests for PSR-4 compliance (#253) * fix(api): sort criteria should be applied to sorts in order (#254) * fix(api): circumvent exception caused by entry version field mapping (#255) * #42 fix: namespace nested anime types [incremental] (#256) * #42 fix: namespace nested anime types * style: fix StyleCI findings * fix: delete new migrations * #42 fix(api): nested api resources (#257) * #42 fix(api): nested api resources * style: fix StyleCI findings * fix(api): fix limit parsing for pagination strategy (#258) * fix(api): rename search indices to match table names (#259) * chore: bump dependencies (#260) * #72 feat(api): initial commit for studio resource (#261) * #72 feat(api): initial commit for studio resource * style: fix StyleCI finding * fix: rollback faker change (#262) * fix(admin): addressing staging exceptions (#263) * fix(api): make studio collection wrap plural (#264) * feat(api): add relation sort (#265) * feat(api): add relation sort * style: fix StyleCI findings * #160 feat(api): initial implementation of relation filter (#266) * feat(api): initial implementation of relation filter * style: fix StyleCI findings * chore: apply select binding to query builder to reduce memory footprint (#267) * chore: apply select binding to query builder to reduce memory footprint * fix: always include name attributes * feat(api): initial commit for api form requests (#268) * feat(api): initial commit for api form requests * style: fix StyleCI findings * fix(api): correcting year index, abstracting search rules * feat(api): validate sparse fieldsets (#269) * feat(api): validate api sparse fieldsets * style: fix StyleCI findings * chore: bump & prune dependencies, update stubs, address static analysis findings (#270) * chore: bump & prune dependencies, update stubs, address static analysis findings * chore: bump static analysis level * test: use new lazily refresh database trait (#271) * test: use new lazily refresh database trait for test cases * style: fix StyleCI findings * fix(api): address exceptions for resource inclusion in search endpoint & minimize requests (#272) * fix(api): address exceptions for resource inclusion in search endpoint & minimize requests * style: fix StyleCI findings * fix(api): revert search resource inclusion fix (#273) * fix(api): plurality of resource wrap should be consistent (#274) * chore: add static analysis github action (#275) * chore: add static analysis action * chore: test static analysis failure * chore: revert * chore: rename static analysis job * chore: add badge to readme * [WIP] StudioResource pivot table (#277) * Added app.css and app.js to .gitignore * Created migration for the studio resource pivot * Created studio resource pivot class * Created relations in Studio and ExternalResource for pivot table * Added StudioResource table to database dump command * Created Events for StudioResource pivot * Added allowed include to Studio and External Resource schema * Created StudioResourceFactory * Added StudioResource attributes to the json resource * Added relations to nova resources * Created new policies for StudioResource pivot * Created unit tests for StudioResource pivot * Apply fixes from StyleCI * Fixed $allowedTables sorting * Fixed various mistakes found in code review. * Apply fixes from StyleCI * Fixed ordering of ATTRIBUTE constants * Fixed mistakes found in 2nd code review. * Apply fixes from StyleCI * Added StudioResourcePivot to StudioSchema fields * Fixed nova "studios" resource in ExternalResource * Apply fixes from StyleCI Co-authored-by: ProWeebDev Co-authored-by: vagrant * chore: bump dependencies, stub updates, use framework casting for enums, arrow functions (#278) * fix(admin): unlinked resource lens should consider studio relation (#279) * fix(db): seed studio resources when seeding anime studios (#282) * #72 fix(admin): add lenses for studio resources (#283) * fix(admin): add lenses for studio resources * style: fix StyleCI findings * fix(admin): check feature flags before handling jobs or notifications (#284) * fix(admin): check feature flags before handling job or notification, relation cleanup in events * style: fix StyleCI findings * feat(api): add config item endpoints & make featured theme configurable (#285) * feat(api): add config item endpoints & make featured theme configurable * feat(api): add config item endpoints & make featured theme configurable [missing config file] * fix(seed): support 2022 & mal client ID header, chore: bump dependencies & stubs (#286) * chore: adopt constructor property promotion prior to php 8.1 upgrade (#287) * chore: adopt constructor property promotion * style: fix StyleCI findings * API Query Types (#288) * fix(api): extract query & search service types, chore: bump dependencies & update stubs * style: fix StyleCI findings * fix: bump incompatible dependencies * test: restore quietly (#289) * fix(console): only allow includeTables option in db dump for sqlite v3.32.0+ (#290) * chore: void typehint, static access fixes, reduce regex, unwrap variables, avoid magic method accessors, grammar & typos (#291) * chore: prune middleware stubs (#292) * chore: prune middleware stubs * chore: prune middleware stubs - forgot modified files * fix: prefer raising exceptions to failing silently or exiting early [incremental] (#293) * fix: prefer raising exceptions to failing silently or exiting early * style: address StyleCI findings * fix: prefer raising exceptions to failing silently or exiting early (#294) * feat: adding pages resource (#296) * feat: add pages resource * style: address StyleCI findings * chore: fix links, missing admin config, buggy test (#297) * Search validation (#298) * feat(api): validate search endpoint parameters [incremental] * style: fix StyleCI findings * feat(api): add scoping to sorts & cleanup validation for endpoints (#299) * fix(api): conditionally validate filter values [incremental] (#300) * fix(api): conditionally validate filter values * style: fix StyleCI findings * feat: update to laravel 9 (#301) * feat: update to Laravel 9 * fix(test): force update of phpstan by removing enlightn & addressing new static analysis findings * style: address StyleCI finding * upgrade to php 8.1 (#302) * feat: upgrade to php 8.1 * test: fix test configuration * feat(api): support select clause for api queries (#303) * feat(api): support select clause for api queries * style: fix StyleCI findings * fix(api): some fields for video resources were the wrong type * chore: update dependencies (#304) * feat(console): add ability to dump document-related database tables (#305) * feat(api): validate allowed filter types [incremental] (#306) * feat(api): validate allowed filter types [incremental] * style: fix StyleCI findings * feat(api): support extended write endpoints [incremental: framework & anime implementation] (#307) * feat(api): support extended write endpoints [incremental: framework & anime implementation] * style: fix StyleCI findings * fix(api): write endpoints need to check token ability as well as policy ability * feat(api): support extended write endpoints [incremental: artists] (#308) * feat(api): support extended write endpoints [incremental: external resources] (#309) * feat(api): support extended write endpoints [incremental: external resources] * feat(api): support extended write endpoints [incremental: external resources] * style: fix StyleCI findings * feat(api): support extended write endpoints [incremental: series] (#310) * feat(api): support extended write endpoints [incremental: songs] (#311) * feat(api): support extended write endpoints [incremental: studios] (#312) * fix(api): support extended write endpoints [incremental: synonyms] (#313) * feat(api): support extended write endpoints [incremental: synonyms] * fix(tests): don't create slug that violates constraint * feat(api): support extended write endpoints [incremental: themes] (#314) * feat(api): support extended write endpoints [incremental: entries] (#315) * feat(api): support extended write endpoints [incremental: entries] * style: fix StyleCI findings * feat(api): support extended write endpoints [incremental: announcements] (#316) * feat(api): support extended write endpoints [incremental: announcements] * style: fix StyleCI findings * feat(api): support extended write endpoints [incremental: videos] (#317) * feat(api): support extended write endpoints [incremental: videos] * style: fix StyleCI findings * feat(api): support extended write endpoints [incremental: pages] (#318) * feat(api): support extended write endpoints [incremental: pages] * style: fix StyleCI findings * feat(api): support extended write endpoints [incremental: balances] (#319) * feat(api): support extended write endpoints [incremental: transactions] (#320) * feat(api): support extended write endpoints [incremental: images] (#321) * Delete reddit seeders (#322) * fix(db): remove reddit seeders * fix(db): remove reddit seeders - update seeder * fix(api): don't serve images from laravel * chore: update dependencies & stubs (#323) * chore: update dependencies & stubs * fix: update dependency version constraints * refactor: discover routes in controller attributes (#324) * feat(admin): update to Nova V4 (#325) * feat(admin): update to Nova V4 * style: fix StyleCI findings * fix(admin): address exceptions & apply fixes from day 1 patches (#326) * feat(admin): Added AnimeThemes branding to Nova (#327) * fix(admin): explicitly declare searchable columns as columns (#328) * fix(admin): explicitly declare searchable columns as columns * fix(admin): missing removal of searchable column declaration on synonyms * feat(admin): add subtitles to distinguish entries in searches (#329) * feat(admin): backfill anime studios & make resources actionable (#330) * feat(admin): backfill anime studios & make resources actionable * style: fix StyleCI findings * feat(admin): support db queue connection (#331) * feat(admin): backfill anime kitsu resources (#332) * feat(admin): backfill anime kitsu resources * style: fix StyleCI findings * fix(admin): add theme from song (#333) * feat(elastic): Improved search query payload for anime (#334) * feat(admin): backfill anidb resources (#335) * feat(admin): backfill anidb resources * style: address StyleCI findings * fix(admin): address eager loading with resource fields (#336) * feat(api): allow client to bypass API rate limiter (#337) * refactor: backfill anime should not require a specific external resource site (#338) * refactor: backfill anime should not require a specific external resource site * style: fix StyleCI findings * fix: fall-through anime studio backfilling * feat(admin): backfill ann anime studios (#339) * feat(api): Added allowed include paths needed for client (#340) * feat(admin): backfill anilist anime resources (#341) * feat(admin): backfill mal resources (#342) * feat(admin): backfill mal resources * style: fix StyleCI findings * feat(admin): backfill ann resources (#343) * fix(admin): fix admin & moderation gates (#344) * feat(admin): backfill anime images (#347) * feat(admin): backfill anime images * style: fix StyleCI findings * Update videos controller to serve videos through nginx (#346) * Update videos controller to serve videos through nginx The proposed change will pass the video through nginx instead of using PHP. * refactor: use default response & update tests * style: fix StyleCI findings Co-authored-by: paranarimasu <33796518+paranarimasu@users.noreply.github.com> * chore: nginx redirect hotfix, remove reconciliation scheduler task & bump nova (#348) * fix(admin): make nested types discoverable & make relations filterable (#349) * fix(admin): make nested types discoverable & make relations filterable * fix(admin): policy functions for nested types were named incorrectly * Admin deletion notifications (#350) * feat(admin): notify admins of soft deletions for review * style: fix StyleCI findings * Chore 2022 04 27 (#351) * chore: bump dependencies & address admin config errors * style: fix StyleCI finding * feat(admin): allow backfilling multiple anime & fixing resource reconciliation (#352) * feat(console): allow video reconciliation against a specific path (#353) * refactor(events): use covariance to build proper type hierarchy for event classes (#354) * chore: bump dependencies (#355) * chore: bump dependencies * style: fix StyleCI findings * chore: prune instance of make & update sanctum token expiration (#356) * feat: replace jetstream teams with spatie permissions (#357) * feat: replace jetstream teams with spatie permissions * style: fix StyleCI findings * style: fix StyleCI finding * fix(tests): address performance issues with seeding permissions * style: fix StyleCI finding * fix(admin): disable wildcard permissions due to performance issues * fix: remove abilities from permission resource * fix: make migrations more fault proof, support db cache & fix user factory permission creation * chore: bump dependencies, fix(admin): a few QoL changes (#358) * fix(admin): make resource field dependency more nullsafe to accommodate official sites (#359) * chore: bumping dependencies, updating stubs & cleanup sweep (#360) * chore: bumping dependencies, updating stubs & cleanup sweep * style: fix StyleCI findings * fix(api): fix discovery of ES Query Payload classes after namespace refactor (#361) * fix(api): has filter validation (#362) * feat: log authentication events, chore: another cleanup sweep (#370) * feat: log authentication events, chore: another cleanup sweep * test: remove status code assertion for test runner env * test: remove streaming test to prevent exception in test runner * chore: bump minimum required laravel framework version * fix(api): support sorting on text fields in elasticsearch queries (#371) * feat(api): filter on keyword multifield for text mappings (#372) * chore: bump dependencies (#373) * chore: bump dependencies * fix: suppress false positive static analysis finding * feat: use permission to determine if api rate limiter can be bypassed (#374) * fix(api): use animethemeentryvideo pivot for featured theme (#375) * fix(api): add missing featured theme relations (#376) * fix(api): song.title relation sort for theme search index (#377) * fix(api): register all sort criteria at once to avoid overriding (#378) * feat: support customization of domains and paths of route groups (#379) * feat: support customization of domains and paths of route groups * fix fallback test url * style: fix StyleCI finding * fix: video url should not append path by default (#380) * fix: remove old database migrations & update robots.txt Co-authored-by: ProWeebDev <34277874+ProWeebDev@users.noreply.github.com> Co-authored-by: ProWeebDev Co-authored-by: vagrant Co-authored-by: Manuel S --- .editorconfig | 3 + .env.example | 200 +- .gitattributes | 11 +- .github/CONTRIBUTING.md | 6 +- .github/workflows/static-analysis.yml | 46 + .github/workflows/test.yml | 46 + .gitignore | 9 +- .styleci.yml | 4 +- README.md | 8 +- app/Actions/Fortify/CreateNewUser.php | 46 + app/Actions/Fortify/ResetUserPassword.php | 39 + app/Actions/Fortify/UpdateUserPassword.php | 46 + .../Fortify/UpdateUserProfileInformation.php | 68 + app/Actions/Jetstream/DeleteUser.php | 28 + app/Concerns/DetectsRedis.php | 26 + .../Discord/HasAttributeUpdateEmbedFields.php | 80 + .../Discord/HasDiscordEmbedFields.php | 41 + .../Requests/Api/ValidatesConditionally.php | 53 + .../Http/Requests/Api/ValidatesFields.php | 33 + .../Http/Requests/Api/ValidatesFilters.php | 140 + .../Http/Requests/Api/ValidatesIncludes.php | 32 + .../Http/Requests/Api/ValidatesPaging.php | 71 + .../Http/Requests/Api/ValidatesParameters.php | 105 + .../Http/Requests/Api/ValidatesSorts.php | 53 + .../Billing/ReconcilesBalanceRepositories.php | 87 + .../ReconcilesTransactionRepositories.php | 45 + .../Repositories/ReconcilesRepositories.php | 341 + .../Wiki/ReconcilesVideoRepositories.php | 82 + .../Commands/AnnouncementCreateCommand.php | 86 - .../Commands/AnnouncementDeleteCommand.php | 73 - .../Commands/AnnouncementListCommand.php | 48 - .../Commands/AnnouncementReadCommand.php | 73 - .../Commands/AnnouncementUpdateCommand.php | 85 - .../Balance/BalanceReconcileCommand.php | 172 + .../Billing/ServiceReconcileCommand.php | 80 + .../TransactionReconcileCommand.php | 172 + app/Console/Commands/DatabaseDumpCommand.php | 157 + .../Document/DocumentDatabaseDumpCommand.php | 50 + .../Commands/PruneDatabaseDumpsCommand.php | 150 + .../Commands/VideoReconcileCommand.php | 156 - .../Wiki/Video/VideoReconcileCommand.php | 171 + .../Commands/Wiki/WikiDatabaseDumpCommand.php | 90 + app/Console/Kernel.php | 108 +- app/Constants/Config/FlagConstants.php | 23 + app/Constants/Config/ServiceConstants.php | 15 + app/Constants/Config/WikiConstants.php | 17 + app/Contracts/Events/CascadesDeletesEvent.php | 18 + .../Events/CascadesRestoresEvent.php | 18 + app/Contracts/Events/DiscordMessageEvent.php | 27 + .../Events/NovaNotificationEvent.php | 35 + .../Events/UpdateRelatedIndicesEvent.php | 18 + .../Http/Api/Field/CreatableField.php | 21 + .../Http/Api/Field/FieldInterface.php | 18 + .../Http/Api/Field/FilterableField.php | 20 + .../Http/Api/Field/SelectableField.php | 21 + .../Http/Api/Field/SortableField.php | 20 + .../Http/Api/Field/UpdatableField.php | 21 + .../Http/Api/Query/QueryInterface.php | 20 + .../Http/Api/Schema/SchemaInterface.php | 51 + .../Http/Requests/Api/SearchableRequest.php | 12 + app/Contracts/Models/Nameable.php | 18 + app/Contracts/Pipes/Pipe.php | 23 + app/Contracts/Repositories/Repository.php | 65 + app/Discord/DiscordEmbedField.php | 90 + app/Enums/BaseEnum.php | 30 + app/Enums/Discord/EmbedColor.php | 21 + .../Http/Api/Filter/AllowedDateFormat.php | 21 + .../Http/Api/Filter/BinaryLogicalOperator.php | 17 + .../Http/Api/Filter/ComparisonOperator.php | 31 + app/Enums/Http/Api/Filter/LogicalOperator.php | 15 + app/Enums/Http/Api/Filter/TrashedStatus.php | 17 + .../Http/Api/Filter/UnaryLogicalOperator.php | 13 + .../Http/Api/Paging/PaginationStrategy.php | 21 + app/Enums/Http/Api/Sort/Direction.php | 19 + app/Enums/Models/Auth/InvitationStatus.php | 19 + app/Enums/Models/Billing/BalanceFrequency.php | 19 + app/Enums/Models/Billing/Service.php | 25 + app/Enums/Models/Wiki/AnimeSeason.php | 18 + app/Enums/Models/Wiki/ImageFacet.php | 21 + app/Enums/Models/Wiki/ResourceSite.php | 137 + app/Enums/Models/Wiki/ThemeType.php | 16 + app/Enums/Models/Wiki/VideoOverlap.php | 21 + app/Enums/Models/Wiki/VideoSource.php | 20 + .../Announcement/AnnouncementCreated.php | 46 + .../Announcement/AnnouncementDeleted.php | 46 + .../Announcement/AnnouncementRestored.php | 46 + .../Announcement/AnnouncementUpdated.php | 47 + .../Auth/Invitation/InvitationCreated.php | 46 + .../Auth/Invitation/InvitationDeleted.php | 46 + .../Auth/Invitation/InvitationRestored.php | 46 + .../Auth/Invitation/InvitationUpdated.php | 47 + app/Events/Auth/User/UserCreated.php | 47 + app/Events/Auth/User/UserDeleted.php | 45 + app/Events/Auth/User/UserEvent.php | 33 + app/Events/Auth/User/UserRestored.php | 47 + app/Events/Auth/User/UserUpdated.php | 61 + app/Events/Base/Admin/AdminCreatedEvent.php | 28 + app/Events/Base/Admin/AdminDeletedEvent.php | 28 + app/Events/Base/Admin/AdminRestoredEvent.php | 28 + app/Events/Base/Admin/AdminUpdatedEvent.php | 28 + app/Events/Base/BaseCreatedEvent.php | 44 + app/Events/Base/BaseDeletedEvent.php | 42 + app/Events/Base/BaseRestoredEvent.php | 44 + app/Events/Base/BaseUpdatedEvent.php | 45 + app/Events/Base/Pivot/PivotCreatedEvent.php | 37 + app/Events/Base/Pivot/PivotDeletedEvent.php | 37 + app/Events/Base/Pivot/PivotUpdatedEvent.php | 38 + app/Events/Base/Wiki/WikiCreatedEvent.php | 28 + app/Events/Base/Wiki/WikiDeletedEvent.php | 85 + app/Events/Base/Wiki/WikiRestoredEvent.php | 28 + app/Events/Base/Wiki/WikiUpdatedEvent.php | 28 + app/Events/BaseEvent.php | 31 + app/Events/BasePivotEvent.php | 66 + app/Events/Billing/Balance/BalanceCreated.php | 46 + app/Events/Billing/Balance/BalanceDeleted.php | 46 + .../Billing/Balance/BalanceRestored.php | 46 + app/Events/Billing/Balance/BalanceUpdated.php | 47 + .../Transaction/TransactionCreated.php | 46 + .../Transaction/TransactionDeleted.php | 46 + .../Transaction/TransactionRestored.php | 46 + .../Transaction/TransactionUpdated.php | 46 + app/Events/Document/Page/PageCreated.php | 46 + app/Events/Document/Page/PageDeleted.php | 69 + app/Events/Document/Page/PageRestored.php | 46 + app/Events/Document/Page/PageUpdated.php | 47 + .../Pivot/AnimeImage/AnimeImageCreated.php | 41 + .../Pivot/AnimeImage/AnimeImageDeleted.php | 41 + .../AnimeResource/AnimeResourceCreated.php | 41 + .../AnimeResource/AnimeResourceDeleted.php | 41 + .../AnimeResource/AnimeResourceUpdated.php | 42 + .../Pivot/AnimeSeries/AnimeSeriesCreated.php | 41 + .../Pivot/AnimeSeries/AnimeSeriesDeleted.php | 41 + .../Pivot/AnimeStudio/AnimeStudioCreated.php | 41 + .../Pivot/AnimeStudio/AnimeStudioDeleted.php | 41 + .../AnimeThemeEntryAnimeThemeCreatedVideo.php | 54 + .../AnimeThemeEntryAnimeThemeDeletedVideo.php | 54 + .../Pivot/ArtistImage/ArtistImageCreated.php | 41 + .../Pivot/ArtistImage/ArtistImageDeleted.php | 41 + .../ArtistMember/ArtistMemberCreated.php | 40 + .../ArtistMember/ArtistMemberDeleted.php | 40 + .../ArtistMember/ArtistMemberUpdated.php | 41 + .../ArtistResource/ArtistResourceCreated.php | 41 + .../ArtistResource/ArtistResourceDeleted.php | 41 + .../ArtistResource/ArtistResourceUpdated.php | 42 + .../Pivot/ArtistSong/ArtistSongCreated.php | 54 + .../Pivot/ArtistSong/ArtistSongDeleted.php | 54 + .../Pivot/ArtistSong/ArtistSongUpdated.php | 55 + .../StudioResource/StudioResourceCreated.php | 41 + .../StudioResource/StudioResourceDeleted.php | 41 + .../StudioResource/StudioResourceUpdated.php | 42 + app/Events/Wiki/Anime/AnimeCreated.php | 68 + app/Events/Wiki/Anime/AnimeDeleted.php | 69 + app/Events/Wiki/Anime/AnimeDeleting.php | 66 + app/Events/Wiki/Anime/AnimeRestored.php | 83 + app/Events/Wiki/Anime/AnimeUpdated.php | 69 + .../Wiki/Anime/Synonym/SynonymCreated.php | 78 + .../Wiki/Anime/Synonym/SynonymDeleted.php | 101 + .../Wiki/Anime/Synonym/SynonymRestored.php | 78 + .../Wiki/Anime/Synonym/SynonymUpdated.php | 79 + .../Wiki/Anime/Theme/Entry/EntryCreated.php | 60 + .../Wiki/Anime/Theme/Entry/EntryDeleted.php | 84 + .../Wiki/Anime/Theme/Entry/EntryDeleting.php | 55 + .../Wiki/Anime/Theme/Entry/EntryRestored.php | 60 + .../Wiki/Anime/Theme/Entry/EntryUpdated.php | 61 + app/Events/Wiki/Anime/Theme/ThemeCreated.php | 73 + app/Events/Wiki/Anime/Theme/ThemeCreating.php | 36 + app/Events/Wiki/Anime/Theme/ThemeDeleted.php | 78 + app/Events/Wiki/Anime/Theme/ThemeDeleting.php | 59 + app/Events/Wiki/Anime/Theme/ThemeRestored.php | 77 + app/Events/Wiki/Anime/Theme/ThemeUpdated.php | 74 + app/Events/Wiki/Artist/ArtistCreated.php | 46 + app/Events/Wiki/Artist/ArtistDeleted.php | 69 + app/Events/Wiki/Artist/ArtistRestored.php | 46 + app/Events/Wiki/Artist/ArtistUpdated.php | 47 + .../ExternalResourceCreated.php | 46 + .../ExternalResourceDeleted.php | 69 + .../ExternalResourceRestored.php | 46 + .../ExternalResourceUpdated.php | 47 + app/Events/Wiki/Image/ImageCreated.php | 46 + app/Events/Wiki/Image/ImageDeleted.php | 69 + app/Events/Wiki/Image/ImageDeleting.php | 36 + app/Events/Wiki/Image/ImageRestored.php | 46 + app/Events/Wiki/Image/ImageUpdated.php | 47 + app/Events/Wiki/Series/SeriesCreated.php | 46 + app/Events/Wiki/Series/SeriesDeleted.php | 69 + app/Events/Wiki/Series/SeriesRestored.php | 46 + app/Events/Wiki/Series/SeriesUpdated.php | 47 + app/Events/Wiki/Song/SongCreated.php | 71 + app/Events/Wiki/Song/SongDeleted.php | 95 + app/Events/Wiki/Song/SongDeleting.php | 71 + app/Events/Wiki/Song/SongRestored.php | 73 + app/Events/Wiki/Song/SongUpdated.php | 72 + app/Events/Wiki/Studio/StudioCreated.php | 46 + app/Events/Wiki/Studio/StudioDeleted.php | 69 + app/Events/Wiki/Studio/StudioRestored.php | 46 + app/Events/Wiki/Studio/StudioUpdated.php | 47 + app/Events/Wiki/Video/VideoCreated.php | 46 + app/Events/Wiki/Video/VideoCreating.php | 36 + app/Events/Wiki/Video/VideoDeleted.php | 69 + app/Events/Wiki/Video/VideoRestored.php | 46 + app/Events/Wiki/Video/VideoUpdated.php | 47 + app/Exceptions/Handler.php | 47 +- app/Grills/Grill.php | 18 - app/Grills/GrillFactory.php | 25 - app/Http/Api/Criteria/Field/Criteria.php | 54 + app/Http/Api/Criteria/Filter/Criteria.php | 139 + app/Http/Api/Criteria/Filter/Expression.php | 30 + app/Http/Api/Criteria/Filter/HasCriteria.php | 154 + app/Http/Api/Criteria/Filter/Predicate.php | 57 + .../Api/Criteria/Filter/TrashedCriteria.php | 79 + .../Api/Criteria/Filter/WhereCriteria.php | 107 + .../Api/Criteria/Filter/WhereInCriteria.php | 116 + app/Http/Api/Criteria/Include/Criteria.php | 32 + .../Api/Criteria/Include/ResourceCriteria.php | 34 + app/Http/Api/Criteria/Paging/Criteria.php | 59 + .../Api/Criteria/Paging/LimitCriteria.php | 39 + .../Api/Criteria/Paging/OffsetCriteria.php | 64 + app/Http/Api/Criteria/Search/Criteria.php | 30 + app/Http/Api/Criteria/Sort/Criteria.php | 76 + app/Http/Api/Criteria/Sort/FieldCriteria.php | 50 + app/Http/Api/Criteria/Sort/RandomCriteria.php | 39 + .../Api/Criteria/Sort/RelationCriteria.php | 36 + .../Announcement/AnnouncementContentField.php | 56 + app/Http/Api/Field/Base/CreatedAtField.php | 22 + app/Http/Api/Field/Base/DeletedAtField.php | 22 + app/Http/Api/Field/Base/IdField.php | 38 + app/Http/Api/Field/Base/UpdatedAtField.php | 22 + .../Billing/Balance/BalanceDateField.php | 56 + .../Billing/Balance/BalanceFrequencyField.php | 56 + .../Balance/BalanceMonthToDateField.php | 55 + .../Billing/Balance/BalanceServiceField.php | 56 + .../Billing/Balance/BalanceUsageField.php | 54 + .../Transaction/TransactionAmountField.php | 54 + .../Transaction/TransactionDateField.php | 56 + .../TransactionDescriptionField.php | 56 + .../TransactionExternalIdField.php | 57 + .../Transaction/TransactionServiceField.php | 56 + app/Http/Api/Field/BooleanField.php | 50 + .../FlagsAllowDiscordNotificationsField.php | 22 + .../Flags/FlagsAllowVideoStreamsField.php | 22 + .../Flags/FlagsAllowViewRecordingField.php | 22 + .../Config/Wiki/WikiFeaturedThemeField.php | 22 + app/Http/Api/Field/DateField.php | 50 + .../Api/Field/Document/Page/PageBodyField.php | 70 + .../Api/Field/Document/Page/PageNameField.php | 56 + .../Api/Field/Document/Page/PageSlugField.php | 59 + app/Http/Api/Field/EnumField.php | 76 + app/Http/Api/Field/Field.php | 43 + app/Http/Api/Field/FloatField.php | 50 + app/Http/Api/Field/IntField.php | 50 + app/Http/Api/Field/StringField.php | 50 + .../Api/Field/Wiki/Anime/AnimeAsField.php | 22 + .../Api/Field/Wiki/Anime/AnimeNameField.php | 56 + .../Api/Field/Wiki/Anime/AnimeSeasonField.php | 56 + .../Api/Field/Wiki/Anime/AnimeSlugField.php | 59 + .../Field/Wiki/Anime/AnimeSynopsisField.php | 55 + .../Api/Field/Wiki/Anime/AnimeYearField.php | 64 + .../Anime/Synonym/SynonymAnimeIdField.php | 72 + .../Wiki/Anime/Synonym/SynonymTextField.php | 56 + .../Anime/Theme/Entry/EntryEpisodesField.php | 57 + .../Anime/Theme/Entry/EntryNotesField.php | 57 + .../Wiki/Anime/Theme/Entry/EntryNsfwField.php | 55 + .../Anime/Theme/Entry/EntrySpoilerField.php | 55 + .../Anime/Theme/Entry/EntryThemeIdField.php | 72 + .../Anime/Theme/Entry/EntryVersionField.php | 57 + .../Wiki/Anime/Theme/ThemeAnimeIdField.php | 72 + .../Wiki/Anime/Theme/ThemeGroupField.php | 57 + .../Wiki/Anime/Theme/ThemeSequenceField.php | 57 + .../Field/Wiki/Anime/Theme/ThemeSlugField.php | 40 + .../Wiki/Anime/Theme/ThemeSongIdField.php | 73 + .../Field/Wiki/Anime/Theme/ThemeTypeField.php | 56 + .../Api/Field/Wiki/Artist/ArtistAsField.php | 22 + .../Api/Field/Wiki/Artist/ArtistNameField.php | 56 + .../Api/Field/Wiki/Artist/ArtistSlugField.php | 59 + .../ExternalResourceAsField.php | 22 + .../ExternalResourceIdField.php | 57 + .../ExternalResourceLinkField.php | 83 + .../ExternalResourceSiteField.php | 83 + .../Api/Field/Wiki/Image/ImageFacetField.php | 57 + .../Api/Field/Wiki/Image/ImageFileField.php | 39 + .../Api/Field/Wiki/Image/ImageLinkField.php | 22 + .../Api/Field/Wiki/Image/ImagePathField.php | 36 + .../Field/Wiki/Search/SearchAnimeField.php | 22 + .../Field/Wiki/Search/SearchArtistField.php | 22 + .../Field/Wiki/Search/SearchSeriesField.php | 22 + .../Api/Field/Wiki/Search/SearchSongField.php | 22 + .../Field/Wiki/Search/SearchStudioField.php | 22 + .../Field/Wiki/Search/SearchThemeField.php | 22 + .../Field/Wiki/Search/SearchVideoField.php | 22 + .../Api/Field/Wiki/Series/SeriesNameField.php | 56 + .../Api/Field/Wiki/Series/SeriesSlugField.php | 59 + app/Http/Api/Field/Wiki/Song/SongAsField.php | 22 + .../Api/Field/Wiki/Song/SongTitleField.php | 57 + .../Api/Field/Wiki/Studio/StudioAsField.php | 22 + .../Api/Field/Wiki/Studio/StudioNameField.php | 56 + .../Api/Field/Wiki/Studio/StudioSlugField.php | 59 + .../Field/Wiki/Video/VideoBasenameField.php | 53 + .../Field/Wiki/Video/VideoFilenameField.php | 39 + .../Api/Field/Wiki/Video/VideoLinkField.php | 22 + .../Api/Field/Wiki/Video/VideoLyricsField.php | 68 + .../Field/Wiki/Video/VideoMimeTypeField.php | 39 + .../Api/Field/Wiki/Video/VideoNcField.php | 68 + .../Field/Wiki/Video/VideoOverlapField.php | 57 + .../Api/Field/Wiki/Video/VideoPathField.php | 39 + .../Field/Wiki/Video/VideoResolutionField.php | 72 + .../Api/Field/Wiki/Video/VideoSizeField.php | 39 + .../Api/Field/Wiki/Video/VideoSourceField.php | 70 + .../Api/Field/Wiki/Video/VideoSubbedField.php | 68 + .../Api/Field/Wiki/Video/VideoTagsField.php | 22 + .../Api/Field/Wiki/Video/VideoUncenField.php | 55 + app/Http/Api/Filter/BooleanFilter.php | 82 + app/Http/Api/Filter/DateFilter.php | 106 + app/Http/Api/Filter/EnumFilter.php | 93 + app/Http/Api/Filter/Filter.php | 139 + app/Http/Api/Filter/FloatFilter.php | 84 + app/Http/Api/Filter/HasFilter.php | 94 + app/Http/Api/Filter/IntFilter.php | 84 + app/Http/Api/Filter/StringFilter.php | 74 + app/Http/Api/Filter/TrashedFilter.php | 40 + app/Http/Api/Include/AllowedInclude.php | 43 + app/Http/Api/Parser/FieldParser.php | 63 + app/Http/Api/Parser/FilterParser.php | 91 + app/Http/Api/Parser/IncludeParser.php | 80 + app/Http/Api/Parser/PagingParser.php | 81 + app/Http/Api/Parser/Parser.php | 26 + app/Http/Api/Parser/SearchParser.php | 55 + app/Http/Api/Parser/SortParser.php | 95 + .../Api/Query/Admin/AnnouncementReadQuery.php | 63 + .../Query/Admin/AnnouncementWriteQuery.php | 50 + app/Http/Api/Query/Base/EloquentReadQuery.php | 224 + .../Api/Query/Base/EloquentWriteQuery.php | 129 + .../Billing/Balance/BalanceReadQuery.php | 63 + .../Billing/Balance/BalanceWriteQuery.php | 50 + .../Transaction/TransactionReadQuery.php | 63 + .../Transaction/TransactionWriteQuery.php | 50 + app/Http/Api/Query/Config/FlagsReadQuery.php | 25 + app/Http/Api/Query/Config/WikiReadQuery.php | 25 + .../Api/Query/Document/Page/PageReadQuery.php | 63 + .../Query/Document/Page/PageWriteQuery.php | 50 + app/Http/Api/Query/ReadQuery.php | 180 + .../Api/Query/Wiki/Anime/AnimeReadQuery.php | 63 + .../Api/Query/Wiki/Anime/AnimeWriteQuery.php | 50 + .../Wiki/Anime/Synonym/SynonymReadQuery.php | 63 + .../Wiki/Anime/Synonym/SynonymWriteQuery.php | 50 + .../Wiki/Anime/Theme/Entry/EntryReadQuery.php | 63 + .../Anime/Theme/Entry/EntryWriteQuery.php | 50 + .../Query/Wiki/Anime/Theme/ThemeReadQuery.php | 63 + .../Wiki/Anime/Theme/ThemeWriteQuery.php | 50 + .../Api/Query/Wiki/Artist/ArtistReadQuery.php | 63 + .../Query/Wiki/Artist/ArtistWriteQuery.php | 50 + .../ExternalResourceReadQuery.php | 63 + .../ExternalResourceWriteQuery.php | 50 + .../Api/Query/Wiki/Image/ImageReadQuery.php | 63 + .../Api/Query/Wiki/Image/ImageWriteQuery.php | 80 + app/Http/Api/Query/Wiki/SearchReadQuery.php | 68 + .../Api/Query/Wiki/Series/SeriesReadQuery.php | 63 + .../Query/Wiki/Series/SeriesWriteQuery.php | 50 + .../Api/Query/Wiki/Song/SongReadQuery.php | 63 + .../Api/Query/Wiki/Song/SongWriteQuery.php | 50 + .../Api/Query/Wiki/Studio/StudioReadQuery.php | 63 + .../Query/Wiki/Studio/StudioWriteQuery.php | 50 + .../Api/Query/Wiki/Video/VideoReadQuery.php | 63 + .../Api/Query/Wiki/Video/VideoWriteQuery.php | 50 + app/Http/Api/Query/WriteQuery.php | 22 + .../Api/Schema/Admin/AnnouncementSchema.php | 65 + app/Http/Api/Schema/Billing/BalanceSchema.php | 73 + .../Api/Schema/Billing/TransactionSchema.php | 73 + app/Http/Api/Schema/Config/FlagsSchema.php | 55 + app/Http/Api/Schema/Config/WikiSchema.php | 51 + app/Http/Api/Schema/Document/PageSchema.php | 69 + app/Http/Api/Schema/EloquentSchema.php | 18 + app/Http/Api/Schema/Schema.php | 80 + .../Api/Schema/Wiki/Anime/SynonymSchema.php | 70 + .../Schema/Wiki/Anime/Theme/EntrySchema.php | 82 + .../Api/Schema/Wiki/Anime/ThemeSchema.php | 112 + app/Http/Api/Schema/Wiki/AnimeSchema.php | 96 + app/Http/Api/Schema/Wiki/ArtistSchema.php | 87 + .../Schema/Wiki/ExternalResourceSchema.php | 75 + app/Http/Api/Schema/Wiki/ImageSchema.php | 74 + app/Http/Api/Schema/Wiki/SearchSchema.php | 72 + app/Http/Api/Schema/Wiki/SeriesSchema.php | 74 + app/Http/Api/Schema/Wiki/SongSchema.php | 72 + app/Http/Api/Schema/Wiki/StudioSchema.php | 77 + app/Http/Api/Schema/Wiki/VideoSchema.php | 97 + app/Http/Api/Scope/GlobalScope.php | 22 + app/Http/Api/Scope/RelationScope.php | 53 + app/Http/Api/Scope/Scope.php | 19 + app/Http/Api/Scope/ScopeParser.php | 37 + app/Http/Api/Scope/TypeScope.php | 45 + app/Http/Api/Sort/RandomSort.php | 35 + app/Http/Api/Sort/Sort.php | 57 + .../Api/Admin/AnnouncementController.php | 124 + .../Api/Billing/BalanceController.php | 124 + .../Api/Billing/TransactionController.php | 124 + .../Api/Config/FlagsController.php | 31 + .../Controllers/Api/Config/WikiController.php | 31 + .../Api/Document/PageController.php | 126 + .../Api/Wiki/Anime/SynonymController.php | 129 + .../Api/Wiki/Anime/Theme/EntryController.php | 129 + .../Api/Wiki/Anime/ThemeController.php | 129 + .../Api/Wiki/Anime/YearController.php | 76 + .../Controllers/Api/Wiki/AnimeController.php | 129 + .../Controllers/Api/Wiki/ArtistController.php | 129 + .../Api/Wiki/ExternalResourceController.php | 124 + .../Controllers/Api/Wiki/ImageController.php | 124 + .../Controllers/Api/Wiki/SearchController.php | 31 + .../Controllers/Api/Wiki/SeriesController.php | 129 + .../Controllers/Api/Wiki/SongController.php | 129 + .../Controllers/Api/Wiki/StudioController.php | 129 + .../Controllers/Api/Wiki/VideoController.php | 129 + .../Controllers/Auth/RegisterController.php | 144 + .../Billing/TransparencyController.php | 34 + app/Http/Controllers/Controller.php | 9 +- app/Http/Controllers/SitemapController.php | 26 - app/Http/Controllers/VideosController.php | 45 - app/Http/Controllers/WelcomeController.php | 25 - app/Http/Controllers/Wiki/VideoController.php | 63 + app/Http/Kernel.php | 101 +- app/Http/Middleware/Authenticate.php | 14 +- .../Middleware/CheckForMaintenanceMode.php | 17 - app/Http/Middleware/EncryptCookies.php | 17 - app/Http/Middleware/HasOpenInvitation.php | 44 + .../Middleware/IsVideoStreamingAllowed.php | 36 + app/Http/Middleware/LogRequest.php | 41 + app/Http/Middleware/LoggerMiddleware.php | 28 - app/Http/Middleware/RecordView.php | 40 + .../Middleware/RedirectIfAuthenticated.php | 26 +- app/Http/Middleware/SetAcceptJsonHeader.php | 28 + .../ThrottleRequestsWithService.php | 52 + app/Http/Middleware/TrimStrings.php | 8 +- app/Http/Middleware/TrustHosts.php | 25 + app/Http/Middleware/TrustProxies.php | 20 +- app/Http/Middleware/VerifyCsrfToken.php | 17 - app/Http/Middleware/WithoutTrashed.php | 39 + .../Api/Admin/AnnouncementDestroyRequest.php | 37 + .../Admin/AnnouncementForceDeleteRequest.php | 37 + .../Api/Admin/AnnouncementIndexRequest.php | 37 + .../Api/Admin/AnnouncementRestoreRequest.php | 37 + .../Api/Admin/AnnouncementShowRequest.php | 37 + .../Api/Admin/AnnouncementStoreRequest.php | 37 + .../Api/Admin/AnnouncementUpdateRequest.php | 37 + .../Api/Base/EloquentDestroyRequest.php | 39 + .../Api/Base/EloquentForceDeleteRequest.php | 39 + .../Api/Base/EloquentIndexRequest.php | 29 + .../Api/Base/EloquentRestoreRequest.php | 39 + .../Requests/Api/Base/EloquentShowRequest.php | 29 + .../Api/Base/EloquentStoreRequest.php | 39 + .../Api/Base/EloquentUpdateRequest.php | 39 + app/Http/Requests/Api/BaseRequest.php | 43 + .../Billing/Balance/BalanceDestroyRequest.php | 37 + .../Balance/BalanceForceDeleteRequest.php | 37 + .../Billing/Balance/BalanceIndexRequest.php | 37 + .../Billing/Balance/BalanceRestoreRequest.php | 37 + .../Billing/Balance/BalanceShowRequest.php | 37 + .../Billing/Balance/BalanceStoreRequest.php | 37 + .../Billing/Balance/BalanceUpdateRequest.php | 37 + .../Transaction/TransactionDestroyRequest.php | 37 + .../TransactionForceDeleteRequest.php | 37 + .../Transaction/TransactionIndexRequest.php | 37 + .../Transaction/TransactionRestoreRequest.php | 37 + .../Transaction/TransactionShowRequest.php | 37 + .../Transaction/TransactionStoreRequest.php | 37 + .../Transaction/TransactionUpdateRequest.php | 37 + app/Http/Requests/Api/Config/FlagsRequest.php | 37 + app/Http/Requests/Api/Config/WikiRequest.php | 37 + app/Http/Requests/Api/DestroyRequest.php | 31 + .../Api/Document/Page/PageDestroyRequest.php | 37 + .../Document/Page/PageForceDeleteRequest.php | 37 + .../Api/Document/Page/PageIndexRequest.php | 37 + .../Api/Document/Page/PageRestoreRequest.php | 37 + .../Api/Document/Page/PageShowRequest.php | 37 + .../Api/Document/Page/PageStoreRequest.php | 37 + .../Api/Document/Page/PageUpdateRequest.php | 37 + app/Http/Requests/Api/ForceDeleteRequest.php | 31 + app/Http/Requests/Api/IndexRequest.php | 176 + app/Http/Requests/Api/ReadRequest.php | 119 + app/Http/Requests/Api/RestoreRequest.php | 31 + app/Http/Requests/Api/ShowRequest.php | 119 + app/Http/Requests/Api/StoreRequest.php | 59 + app/Http/Requests/Api/UpdateRequest.php | 59 + .../Api/Wiki/Anime/AnimeDestroyRequest.php | 37 + .../Wiki/Anime/AnimeForceDeleteRequest.php | 37 + .../Api/Wiki/Anime/AnimeIndexRequest.php | 38 + .../Api/Wiki/Anime/AnimeRestoreRequest.php | 37 + .../Api/Wiki/Anime/AnimeShowRequest.php | 37 + .../Api/Wiki/Anime/AnimeStoreRequest.php | 37 + .../Api/Wiki/Anime/AnimeUpdateRequest.php | 37 + .../Anime/Synonym/SynonymDestroyRequest.php | 37 + .../Synonym/SynonymForceDeleteRequest.php | 37 + .../Anime/Synonym/SynonymIndexRequest.php | 38 + .../Anime/Synonym/SynonymRestoreRequest.php | 37 + .../Wiki/Anime/Synonym/SynonymShowRequest.php | 37 + .../Anime/Synonym/SynonymStoreRequest.php | 37 + .../Anime/Synonym/SynonymUpdateRequest.php | 37 + .../Anime/Theme/Entry/EntryDestroyRequest.php | 37 + .../Theme/Entry/EntryForceDeleteRequest.php | 37 + .../Anime/Theme/Entry/EntryIndexRequest.php | 38 + .../Anime/Theme/Entry/EntryRestoreRequest.php | 37 + .../Anime/Theme/Entry/EntryShowRequest.php | 37 + .../Anime/Theme/Entry/EntryStoreRequest.php | 37 + .../Anime/Theme/Entry/EntryUpdateRequest.php | 37 + .../Wiki/Anime/Theme/ThemeDestroyRequest.php | 37 + .../Anime/Theme/ThemeForceDeleteRequest.php | 37 + .../Wiki/Anime/Theme/ThemeIndexRequest.php | 38 + .../Wiki/Anime/Theme/ThemeRestoreRequest.php | 37 + .../Api/Wiki/Anime/Theme/ThemeShowRequest.php | 37 + .../Wiki/Anime/Theme/ThemeStoreRequest.php | 37 + .../Wiki/Anime/Theme/ThemeUpdateRequest.php | 37 + .../Api/Wiki/Anime/YearIndexRequest.php | 107 + .../Api/Wiki/Anime/YearShowRequest.php | 37 + .../Api/Wiki/Artist/ArtistDestroyRequest.php | 37 + .../Wiki/Artist/ArtistForceDeleteRequest.php | 37 + .../Api/Wiki/Artist/ArtistIndexRequest.php | 38 + .../Api/Wiki/Artist/ArtistRestoreRequest.php | 37 + .../Api/Wiki/Artist/ArtistShowRequest.php | 37 + .../Api/Wiki/Artist/ArtistStoreRequest.php | 37 + .../Api/Wiki/Artist/ArtistUpdateRequest.php | 37 + .../ExternalResourceDestroyRequest.php | 37 + .../ExternalResourceForceDeleteRequest.php | 37 + .../ExternalResourceIndexRequest.php | 37 + .../ExternalResourceRestoreRequest.php | 37 + .../ExternalResourceShowRequest.php | 37 + .../ExternalResourceStoreRequest.php | 37 + .../ExternalResourceUpdateRequest.php | 37 + .../Api/Wiki/Image/ImageDestroyRequest.php | 37 + .../Wiki/Image/ImageForceDeleteRequest.php | 37 + .../Api/Wiki/Image/ImageIndexRequest.php | 37 + .../Api/Wiki/Image/ImageRestoreRequest.php | 37 + .../Api/Wiki/Image/ImageShowRequest.php | 37 + .../Api/Wiki/Image/ImageStoreRequest.php | 37 + .../Api/Wiki/Image/ImageUpdateRequest.php | 37 + app/Http/Requests/Api/Wiki/SearchRequest.php | 226 + .../Api/Wiki/Series/SeriesDestroyRequest.php | 37 + .../Wiki/Series/SeriesForceDeleteRequest.php | 37 + .../Api/Wiki/Series/SeriesIndexRequest.php | 38 + .../Api/Wiki/Series/SeriesRestoreRequest.php | 37 + .../Api/Wiki/Series/SeriesShowRequest.php | 37 + .../Api/Wiki/Series/SeriesStoreRequest.php | 37 + .../Api/Wiki/Series/SeriesUpdateRequest.php | 37 + .../Api/Wiki/Song/SongDestroyRequest.php | 37 + .../Api/Wiki/Song/SongForceDeleteRequest.php | 37 + .../Api/Wiki/Song/SongIndexRequest.php | 38 + .../Api/Wiki/Song/SongRestoreRequest.php | 37 + .../Api/Wiki/Song/SongShowRequest.php | 37 + .../Api/Wiki/Song/SongStoreRequest.php | 37 + .../Api/Wiki/Song/SongUpdateRequest.php | 37 + .../Api/Wiki/Studio/StudioDestroyRequest.php | 37 + .../Wiki/Studio/StudioForceDeleteRequest.php | 37 + .../Api/Wiki/Studio/StudioIndexRequest.php | 38 + .../Api/Wiki/Studio/StudioRestoreRequest.php | 37 + .../Api/Wiki/Studio/StudioShowRequest.php | 37 + .../Api/Wiki/Studio/StudioStoreRequest.php | 37 + .../Api/Wiki/Studio/StudioUpdateRequest.php | 37 + .../Api/Wiki/Video/VideoDestroyRequest.php | 37 + .../Wiki/Video/VideoForceDeleteRequest.php | 37 + .../Api/Wiki/Video/VideoIndexRequest.php | 38 + .../Api/Wiki/Video/VideoRestoreRequest.php | 37 + .../Api/Wiki/Video/VideoShowRequest.php | 37 + .../Api/Wiki/Video/VideoStoreRequest.php | 37 + .../Api/Wiki/Video/VideoUpdateRequest.php | 37 + app/Http/Requests/Api/WriteRequest.php | 108 + .../Requests/Billing/TransparencyRequest.php | 167 + .../Collection/AnnouncementCollection.php | 38 + .../Admin/Resource/AnnouncementResource.php | 74 + app/Http/Resources/BaseCollection.php | 26 + app/Http/Resources/BaseResource.php | 41 + .../Billing/Collection/BalanceCollection.php | 36 + .../Collection/TransactionCollection.php | 38 + .../Billing/Resource/BalanceResource.php | 92 + .../Billing/Resource/TransactionResource.php | 90 + .../Config/Resource/FlagsResource.php | 62 + .../Config/Resource/WikiResource.php | 70 + .../Document/Collection/PageCollection.php | 38 + .../Document/Resource/PageResource.php | 82 + .../Resource/AnimeThemeEntryVideoResource.php | 67 + .../Anime/Collection/SynonymCollection.php | 38 + .../Wiki/Anime/Collection/ThemeCollection.php | 36 + .../Wiki/Anime/Resource/SynonymResource.php | 77 + .../Wiki/Anime/Resource/ThemeResource.php | 93 + .../Theme/Collection/EntryCollection.php | 36 + .../Anime/Theme/Resource/EntryResource.php | 95 + .../Wiki/Collection/AnimeCollection.php | 36 + .../Wiki/Collection/ArtistCollection.php | 36 + .../Collection/ExternalResourceCollection.php | 38 + .../Wiki/Collection/ImageCollection.php | 36 + .../Wiki/Collection/SeriesCollection.php | 36 + .../Wiki/Collection/SongCollection.php | 36 + .../Wiki/Collection/StudioCollection.php | 36 + .../Wiki/Collection/VideoCollection.php | 36 + .../Resources/Wiki/Resource/AnimeResource.php | 109 + .../Wiki/Resource/ArtistResource.php | 106 + .../Resource/ExternalResourceResource.php | 107 + .../Resources/Wiki/Resource/ImageResource.php | 90 + .../Wiki/Resource/SearchResource.php | 127 + .../Wiki/Resource/SeriesResource.php | 81 + .../Resources/Wiki/Resource/SongResource.php | 84 + .../Wiki/Resource/StudioResource.php | 88 + .../Resources/Wiki/Resource/VideoResource.php | 131 + app/Jobs/Middleware/RateLimited.php | 48 + app/Jobs/SendDiscordNotificationJob.php | 71 + .../Auth/Invitation/SendInvitationMail.php | 32 + app/Listeners/Auth/LogAttempting.php | 30 + app/Listeners/Auth/LogCurrentDeviceLogout.php | 28 + app/Listeners/Auth/LogFailed.php | 30 + app/Listeners/Auth/LogLockout.php | 27 + app/Listeners/Auth/LogLogin.php | 29 + app/Listeners/Auth/LogLogout.php | 28 + app/Listeners/Auth/LogOtherDeviceLogout.php | 28 + app/Listeners/Auth/LogPasswordReset.php | 27 + app/Listeners/Auth/LogRegistered.php | 27 + app/Listeners/CascadesDeletes.php | 24 + app/Listeners/CascadesRestores.php | 24 + app/Listeners/NotifiesNovaUsers.php | 33 + app/Listeners/SendDiscordNotification.php | 32 + app/Listeners/UpdateRelatedIndices.php | 28 + .../Wiki/Anime/Theme/CreateThemeSlug.php | 30 + .../Wiki/Image/RemoveImageFromStorage.php | 33 + .../Wiki/Video/InitializeVideoTags.php | 68 + app/Mail/InvitationMail.php | 48 + app/Models/Admin/Announcement.php | 78 + app/Models/Announcement.php | 11 - app/Models/Auth/Invitation.php | 105 + app/Models/Auth/Permission.php | 34 + app/Models/Auth/Role.php | 34 + app/Models/Auth/User.php | 168 + app/Models/BaseModel.php | 97 + app/Models/Billing/Balance.php | 112 + app/Models/Billing/Transaction.php | 109 + app/Models/Document/Page.php | 105 + app/Models/User.php | 39 - app/Models/Video.php | 10 - app/Models/Wiki/Anime.php | 240 + app/Models/Wiki/Anime/AnimeSynonym.php | 99 + app/Models/Wiki/Anime/AnimeTheme.php | 188 + .../Wiki/Anime/Theme/AnimeThemeEntry.php | 209 + app/Models/Wiki/Artist.php | 205 + app/Models/Wiki/ExternalResource.php | 148 + app/Models/Wiki/Image.php | 129 + app/Models/Wiki/Series.php | 113 + app/Models/Wiki/Song.php | 123 + app/Models/Wiki/Studio.php | 130 + app/Models/Wiki/Video.php | 241 + app/Notifications/DiscordNotification.php | 70 + .../Invitation/ResendInvitationAction.php | 66 + .../Auth/Permission/GiveRoleAction.php | 75 + .../Auth/Permission/RevokeRoleAction.php | 75 + .../Auth/Role/GivePermissionAction.php | 75 + .../Auth/Role/RevokePermissionAction.php | 75 + .../Auth/User/GivePermissionAction.php | 75 + app/Nova/Actions/Auth/User/GiveRoleAction.php | 75 + .../Auth/User/RevokePermissionAction.php | 75 + .../Actions/Auth/User/RevokeRoleAction.php | 75 + .../Wiki/Anime/BackfillAnimeAction.php | 202 + ...eateExternalResourceSiteForAnimeAction.php | 86 + ...ateExternalResourceSiteForArtistAction.php | 86 + ...ateExternalResourceSiteForStudioAction.php | 86 + app/Nova/Dashboards/Main.php | 46 + .../Lenses/Anime/AnimeAniDbResourceLens.php | 117 + .../Lenses/Anime/AnimeAnilistResourceLens.php | 117 + .../Lenses/Anime/AnimeAnnResourceLens.php | 117 + app/Nova/Lenses/Anime/AnimeCoverLargeLens.php | 109 + app/Nova/Lenses/Anime/AnimeCoverSmallLens.php | 109 + .../Lenses/Anime/AnimeKitsuResourceLens.php | 117 + .../Lenses/Anime/AnimeMalResourceLens.php | 117 + .../Lenses/Anime/AnimePlanetResourceLens.php | 118 + app/Nova/Lenses/Anime/AnimeStudioLens.php | 105 + .../Lenses/Artist/ArtistAniDbResourceLens.php | 104 + .../Artist/ArtistAnilistResourceLens.php | 104 + .../Lenses/Artist/ArtistAnnResourceLens.php | 104 + .../Lenses/Artist/ArtistCoverLargeLens.php | 97 + .../Lenses/Artist/ArtistCoverSmallLens.php | 97 + .../Lenses/Artist/ArtistMalResourceLens.php | 104 + app/Nova/Lenses/Artist/ArtistSongLens.php | 92 + app/Nova/Lenses/BaseLens.php | 34 + .../ExternalResourceUnlinkedLens.php | 95 + app/Nova/Lenses/Image/ImageUnlinkedLens.php | 97 + app/Nova/Lenses/Song/SongArtistLens.php | 88 + .../Lenses/Studio/StudioAniDbResourceLens.php | 104 + .../Studio/StudioAnilistResourceLens.php | 104 + .../Studio/StudioAnimePlanetResourceLens.php | 105 + .../Lenses/Studio/StudioAnnResourceLens.php | 104 + .../Lenses/Studio/StudioMalResourceLens.php | 104 + app/Nova/Lenses/Studio/StudioUnlinkedLens.php | 92 + app/Nova/Lenses/Video/VideoResolutionLens.php | 105 + app/Nova/Lenses/Video/VideoSourceLens.php | 110 + app/Nova/Lenses/Video/VideoUnlinkedLens.php | 110 + app/Nova/Metrics/Anime/AnimePerDay.php | 69 + app/Nova/Metrics/Anime/NewAnime.php | 74 + app/Nova/Metrics/Artist/ArtistsPerDay.php | 69 + app/Nova/Metrics/Artist/NewArtists.php | 74 + app/Nova/Metrics/Series/NewSeries.php | 74 + app/Nova/Metrics/Series/SeriesPerDay.php | 69 + app/Nova/Metrics/Video/NewVideos.php | 74 + app/Nova/Metrics/Video/VideosPerDay.php | 69 + app/Nova/Resources/Admin/Announcement.php | 149 + app/Nova/Resources/Auth/Invitation.php | 183 + app/Nova/Resources/Auth/Permission.php | 165 + app/Nova/Resources/Auth/Role.php | 163 + app/Nova/Resources/Auth/User.php | 207 + app/Nova/Resources/Billing/Balance.php | 165 + app/Nova/Resources/Billing/Transaction.php | 169 + app/Nova/Resources/Document/Page.php | 157 + app/Nova/Resources/Resource.php | 95 + app/Nova/Resources/Wiki/Anime.php | 334 + app/Nova/Resources/Wiki/Anime/Synonym.php | 149 + app/Nova/Resources/Wiki/Anime/Theme.php | 227 + app/Nova/Resources/Wiki/Anime/Theme/Entry.php | 253 + app/Nova/Resources/Wiki/Artist.php | 290 + app/Nova/Resources/Wiki/ExternalResource.php | 273 + app/Nova/Resources/Wiki/Image.php | 214 + app/Nova/Resources/Wiki/Series.php | 176 + app/Nova/Resources/Wiki/Song.php | 211 + app/Nova/Resources/Wiki/Studio.php | 204 + app/Nova/Resources/Wiki/Video.php | 282 + app/Pipes/Wiki/Anime/BackfillAnimeImage.php | 113 + app/Pipes/Wiki/Anime/BackfillAnimePipe.php | 46 + .../Wiki/Anime/BackfillAnimeResource.php | 114 + .../Anime/Image/BackfillLargeCoverImage.php | 89 + .../Anime/Image/BackfillSmallCoverImage.php | 89 + .../Anime/Resource/BackfillAnidbResource.php | 97 + .../Resource/BackfillAnilistResource.php | 178 + .../Anime/Resource/BackfillAnnResource.php | 95 + .../Anime/Resource/BackfillKitsuResource.php | 131 + .../Anime/Resource/BackfillMalResource.php | 176 + .../Anime/Studio/BackfillAnimeStudios.php | 380 + app/Pivots/AnimeImage.php | 67 + app/Pivots/AnimeResource.php | 80 + app/Pivots/AnimeSeries.php | 67 + app/Pivots/AnimeStudio.php | 67 + app/Pivots/AnimeThemeEntryVideo.php | 76 + app/Pivots/ArtistImage.php | 67 + app/Pivots/ArtistMember.php | 79 + app/Pivots/ArtistResource.php | 80 + app/Pivots/ArtistSong.php | 80 + app/Pivots/BasePivot.php | 31 + app/Pivots/StudioResource.php | 80 + app/Policies/Admin/AnnouncementPolicy.php | 93 + app/Policies/Auth/InvitationPolicy.php | 93 + app/Policies/Auth/PermissionPolicy.php | 153 + app/Policies/Auth/RolePolicy.php | 153 + app/Policies/Auth/UserPolicy.php | 153 + app/Policies/Billing/BalancePolicy.php | 93 + app/Policies/Billing/TransactionPolicy.php | 93 + app/Policies/Document/PagePolicy.php | 93 + .../Wiki/Anime/AnimeSynonymPolicy.php | 93 + app/Policies/Wiki/Anime/AnimeThemePolicy.php | 93 + .../Anime/Theme/AnimeThemeEntryPolicy.php | 136 + app/Policies/Wiki/AnimePolicy.php | 253 + app/Policies/Wiki/ArtistPolicy.php | 235 + app/Policies/Wiki/ExternalResourcePolicy.php | 192 + app/Policies/Wiki/ImagePolicy.php | 178 + app/Policies/Wiki/SeriesPolicy.php | 136 + app/Policies/Wiki/SongPolicy.php | 137 + app/Policies/Wiki/StudioPolicy.php | 169 + app/Policies/Wiki/VideoPolicy.php | 126 + app/Providers/AppServiceProvider.php | 29 +- app/Providers/AuthServiceProvider.php | 38 +- app/Providers/BroadcastServiceProvider.php | 7 +- app/Providers/EventServiceProvider.php | 29 +- app/Providers/FortifyServiceProvider.php | 38 + app/Providers/HorizonServiceProvider.php | 45 + app/Providers/JetstreamServiceProvider.php | 27 + app/Providers/NovaServiceProvider.php | 96 + app/Providers/RouteServiceProvider.php | 101 +- app/Providers/TelescopeServiceProvider.php | 77 + .../Billing/DigitalOceanBalanceRepository.php | 58 + .../DigitalOceanTransactionRepository.php | 51 + .../Eloquent/EloquentRepository.php | 83 + .../Eloquent/Wiki/VideoRepository.php | 57 + .../Billing/DigitalOceanBalanceRepository.php | 120 + .../DigitalOceanTransactionRepository.php | 136 + .../Service/DigitalOcean/VideoRepository.php | 149 + .../Api/DistinctIgnoringDirectionRule.php | 46 + app/Rules/Api/EnumDescriptionRule.php | 46 + app/Rules/Api/IsValidBoolean.php | 35 + app/Rules/Api/RandomSoleRule.php | 39 + app/Rules/Billing/TransparencyDateRule.php | 50 + .../Wiki/ResourceLinkMatchesSiteRule.php | 48 + .../Wiki/ResourceSiteMatchesLinkRule.php | 48 + .../Api/Criteria/Filter/Criteria.php | 35 + .../Api/Criteria/Filter/WhereCriteria.php | 103 + .../Api/Criteria/Filter/WhereInCriteria.php | 60 + .../Api/Criteria/Paging/Criteria.php | 44 + .../Api/Criteria/Paging/LimitCriteria.php | 40 + .../Api/Criteria/Paging/OffsetCriteria.php | 57 + .../Api/Criteria/Sort/Criteria.php | 31 + .../Api/Criteria/Sort/FieldCriteria.php | 39 + .../Api/Criteria/Sort/RelationCriteria.php | 34 + .../Api/Field/Base/CreatedAtField.php | 22 + .../Api/Field/Base/DeletedAtField.php | 22 + .../Elasticsearch/Api/Field/Base/IdField.php | 24 + .../Api/Field/Base/UpdatedAtField.php | 22 + .../Elasticsearch/Api/Field/BooleanField.php | 37 + .../Elasticsearch/Api/Field/DateField.php | 37 + .../Elasticsearch/Api/Field/EnumField.php | 53 + app/Scout/Elasticsearch/Api/Field/Field.php | 57 + .../Elasticsearch/Api/Field/FloatField.php | 37 + .../Elasticsearch/Api/Field/IntField.php | 37 + .../Elasticsearch/Api/Field/StringField.php | 60 + .../Api/Field/Wiki/Anime/AnimeNameField.php | 22 + .../Api/Field/Wiki/Anime/AnimeSeasonField.php | 23 + .../Api/Field/Wiki/Anime/AnimeSlugField.php | 22 + .../Field/Wiki/Anime/AnimeSynopsisField.php | 22 + .../Api/Field/Wiki/Anime/AnimeYearField.php | 22 + .../Wiki/Anime/Synonym/SynonymTextField.php | 22 + .../Anime/Theme/Entry/EntryEpisodesField.php | 22 + .../Anime/Theme/Entry/EntryNotesField.php | 22 + .../Wiki/Anime/Theme/Entry/EntryNsfwField.php | 22 + .../Anime/Theme/Entry/EntrySpoilerField.php | 22 + .../Anime/Theme/Entry/EntryVersionField.php | 22 + .../Wiki/Anime/Theme/ThemeGroupField.php | 22 + .../Wiki/Anime/Theme/ThemeSequenceField.php | 22 + .../Field/Wiki/Anime/Theme/ThemeSlugField.php | 22 + .../Field/Wiki/Anime/Theme/ThemeTypeField.php | 23 + .../Api/Field/Wiki/Artist/ArtistNameField.php | 22 + .../Api/Field/Wiki/Artist/ArtistSlugField.php | 22 + .../Api/Field/Wiki/Series/SeriesNameField.php | 22 + .../Api/Field/Wiki/Series/SeriesSlugField.php | 22 + .../Api/Field/Wiki/Song/SongTitleField.php | 22 + .../Api/Field/Wiki/Studio/StudioNameField.php | 22 + .../Api/Field/Wiki/Studio/StudioSlugField.php | 22 + .../Field/Wiki/Video/VideoBasenameField.php | 22 + .../Field/Wiki/Video/VideoFilenameField.php | 22 + .../Api/Field/Wiki/Video/VideoLyricsField.php | 22 + .../Field/Wiki/Video/VideoMimeTypeField.php | 22 + .../Api/Field/Wiki/Video/VideoNcField.php | 22 + .../Field/Wiki/Video/VideoOverlapField.php | 23 + .../Api/Field/Wiki/Video/VideoPathField.php | 22 + .../Field/Wiki/Video/VideoResolutionField.php | 22 + .../Api/Field/Wiki/Video/VideoSizeField.php | 22 + .../Api/Field/Wiki/Video/VideoSourceField.php | 23 + .../Api/Field/Wiki/Video/VideoSubbedField.php | 22 + .../Api/Field/Wiki/Video/VideoUncenField.php | 22 + .../Elasticsearch/Api/Parser/FilterParser.php | 37 + .../Elasticsearch/Api/Parser/PagingParser.php | 37 + .../Elasticsearch/Api/Parser/SortParser.php | 37 + .../Api/Query/Anime/SynonymQueryPayload.php | 72 + .../Query/Anime/Theme/EntryQueryPayload.php | 271 + .../Api/Query/Anime/ThemeQueryPayload.php | 216 + .../Api/Query/AnimeQueryPayload.php | 152 + .../Api/Query/ArtistQueryPayload.php | 115 + .../Api/Query/ElasticQueryPayload.php | 45 + .../Api/Query/SeriesQueryPayload.php | 71 + .../Api/Query/SongQueryPayload.php | 71 + .../Api/Query/StudioQueryPayload.php | 71 + .../Api/Query/VideoQueryPayload.php | 344 + app/Scout/Elasticsearch/Api/Schema/Schema.php | 76 + .../Api/Schema/Wiki/Anime/SynonymSchema.php | 68 + .../Schema/Wiki/Anime/Theme/EntrySchema.php | 80 + .../Api/Schema/Wiki/Anime/ThemeSchema.php | 110 + .../Api/Schema/Wiki/AnimeSchema.php | 89 + .../Api/Schema/Wiki/ArtistSchema.php | 78 + .../Api/Schema/Wiki/SeriesSchema.php | 69 + .../Api/Schema/Wiki/SongSchema.php | 70 + .../Api/Schema/Wiki/StudioSchema.php | 71 + .../Api/Schema/Wiki/VideoSchema.php | 93 + .../DiscoverElasticQueryPayload.php | 95 + app/Scout/Elasticsearch/Elasticsearch.php | 161 + app/Scout/Search.php | 35 + app/View/Components/AppLayout.php | 26 + app/View/Components/GuestLayout.php | 26 + artisan | 2 +- composer.json | 103 +- composer.lock | 8265 ++++++++++--- config/api.php | 24 + config/app.php | 127 +- config/audit.php | 171 + config/auth.php | 8 +- config/broadcasting.php | 12 +- config/cache.php | 22 +- config/cors.php | 8 +- config/database.php | 46 +- config/elastic.client.php | 15 + config/elastic.migrations.php | 9 + config/elastic.scout_driver.php | 7 + config/eloquent-viewable.php | 109 + config/filesystems.php | 65 +- config/flags.php | 46 + config/fortify.php | 150 + config/hashing.php | 8 +- config/horizon.php | 203 + config/jetstream.php | 67 + config/logging.php | 36 +- config/mail.php | 16 +- config/nova.php | 205 + config/permission.php | 166 + config/queue.php | 8 +- config/route-discovery.php | 32 + config/sanctum.php | 70 + config/scout.php | 121 + config/secure-headers.php | 663 + config/services.php | 16 + config/session.php | 22 +- config/telescope.php | 178 + config/video.php | 24 + config/view.php | 2 + config/wiki.php | 23 + .../factories/Admin/AnnouncementFactory.php | 38 + database/factories/Auth/InvitationFactory.php | 41 + database/factories/Auth/UserFactory.php | 65 + database/factories/Billing/BalanceFactory.php | 44 + .../factories/Billing/TransactionFactory.php | 43 + database/factories/Document/PageFactory.php | 41 + .../factories/Pivots/AnimeImageFactory.php | 38 + .../factories/Pivots/AnimeResourceFactory.php | 39 + .../factories/Pivots/AnimeSeriesFactory.php | 38 + .../factories/Pivots/AnimeStudioFactory.php | 38 + .../Pivots/AnimeThemeEntryVideoFactory.php | 38 + .../factories/Pivots/ArtistImageFactory.php | 38 + .../factories/Pivots/ArtistMemberFactory.php | 39 + .../Pivots/ArtistResourceFactory.php | 39 + .../factories/Pivots/ArtistSongFactory.php | 39 + .../Pivots/StudioResourceFactory.php | 39 + database/factories/UserFactory.php | 28 - .../Wiki/Anime/AnimeSynonymFactory.php | 38 + .../Wiki/Anime/AnimeThemeFactory.php | 41 + .../Anime/Theme/AnimeThemeEntryFactory.php | 42 + database/factories/Wiki/AnimeFactory.php | 95 + database/factories/Wiki/ArtistFactory.php | 83 + .../Wiki/ExternalResourceFactory.php | 41 + database/factories/Wiki/ImageFactory.php | 41 + database/factories/Wiki/SeriesFactory.php | 40 + database/factories/Wiki/SongFactory.php | 38 + database/factories/Wiki/StudioFactory.php | 40 + database/factories/Wiki/VideoFactory.php | 52 + .../2014_10_12_000000_create_users_table.php | 34 +- ...12_100000_create_password_resets_table.php | 36 + ..._add_two_factor_columns_to_users_table.php | 54 + .../2017_11_26_225409_create_videos_table.php | 40 +- .../2017_12_26_221942_create_natural_sort.php | 106 - ...28_023041_add_basename_to_videos_table.php | 34 - ..._08_19_000000_create_failed_jobs_table.php | 29 +- ...01_create_personal_access_tokens_table.php | 40 + ...4_12_212732_create_announcements_table.php | 27 +- .../2020_05_05_044925_create_anime_table.php | 43 + .../2020_05_05_044939_create_series_table.php | 40 + .../2020_05_05_044953_create_songs_table.php | 39 + ...2020_05_05_045006_create_artists_table.php | 40 + ...20_05_05_045017_create_resources_table.php | 41 + ...05_05_045028_create_anime_themes_table.php | 50 + ...45037_create_anime_theme_entries_table.php | 47 + ..._05_051400_create_anime_synonyms_table.php | 43 + .../2020_05_05_054036_create_anime_series.php | 42 + ..._055804_create_anime_theme_entry_video.php | 42 + ...020_05_05_060307_create_anime_resource.php | 43 + .../2020_05_05_062655_create_artist_song.php | 43 + ...20_05_05_063144_create_artist_resource.php | 43 + .../2020_05_06_024632_create_audits_table.php | 47 + ..._05_24_220046_create_invitations_table.php | 42 + ...2020_06_10_050701_create_artist_member.php | 42 + ...0_09_09_195220_add_uuid_to_failed_jobs.php | 32 - ...020_09_16_183751_create_sessions_table.php | 39 + ...2020_11_05_220613_create_configs_table.php | 35 + ..._11_13_204613_add_size_to_videos_table.php | 32 - .../2020_11_24_231534_create_image_table.php | 40 + .../2020_11_24_232439_create_anime_image.php | 42 + .../2020_11_24_232525_create_artist_image.php | 42 + .../2020_12_08_034959_create_views_table.php | 67 + ..._022128_update_telescope_entries_table.php | 40 + ...05_13_192052_create_transactions_table.php | 43 + ...021_05_13_194800_create_balances_table.php | 43 + ...2021_08_20_185746_create_studios_table.php | 40 + .../2021_08_20_213018_create_anime_studio.php | 42 + ...21_11_03_003056_create_studio_resource.php | 43 + .../2022_02_14_175203_create_page_table.php | 41 + .../2022_04_09_234337_create_jobs_table.php | 40 + ..._05_09_032230_create_permission_tables.php | 154 + .../2022_05_12_165551_create_cache_table.php | 45 + database/seeders/AniDbResourceSeeder.php | 97 + .../seeders/AnilistAnimeResourceSeeder.php | 109 + .../seeders/AnilistArtistResourceSeeder.php | 106 + database/seeders/ArtistCoverSeeder.php | 126 + database/seeders/DatabaseSeeder.php | 30 + .../seeders/DigitalOceanTransactionSeeder.php | 132 + database/seeders/KitsuResourceSeeder.php | 106 + database/seeders/PermissionSeeder.php | 173 + database/seeders/StudioSeeder.php | 128 + database/seeders/SynopsisCoverSeeder.php | 135 + database/seeders/VideoSeeder.php | 132 + database/seeds/DatabaseSeeder.php | 11 - database/seeds/VideosTableSeeder.php | 33 - .../2020_12_21_225415_create_anime_index.php | 75 + .../2020_12_22_033019_create_artist_index.php | 77 + .../2020_12_22_034505_create_entry_index.php | 169 + .../2020_12_22_034544_create_series_index.php | 47 + .../2020_12_22_034549_create_song_index.php | 40 + ...2020_12_22_034553_create_synonym_index.php | 69 + .../2020_12_22_034557_create_theme_index.php | 126 + .../2020_12_22_034601_create_video_index.php | 223 + .../2021_08_20_213637_create_studio_index.php | 47 + package-lock.json | 10076 ++++++++-------- package.json | 20 +- phpstan.neon | 25 + phpunit.xml | 32 +- public/css/pomf.css | 406 - public/favicon.ico | Bin 0 -> 15406 bytes public/img/bg.png | Bin 19452 -> 0 bytes public/img/logo.svg | 1 + public/index.php | 57 +- public/js/pomf.js | 5 - public/manifest.js | 150 - public/mix-manifest.json | 3 +- public/robots.txt | 1 - public/vendor/horizon/app-dark.css | 8 + public/vendor/horizon/app.css | 8 + public/vendor/horizon/app.js | 2 + public/vendor/horizon/img/favicon.png | Bin 0 -> 648 bytes public/vendor/horizon/img/horizon.svg | 4 + public/vendor/horizon/img/sprite.svg | 806 ++ public/vendor/horizon/mix-manifest.json | 6 + public/vendor/nova/app.css | 3 + public/vendor/nova/app.css.map | 1 + public/vendor/nova/app.js | 2 + public/vendor/nova/app.js.map | 1 + ...mslybiv1o4x1m8cce4g1pty10iurt9w6fk2a.woff2 | Bin 0 -> 11768 bytes ...mslybiv1o4x1m8cce4g1pty14iurt9w6fk2a.woff2 | Bin 0 -> 8760 bytes ...mslybiv1o4x1m8cce4g1pty1ciurt9w6fk2a.woff2 | Bin 0 -> 6380 bytes ...1mimslybiv1o4x1m8cce4g1pty1miurt9w6c.woff2 | Bin 0 -> 12064 bytes ...mslybiv1o4x1m8cce4g1pty1wiurt9w6fk2a.woff2 | Bin 0 -> 4168 bytes ...mslybiv1o4x1m8cce4g35sy10iurt9w6fk2a.woff2 | Bin 0 -> 11704 bytes ...mslybiv1o4x1m8cce4g35sy14iurt9w6fk2a.woff2 | Bin 0 -> 8812 bytes ...mslybiv1o4x1m8cce4g35sy1ciurt9w6fk2a.woff2 | Bin 0 -> 6380 bytes ...1mimslybiv1o4x1m8cce4g35sy1miurt9w6c.woff2 | Bin 0 -> 12080 bytes ...mslybiv1o4x1m8cce4g35sy1wiurt9w6fk2a.woff2 | Bin 0 -> 4160 bytes ...mslybiv1o4x1m8cce4g3joy10iurt9w6fk2a.woff2 | Bin 0 -> 11492 bytes ...mslybiv1o4x1m8cce4g3joy14iurt9w6fk2a.woff2 | Bin 0 -> 8636 bytes ...mslybiv1o4x1m8cce4g3joy1ciurt9w6fk2a.woff2 | Bin 0 -> 6304 bytes ...1mimslybiv1o4x1m8cce4g3joy1miurt9w6c.woff2 | Bin 0 -> 11872 bytes ...mslybiv1o4x1m8cce4g3joy1wiurt9w6fk2a.woff2 | Bin 0 -> 4024 bytes ...mslybiv1o4x1m8cce4gwzuy10iurt9w6fk2a.woff2 | Bin 0 -> 11616 bytes ...mslybiv1o4x1m8cce4gwzuy14iurt9w6fk2a.woff2 | Bin 0 -> 8888 bytes ...mslybiv1o4x1m8cce4gwzuy1ciurt9w6fk2a.woff2 | Bin 0 -> 6348 bytes ...1mimslybiv1o4x1m8cce4gwzuy1miurt9w6c.woff2 | Bin 0 -> 12116 bytes ...mslybiv1o4x1m8cce4gwzuy1wiurt9w6fk2a.woff2 | Bin 0 -> 4116 bytes ...mslybiv1o4x1m8cce4gxzry10iurt9w6fk2a.woff2 | Bin 0 -> 11088 bytes ...mslybiv1o4x1m8cce4gxzry14iurt9w6fk2a.woff2 | Bin 0 -> 8336 bytes ...mslybiv1o4x1m8cce4gxzry1ciurt9w6fk2a.woff2 | Bin 0 -> 6100 bytes ...1mimslybiv1o4x1m8cce4gxzry1miurt9w6c.woff2 | Bin 0 -> 11580 bytes ...mslybiv1o4x1m8cce4gxzry1wiurt9w6fk2a.woff2 | Bin 0 -> 3808 bytes ...3mimslybiv1o4x1m8cc8-bm5gu1ecvzl-86y.woff2 | Bin 0 -> 8176 bytes ...3mimslybiv1o4x1m8cc8-bm5iu1ecvzl-86y.woff2 | Bin 0 -> 3900 bytes ...3mimslybiv1o4x1m8cc8-bm5ju1ecvzl-86y.woff2 | Bin 0 -> 11056 bytes ...3mimslybiv1o4x1m8cc8-bm5pu1ecvzl-86y.woff2 | Bin 0 -> 5972 bytes ...1pe03mimslybiv1o4x1m8cc8-bm5tu1ecvzl.woff2 | Bin 0 -> 11340 bytes ...3mimslybiv1o4x1m8cc8abc5gu1ecvzl-86y.woff2 | Bin 0 -> 8272 bytes ...3mimslybiv1o4x1m8cc8abc5iu1ecvzl-86y.woff2 | Bin 0 -> 3844 bytes ...3mimslybiv1o4x1m8cc8abc5ju1ecvzl-86y.woff2 | Bin 0 -> 11012 bytes ...3mimslybiv1o4x1m8cc8abc5pu1ecvzl-86y.woff2 | Bin 0 -> 5984 bytes ...1pe03mimslybiv1o4x1m8cc8abc5tu1ecvzl.woff2 | Bin 0 -> 11384 bytes ...3mimslybiv1o4x1m8cc8wac5gu1ecvzl-86y.woff2 | Bin 0 -> 8200 bytes ...3mimslybiv1o4x1m8cc8wac5iu1ecvzl-86y.woff2 | Bin 0 -> 3820 bytes ...3mimslybiv1o4x1m8cc8wac5ju1ecvzl-86y.woff2 | Bin 0 -> 10896 bytes ...3mimslybiv1o4x1m8cc8wac5pu1ecvzl-86y.woff2 | Bin 0 -> 5952 bytes ...1pe03mimslybiv1o4x1m8cc8wac5tu1ecvzl.woff2 | Bin 0 -> 11176 bytes ...3mimslybiv1o4x1m8cc9ib85gu1ecvzl-86y.woff2 | Bin 0 -> 8228 bytes ...3mimslybiv1o4x1m8cc9ib85iu1ecvzl-86y.woff2 | Bin 0 -> 3840 bytes ...3mimslybiv1o4x1m8cc9ib85ju1ecvzl-86y.woff2 | Bin 0 -> 10956 bytes ...3mimslybiv1o4x1m8cc9ib85pu1ecvzl-86y.woff2 | Bin 0 -> 5968 bytes ...1pe03mimslybiv1o4x1m8cc9ib85tu1ecvzl.woff2 | Bin 0 -> 11368 bytes ...3mimslybiv1o4x1m8cc9yas5gu1ecvzl-86y.woff2 | Bin 0 -> 7992 bytes ...3mimslybiv1o4x1m8cc9yas5iu1ecvzl-86y.woff2 | Bin 0 -> 3616 bytes ...3mimslybiv1o4x1m8cc9yas5ju1ecvzl-86y.woff2 | Bin 0 -> 10512 bytes ...3mimslybiv1o4x1m8cc9yas5pu1ecvzl-86y.woff2 | Bin 0 -> 5848 bytes ...1pe03mimslybiv1o4x1m8cc9yas5tu1ecvzl.woff2 | Bin 0 -> 11088 bytes ...0omimslybiv1o4x1m8cce4e91kdn4qx5fhyg.woff2 | Bin 0 -> 6344 bytes ...0omimslybiv1o4x1m8cce4e9dkdn4qx5fhyg.woff2 | Bin 0 -> 11572 bytes ...1pe0omimslybiv1o4x1m8cce4e9lkdn4qx5e.woff2 | Bin 0 -> 12020 bytes ...0omimslybiv1o4x1m8cce4e9rkdn4qx5fhyg.woff2 | Bin 0 -> 8768 bytes ...0omimslybiv1o4x1m8cce4e9zkdn4qx5fhyg.woff2 | Bin 0 -> 4068 bytes ...1pe0qmimslybiv1o4x1m8cce5i9tacvwob5a.woff2 | Bin 0 -> 5988 bytes ...sv11pe0qmimslybiv1o4x1m8cce9i9tacvwo.woff2 | Bin 0 -> 11380 bytes ...1pe0qmimslybiv1o4x1m8ccewi9tacvwob5a.woff2 | Bin 0 -> 8128 bytes ...1pe0qmimslybiv1o4x1m8cceyi9tacvwob5a.woff2 | Bin 0 -> 3816 bytes ...1pe0qmimslybiv1o4x1m8ccezi9tacvwob5a.woff2 | Bin 0 -> 10936 bytes public/vendor/nova/manifest.js | 2 + public/vendor/nova/manifest.js.map | 1 + public/vendor/nova/mix-manifest.json | 67 + public/vendor/nova/vendor.js | 3 + public/vendor/nova/vendor.js.LICENSE.txt | 48 + public/vendor/nova/vendor.js.map | 1 + public/vendor/telescope/app-dark.css | 7 + public/vendor/telescope/app.css | 7 + public/vendor/telescope/app.js | 2 + public/vendor/telescope/favicon.ico | Bin 0 -> 26622 bytes public/vendor/telescope/mix-manifest.json | 5 + resources/css/app.css | 3 + resources/js/app.js | 6 + resources/lang/en/auth.php | 2 + resources/lang/en/enums.php | 72 + resources/lang/en/nova.php | 235 + resources/lang/en/pagination.php | 2 + resources/lang/en/passwords.php | 2 + resources/lang/en/validation.php | 83 +- .../lang/vendor/exception/en/exceptions.php | 42 + .../vendor/exception/pt-br/exceptions.php | 42 + .../lang/vendor/laravelEnum/en/messages.php | 9 + resources/lang/vendor/nova/en.json | 441 + resources/lang/vendor/nova/en/validation.php | 21 + resources/markdown/policy.md | 38 + resources/markdown/terms.md | 125 + resources/sass/app.scss | 1 - .../views/api/api-token-manager.blade.php | 169 + resources/views/api/index.blade.php | 13 + .../views/auth/confirm-password.blade.php | 28 + .../views/auth/forgot-password.blade.php | 34 + resources/views/auth/login.blade.php | 48 + resources/views/auth/register.blade.php | 60 + resources/views/auth/reset-password.blade.php | 36 + .../views/auth/two-factor-challenge.blade.php | 57 + resources/views/auth/verify-email.blade.php | 45 + .../views/billing/transparency.blade.php | 159 + resources/views/dashboard.blade.php | 14 + resources/views/layouts/analytics.blade.php | 11 - .../views/layouts/announcements.blade.php | 3 - resources/views/layouts/app.blade.php | 58 +- resources/views/layouts/footer.blade.php | 33 +- resources/views/layouts/grill.blade.php | 3 - resources/views/layouts/guest.blade.php | 24 + resources/views/layouts/nav.blade.php | 12 - resources/views/layouts/webm.blade.php | 3 - resources/views/mail/invitation.blade.php | 9 + resources/views/navigation-menu.blade.php | 311 + resources/views/policy.blade.php | 13 + .../views/profile/delete-user-form.blade.php | 52 + ...gout-other-browser-sessions-form.blade.php | 97 + resources/views/profile/show.blade.php | 45 + .../two-factor-authentication-form.blade.php | 124 + .../profile/update-password-form.blade.php | 39 + .../update-profile-information-form.blade.php | 95 + resources/views/sitemap/index.blade.php | 8 - resources/views/sitemap/videos.blade.php | 9 - resources/views/terms.blade.php | 13 + .../components/application-logo.blade.php | 4 + .../components/application-mark.blade.php | 4 + .../authentication-card-logo.blade.php | 6 + resources/views/videos.blade.php | 19 - resources/views/welcome.blade.php | 20 - routes/api.php | 11 + routes/channels.php | 2 + routes/console.php | 2 + routes/video.php | 7 + routes/web.php | 15 +- server.php | 21 - storage/db-dumps/.gitignore | 2 + tailwind.config.js | 20 + tests/CreatesApplication.php | 10 +- .../Actions/Fortify/AuthenticationTest.php | 62 + .../Actions/Fortify/EmailVerificationTest.php | 96 + .../Fortify/PasswordConfirmationTest.php | 61 + .../Actions/Fortify/PasswordResetTest.php | 121 + .../Actions/Jetstream/BrowserSessionsTest.php | 30 + .../Actions/Jetstream/CreateApiTokenTest.php | 41 + .../Actions/Jetstream/DeleteAccountTest.php | 58 + .../Actions/Jetstream/DeleteApiTokenTest.php | 44 + .../Jetstream/ProfileInformationTest.php | 50 + .../TwoFactorAuthenticationSettingsTest.php | 80 + .../Actions/Jetstream/UpdatePasswordTest.php | 86 + .../Commands/Billing/BalanceReconcileTest.php | 140 + .../Billing/TransactionReconcileTest.php | 110 + .../Document/DocumentDatabaseDumpTest.php | 61 + .../Commands/Wiki/PruneDatabaseDumpsTest.php | 61 + .../Commands/Wiki/VideoReconcileTest.php | 103 + .../Commands/Wiki/WikiDatabaseDumpTest.php | 61 + .../Feature/Events/Admin/AnnouncementTest.php | 101 + tests/Feature/Events/Auth/InvitationTest.php | 101 + tests/Feature/Events/Auth/UserTest.php | 101 + tests/Feature/Events/Billing/BalanceTest.php | 101 + .../Events/Billing/TransactionTest.php | 101 + tests/Feature/Events/Document/PageTest.php | 101 + tests/Feature/Events/Pivot/AnimeImageTest.php | 53 + .../Events/Pivot/AnimeResourceTest.php | 83 + .../Feature/Events/Pivot/AnimeSeriesTest.php | 53 + .../Feature/Events/Pivot/AnimeStudioTest.php | 53 + .../Events/Pivot/AnimeThemeEntryVideoTest.php | 59 + .../Feature/Events/Pivot/ArtistImageTest.php | 53 + .../Feature/Events/Pivot/ArtistMemberTest.php | 82 + .../Events/Pivot/ArtistResourceTest.php | 83 + tests/Feature/Events/Pivot/ArtistSongTest.php | 83 + .../Events/Pivot/StudioResourceTest.php | 83 + .../Feature/Events/Wiki/Anime/SynonymTest.php | 115 + .../Events/Wiki/Anime/Theme/EntryTest.php | 117 + tests/Feature/Events/Wiki/Anime/ThemeTest.php | 116 + tests/Feature/Events/Wiki/AnimeTest.php | 101 + tests/Feature/Events/Wiki/ArtistTest.php | 101 + .../Events/Wiki/ExternalResourceTest.php | 101 + tests/Feature/Events/Wiki/ImageTest.php | 101 + tests/Feature/Events/Wiki/SeriesTest.php | 101 + tests/Feature/Events/Wiki/SongTest.php | 101 + tests/Feature/Events/Wiki/StudioTest.php | 101 + tests/Feature/Events/Wiki/VideoTest.php | 101 + tests/Feature/ExampleTest.php | 21 - .../Announcement/AnnouncementDestroyTest.php | 52 + .../AnnouncementForceDeleteTest.php | 52 + .../Announcement/AnnouncementIndexTest.php | 401 + .../Announcement/AnnouncementRestoreTest.php | 56 + .../Announcement/AnnouncementShowTest.php | 109 + .../Announcement/AnnouncementStoreTest.php | 70 + .../Announcement/AnnouncementUpdateTest.php | 55 + .../Billing/Balance/BalanceDestroyTest.php | 52 + .../Balance/BalanceForceDeleteTest.php | 52 + .../Api/Billing/Balance/BalanceIndexTest.php | 401 + .../Billing/Balance/BalanceRestoreTest.php | 56 + .../Api/Billing/Balance/BalanceShowTest.php | 109 + .../Api/Billing/Balance/BalanceStoreTest.php | 82 + .../Api/Billing/Balance/BalanceUpdateTest.php | 69 + .../Transaction/TransactionDestroyTest.php | 52 + .../TransactionForceDeleteTest.php | 52 + .../Transaction/TransactionIndexTest.php | 401 + .../Transaction/TransactionRestoreTest.php | 56 + .../Transaction/TransactionShowTest.php | 109 + .../Transaction/TransactionStoreTest.php | 77 + .../Transaction/TransactionUpdateTest.php | 62 + .../Feature/Http/Api/Config/FlagsShowTest.php | 87 + .../Feature/Http/Api/Config/WikiShowTest.php | 104 + .../Api/Document/Page/PageDestroyTest.php | 52 + .../Api/Document/Page/PageForceDeleteTest.php | 52 + .../Http/Api/Document/Page/PageIndexTest.php | 401 + .../Api/Document/Page/PageRestoreTest.php | 56 + .../Http/Api/Document/Page/PageShowTest.php | 109 + .../Http/Api/Document/Page/PageStoreTest.php | 72 + .../Http/Api/Document/Page/PageUpdateTest.php | 55 + tests/Feature/Http/Api/FallbackTest.php | 29 + tests/Feature/Http/Api/ThrottleTest.php | 45 + .../Http/Api/Wiki/Anime/AnimeDestroyTest.php | 52 + .../Api/Wiki/Anime/AnimeForceDeleteTest.php | 52 + .../Http/Api/Wiki/Anime/AnimeIndexTest.php | 1211 ++ .../Http/Api/Wiki/Anime/AnimeRestoreTest.php | 56 + .../Http/Api/Wiki/Anime/AnimeShowTest.php | 808 ++ .../Http/Api/Wiki/Anime/AnimeStoreTest.php | 77 + .../Http/Api/Wiki/Anime/AnimeUpdateTest.php | 62 + .../Wiki/Anime/Synonym/SynonymDestroyTest.php | 53 + .../Anime/Synonym/SynonymForceDeleteTest.php | 53 + .../Wiki/Anime/Synonym/SynonymIndexTest.php | 585 + .../Wiki/Anime/Synonym/SynonymRestoreTest.php | 57 + .../Wiki/Anime/Synonym/SynonymShowTest.php | 238 + .../Wiki/Anime/Synonym/SynonymStoreTest.php | 76 + .../Wiki/Anime/Synonym/SynonymUpdateTest.php | 56 + .../Anime/Theme/Entry/EntryDestroyTest.php | 61 + .../Theme/Entry/EntryForceDeleteTest.php | 61 + .../Wiki/Anime/Theme/Entry/EntryIndexTest.php | 847 ++ .../Anime/Theme/Entry/EntryRestoreTest.php | 65 + .../Wiki/Anime/Theme/Entry/EntryShowTest.php | 382 + .../Wiki/Anime/Theme/Entry/EntryStoreTest.php | 84 + .../Anime/Theme/Entry/EntryUpdateTest.php | 64 + .../Api/Wiki/Anime/Theme/ThemeDestroyTest.php | 56 + .../Wiki/Anime/Theme/ThemeForceDeleteTest.php | 56 + .../Api/Wiki/Anime/Theme/ThemeIndexTest.php | 1238 ++ .../Api/Wiki/Anime/Theme/ThemeRestoreTest.php | 60 + .../Api/Wiki/Anime/Theme/ThemeShowTest.php | 760 ++ .../Api/Wiki/Anime/Theme/ThemeStoreTest.php | 84 + .../Api/Wiki/Anime/Theme/ThemeUpdateTest.php | 66 + .../Http/Api/Wiki/Anime/YearIndexTest.php | 37 + .../Http/Api/Wiki/Anime/YearShowTest.php | 224 + .../Api/Wiki/Artist/ArtistDestroyTest.php | 52 + .../Api/Wiki/Artist/ArtistForceDeleteTest.php | 52 + .../Http/Api/Wiki/Artist/ArtistIndexTest.php | 827 ++ .../Api/Wiki/Artist/ArtistRestoreTest.php | 56 + .../Http/Api/Wiki/Artist/ArtistShowTest.php | 504 + .../Http/Api/Wiki/Artist/ArtistStoreTest.php | 71 + .../Http/Api/Wiki/Artist/ArtistUpdateTest.php | 55 + .../ExternalResourceDestroyTest.php | 52 + .../ExternalResourceForceDeleteTest.php | 52 + .../ExternalResourceIndexTest.php | 579 + .../ExternalResourceRestoreTest.php | 56 + .../ExternalResourceShowTest.php | 241 + .../ExternalResourceStoreTest.php | 75 + .../ExternalResourceUpdateTest.php | 64 + .../Http/Api/Wiki/Image/ImageDestroyTest.php | 52 + .../Api/Wiki/Image/ImageForceDeleteTest.php | 51 + .../Http/Api/Wiki/Image/ImageIndexTest.php | 579 + .../Http/Api/Wiki/Image/ImageRestoreTest.php | 56 + .../Http/Api/Wiki/Image/ImageShowTest.php | 241 + .../Http/Api/Wiki/Image/ImageStoreTest.php | 80 + .../Http/Api/Wiki/Image/ImageUpdateTest.php | 62 + tests/Feature/Http/Api/Wiki/SearchTest.php | 113 + .../Api/Wiki/Series/SeriesDestroyTest.php | 52 + .../Api/Wiki/Series/SeriesForceDeleteTest.php | 52 + .../Http/Api/Wiki/Series/SeriesIndexTest.php | 561 + .../Api/Wiki/Series/SeriesRestoreTest.php | 56 + .../Http/Api/Wiki/Series/SeriesShowTest.php | 249 + .../Http/Api/Wiki/Series/SeriesStoreTest.php | 71 + .../Http/Api/Wiki/Series/SeriesUpdateTest.php | 55 + .../Http/Api/Wiki/Song/SongDestroyTest.php | 52 + .../Api/Wiki/Song/SongForceDeleteTest.php | 52 + .../Http/Api/Wiki/Song/SongIndexTest.php | 706 ++ .../Http/Api/Wiki/Song/SongRestoreTest.php | 56 + .../Http/Api/Wiki/Song/SongShowTest.php | 390 + .../Http/Api/Wiki/Song/SongStoreTest.php | 52 + .../Http/Api/Wiki/Song/SongUpdateTest.php | 55 + .../Api/Wiki/Studio/StudioDestroyTest.php | 52 + .../Api/Wiki/Studio/StudioForceDeleteTest.php | 52 + .../Http/Api/Wiki/Studio/StudioIndexTest.php | 561 + .../Api/Wiki/Studio/StudioRestoreTest.php | 56 + .../Http/Api/Wiki/Studio/StudioShowTest.php | 249 + .../Http/Api/Wiki/Studio/StudioStoreTest.php | 71 + .../Http/Api/Wiki/Studio/StudioUpdateTest.php | 55 + .../Http/Api/Wiki/Video/VideoDestroyTest.php | 52 + .../Api/Wiki/Video/VideoForceDeleteTest.php | 52 + .../Http/Api/Wiki/Video/VideoIndexTest.php | 1143 ++ .../Http/Api/Wiki/Video/VideoRestoreTest.php | 56 + .../Http/Api/Wiki/Video/VideoShowTest.php | 543 + .../Http/Api/Wiki/Video/VideoStoreTest.php | 82 + .../Http/Api/Wiki/Video/VideoUpdateTest.php | 69 + tests/Feature/Http/Auth/RegistrationTest.php | 335 + .../Feature/Http/Billing/TransparencyTest.php | 132 + tests/Feature/Http/Wiki/VideoTest.php | 110 + tests/Feature/Jobs/Admin/AnnouncementTest.php | 87 + tests/Feature/Jobs/Auth/InvitationTest.php | 87 + tests/Feature/Jobs/Auth/UserTest.php | 87 + tests/Feature/Jobs/Billing/BalanceTest.php | 87 + .../Feature/Jobs/Billing/TransactionTest.php | 87 + tests/Feature/Jobs/Document/PageTest.php | 87 + tests/Feature/Jobs/Pivot/AnimeImageTest.php | 57 + .../Feature/Jobs/Pivot/AnimeResourceTest.php | 87 + tests/Feature/Jobs/Pivot/AnimeSeriesTest.php | 57 + tests/Feature/Jobs/Pivot/AnimeStudioTest.php | 57 + .../Jobs/Pivot/AnimeThemeEntryVideoTest.php | 63 + tests/Feature/Jobs/Pivot/ArtistImageTest.php | 57 + tests/Feature/Jobs/Pivot/ArtistMemberTest.php | 86 + .../Feature/Jobs/Pivot/ArtistResourceTest.php | 87 + tests/Feature/Jobs/Pivot/ArtistSongTest.php | 87 + .../Feature/Jobs/Pivot/StudioResourceTest.php | 87 + .../Jobs/SendDiscordNotificationTest.php | 107 + tests/Feature/Jobs/Wiki/Anime/SynonymTest.php | 98 + .../Jobs/Wiki/Anime/Theme/EntryTest.php | 101 + tests/Feature/Jobs/Wiki/Anime/ThemeTest.php | 98 + tests/Feature/Jobs/Wiki/AnimeTest.php | 87 + tests/Feature/Jobs/Wiki/ArtistTest.php | 87 + .../Jobs/Wiki/ExternalResourceTest.php | 87 + tests/Feature/Jobs/Wiki/ImageTest.php | 87 + tests/Feature/Jobs/Wiki/SeriesTest.php | 87 + tests/Feature/Jobs/Wiki/SongTest.php | 87 + tests/Feature/Jobs/Wiki/StudioTest.php | 87 + tests/Feature/Jobs/Wiki/VideoTest.php | 87 + .../Listeners/SendDiscordNotificationTest.php | 107 + .../Wiki/InitializeVideoTagsTest.php | 208 + tests/TestCase.php | 20 + tests/Unit/Enums/BaseEnumTest.php | 56 + tests/Unit/Events/CascadesDeletesTest.php | 28 + tests/Unit/Events/CascadesRestoresTest.php | 28 + tests/Unit/Events/NotifiesNovaUsersTest.php | 28 + .../Unit/Events/UpdateRelatedIndicesTest.php | 28 + .../Http/Api/Criteria/Field/CriteriaTest.php | 46 + .../Http/Api/Criteria/Filter/CriteriaTest.php | 322 + .../Http/Api/Criteria/Filter/FakeCriteria.php | 67 + .../Api/Criteria/Filter/HasCriteriaTest.php | 118 + .../Criteria/Filter/TrashedCriteriaTest.php | 30 + .../Api/Criteria/Filter/WhereCriteriaTest.php | 92 + .../Criteria/Filter/WhereInCriteriaTest.php | 117 + .../Http/Api/Criteria/Paging/CriteriaTest.php | 129 + .../Http/Api/Criteria/Sort/CriteriaTest.php | 140 + .../Http/Api/Filter/BooleanFilterTest.php | 69 + tests/Unit/Http/Api/Filter/DateFilterTest.php | 96 + tests/Unit/Http/Api/Filter/EnumFilterTest.php | 91 + tests/Unit/Http/Api/Filter/FilterTest.php | 85 + .../Unit/Http/Api/Filter/FloatFilterTest.php | 55 + tests/Unit/Http/Api/Filter/HasFilterTest.php | 133 + tests/Unit/Http/Api/Filter/IntFilterTest.php | 55 + .../Unit/Http/Api/Parser/FieldParserTest.php | 92 + .../Unit/Http/Api/Parser/FilterParserTest.php | 152 + .../Http/Api/Parser/IncludeParserTest.php | 132 + .../Unit/Http/Api/Parser/PagingParserTest.php | 153 + .../Unit/Http/Api/Parser/SearchParserTest.php | 64 + tests/Unit/Http/Api/Parser/SortParserTest.php | 180 + tests/Unit/Http/Api/Query/FakeReadQuery.php | 60 + tests/Unit/Http/Api/Query/QueryTest.php | 211 + tests/Unit/Http/Api/Scope/GlobalScopeTest.php | 61 + .../Unit/Http/Api/Scope/RelationScopeTest.php | 77 + tests/Unit/Http/Api/Scope/ScopeParserTest.php | 52 + tests/Unit/Http/Api/Scope/TypeScopeTest.php | 93 + tests/Unit/Http/Api/Sort/RandomSortTest.php | 31 + tests/Unit/Http/Api/Sort/SortTest.php | 58 + tests/Unit/Models/Admin/AnnouncementTest.php | 41 + tests/Unit/Models/Auth/InvitationTest.php | 108 + tests/Unit/Models/Auth/UserTest.php | 67 + tests/Unit/Models/Billing/BalanceTest.php | 71 + tests/Unit/Models/Billing/TransactionTest.php | 56 + tests/Unit/Models/Document/PageTest.php | 41 + .../Models/Wiki/Anime/AnimeSynonymTest.php | 93 + .../Unit/Models/Wiki/Anime/AnimeThemeTest.php | 162 + .../Wiki/Anime/Theme/AnimeThemeEntryTest.php | 133 + tests/Unit/Models/Wiki/AnimeTest.php | 207 + tests/Unit/Models/Wiki/ArtistTest.php | 171 + .../Unit/Models/Wiki/ExternalResourceTest.php | 123 + tests/Unit/Models/Wiki/ImageTest.php | 146 + tests/Unit/Models/Wiki/SeriesTest.php | 90 + tests/Unit/Models/Wiki/SongTest.php | 111 + tests/Unit/Models/Wiki/StudioTest.php | 111 + tests/Unit/Models/Wiki/VideoTest.php | 291 + .../Notifications/DiscordNotificationTest.php | 45 + tests/Unit/Pivots/AnimeImageTest.php | 52 + tests/Unit/Pivots/AnimeResourceTest.php | 52 + tests/Unit/Pivots/AnimeSeriesTest.php | 52 + tests/Unit/Pivots/AnimeStudioTest.php | 52 + .../Unit/Pivots/AnimeThemeEntryVideoTest.php | 51 + tests/Unit/Pivots/ArtistImageTest.php | 52 + tests/Unit/Pivots/ArtistMemberTest.php | 51 + tests/Unit/Pivots/ArtistResourceTest.php | 52 + tests/Unit/Pivots/ArtistSongTest.php | 52 + tests/Unit/Pivots/StudioResourceTest.php | 52 + .../Api/DistinctIgnoringDirectionTest.php | 71 + tests/Unit/Rules/Api/EnumDescriptionTest.php | 76 + tests/Unit/Rules/Api/IsValidBooleanTest.php | 100 + tests/Unit/Rules/Api/RandomSoleTest.php | 60 + .../Rules/Billing/TransparencyDateTest.php | 96 + .../Wiki/ResourceLinkMatchesSiteTest.php | 73 + .../Wiki/ResourceSiteMatchesLinkTest.php | 78 + .../Discord/DiscordEmbedFieldTest.php | 143 + .../Api/Parser/FilterParserTest.php | 106 + .../Api/Parser/PagingParserTest.php | 45 + .../Api/Parser/SortParserTest.php | 60 + webpack.mix.js | 39 +- 1412 files changed, 111723 insertions(+), 9084 deletions(-) create mode 100644 .github/workflows/static-analysis.yml create mode 100644 .github/workflows/test.yml create mode 100644 app/Actions/Fortify/CreateNewUser.php create mode 100644 app/Actions/Fortify/ResetUserPassword.php create mode 100644 app/Actions/Fortify/UpdateUserPassword.php create mode 100644 app/Actions/Fortify/UpdateUserProfileInformation.php create mode 100644 app/Actions/Jetstream/DeleteUser.php create mode 100644 app/Concerns/DetectsRedis.php create mode 100644 app/Concerns/Discord/HasAttributeUpdateEmbedFields.php create mode 100644 app/Concerns/Discord/HasDiscordEmbedFields.php create mode 100644 app/Concerns/Http/Requests/Api/ValidatesConditionally.php create mode 100644 app/Concerns/Http/Requests/Api/ValidatesFields.php create mode 100644 app/Concerns/Http/Requests/Api/ValidatesFilters.php create mode 100644 app/Concerns/Http/Requests/Api/ValidatesIncludes.php create mode 100644 app/Concerns/Http/Requests/Api/ValidatesPaging.php create mode 100644 app/Concerns/Http/Requests/Api/ValidatesParameters.php create mode 100644 app/Concerns/Http/Requests/Api/ValidatesSorts.php create mode 100644 app/Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php create mode 100644 app/Concerns/Repositories/Billing/ReconcilesTransactionRepositories.php create mode 100644 app/Concerns/Repositories/ReconcilesRepositories.php create mode 100644 app/Concerns/Repositories/Wiki/ReconcilesVideoRepositories.php delete mode 100644 app/Console/Commands/AnnouncementCreateCommand.php delete mode 100644 app/Console/Commands/AnnouncementDeleteCommand.php delete mode 100644 app/Console/Commands/AnnouncementListCommand.php delete mode 100644 app/Console/Commands/AnnouncementReadCommand.php delete mode 100644 app/Console/Commands/AnnouncementUpdateCommand.php create mode 100644 app/Console/Commands/Billing/Balance/BalanceReconcileCommand.php create mode 100644 app/Console/Commands/Billing/ServiceReconcileCommand.php create mode 100644 app/Console/Commands/Billing/Transaction/TransactionReconcileCommand.php create mode 100644 app/Console/Commands/DatabaseDumpCommand.php create mode 100644 app/Console/Commands/Document/DocumentDatabaseDumpCommand.php create mode 100644 app/Console/Commands/PruneDatabaseDumpsCommand.php delete mode 100644 app/Console/Commands/VideoReconcileCommand.php create mode 100644 app/Console/Commands/Wiki/Video/VideoReconcileCommand.php create mode 100644 app/Console/Commands/Wiki/WikiDatabaseDumpCommand.php create mode 100644 app/Constants/Config/FlagConstants.php create mode 100644 app/Constants/Config/ServiceConstants.php create mode 100644 app/Constants/Config/WikiConstants.php create mode 100644 app/Contracts/Events/CascadesDeletesEvent.php create mode 100644 app/Contracts/Events/CascadesRestoresEvent.php create mode 100644 app/Contracts/Events/DiscordMessageEvent.php create mode 100644 app/Contracts/Events/NovaNotificationEvent.php create mode 100644 app/Contracts/Events/UpdateRelatedIndicesEvent.php create mode 100644 app/Contracts/Http/Api/Field/CreatableField.php create mode 100644 app/Contracts/Http/Api/Field/FieldInterface.php create mode 100644 app/Contracts/Http/Api/Field/FilterableField.php create mode 100644 app/Contracts/Http/Api/Field/SelectableField.php create mode 100644 app/Contracts/Http/Api/Field/SortableField.php create mode 100644 app/Contracts/Http/Api/Field/UpdatableField.php create mode 100644 app/Contracts/Http/Api/Query/QueryInterface.php create mode 100644 app/Contracts/Http/Api/Schema/SchemaInterface.php create mode 100644 app/Contracts/Http/Requests/Api/SearchableRequest.php create mode 100644 app/Contracts/Models/Nameable.php create mode 100644 app/Contracts/Pipes/Pipe.php create mode 100644 app/Contracts/Repositories/Repository.php create mode 100644 app/Discord/DiscordEmbedField.php create mode 100644 app/Enums/BaseEnum.php create mode 100644 app/Enums/Discord/EmbedColor.php create mode 100644 app/Enums/Http/Api/Filter/AllowedDateFormat.php create mode 100644 app/Enums/Http/Api/Filter/BinaryLogicalOperator.php create mode 100644 app/Enums/Http/Api/Filter/ComparisonOperator.php create mode 100644 app/Enums/Http/Api/Filter/LogicalOperator.php create mode 100644 app/Enums/Http/Api/Filter/TrashedStatus.php create mode 100644 app/Enums/Http/Api/Filter/UnaryLogicalOperator.php create mode 100644 app/Enums/Http/Api/Paging/PaginationStrategy.php create mode 100644 app/Enums/Http/Api/Sort/Direction.php create mode 100644 app/Enums/Models/Auth/InvitationStatus.php create mode 100644 app/Enums/Models/Billing/BalanceFrequency.php create mode 100644 app/Enums/Models/Billing/Service.php create mode 100644 app/Enums/Models/Wiki/AnimeSeason.php create mode 100644 app/Enums/Models/Wiki/ImageFacet.php create mode 100644 app/Enums/Models/Wiki/ResourceSite.php create mode 100644 app/Enums/Models/Wiki/ThemeType.php create mode 100644 app/Enums/Models/Wiki/VideoOverlap.php create mode 100644 app/Enums/Models/Wiki/VideoSource.php create mode 100644 app/Events/Admin/Announcement/AnnouncementCreated.php create mode 100644 app/Events/Admin/Announcement/AnnouncementDeleted.php create mode 100644 app/Events/Admin/Announcement/AnnouncementRestored.php create mode 100644 app/Events/Admin/Announcement/AnnouncementUpdated.php create mode 100644 app/Events/Auth/Invitation/InvitationCreated.php create mode 100644 app/Events/Auth/Invitation/InvitationDeleted.php create mode 100644 app/Events/Auth/Invitation/InvitationRestored.php create mode 100644 app/Events/Auth/Invitation/InvitationUpdated.php create mode 100644 app/Events/Auth/User/UserCreated.php create mode 100644 app/Events/Auth/User/UserDeleted.php create mode 100644 app/Events/Auth/User/UserEvent.php create mode 100644 app/Events/Auth/User/UserRestored.php create mode 100644 app/Events/Auth/User/UserUpdated.php create mode 100644 app/Events/Base/Admin/AdminCreatedEvent.php create mode 100644 app/Events/Base/Admin/AdminDeletedEvent.php create mode 100644 app/Events/Base/Admin/AdminRestoredEvent.php create mode 100644 app/Events/Base/Admin/AdminUpdatedEvent.php create mode 100644 app/Events/Base/BaseCreatedEvent.php create mode 100644 app/Events/Base/BaseDeletedEvent.php create mode 100644 app/Events/Base/BaseRestoredEvent.php create mode 100644 app/Events/Base/BaseUpdatedEvent.php create mode 100644 app/Events/Base/Pivot/PivotCreatedEvent.php create mode 100644 app/Events/Base/Pivot/PivotDeletedEvent.php create mode 100644 app/Events/Base/Pivot/PivotUpdatedEvent.php create mode 100644 app/Events/Base/Wiki/WikiCreatedEvent.php create mode 100644 app/Events/Base/Wiki/WikiDeletedEvent.php create mode 100644 app/Events/Base/Wiki/WikiRestoredEvent.php create mode 100644 app/Events/Base/Wiki/WikiUpdatedEvent.php create mode 100644 app/Events/BaseEvent.php create mode 100644 app/Events/BasePivotEvent.php create mode 100644 app/Events/Billing/Balance/BalanceCreated.php create mode 100644 app/Events/Billing/Balance/BalanceDeleted.php create mode 100644 app/Events/Billing/Balance/BalanceRestored.php create mode 100644 app/Events/Billing/Balance/BalanceUpdated.php create mode 100644 app/Events/Billing/Transaction/TransactionCreated.php create mode 100644 app/Events/Billing/Transaction/TransactionDeleted.php create mode 100644 app/Events/Billing/Transaction/TransactionRestored.php create mode 100644 app/Events/Billing/Transaction/TransactionUpdated.php create mode 100644 app/Events/Document/Page/PageCreated.php create mode 100644 app/Events/Document/Page/PageDeleted.php create mode 100644 app/Events/Document/Page/PageRestored.php create mode 100644 app/Events/Document/Page/PageUpdated.php create mode 100644 app/Events/Pivot/AnimeImage/AnimeImageCreated.php create mode 100644 app/Events/Pivot/AnimeImage/AnimeImageDeleted.php create mode 100644 app/Events/Pivot/AnimeResource/AnimeResourceCreated.php create mode 100644 app/Events/Pivot/AnimeResource/AnimeResourceDeleted.php create mode 100644 app/Events/Pivot/AnimeResource/AnimeResourceUpdated.php create mode 100644 app/Events/Pivot/AnimeSeries/AnimeSeriesCreated.php create mode 100644 app/Events/Pivot/AnimeSeries/AnimeSeriesDeleted.php create mode 100644 app/Events/Pivot/AnimeStudio/AnimeStudioCreated.php create mode 100644 app/Events/Pivot/AnimeStudio/AnimeStudioDeleted.php create mode 100644 app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeCreatedVideo.php create mode 100644 app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeDeletedVideo.php create mode 100644 app/Events/Pivot/ArtistImage/ArtistImageCreated.php create mode 100644 app/Events/Pivot/ArtistImage/ArtistImageDeleted.php create mode 100644 app/Events/Pivot/ArtistMember/ArtistMemberCreated.php create mode 100644 app/Events/Pivot/ArtistMember/ArtistMemberDeleted.php create mode 100644 app/Events/Pivot/ArtistMember/ArtistMemberUpdated.php create mode 100644 app/Events/Pivot/ArtistResource/ArtistResourceCreated.php create mode 100644 app/Events/Pivot/ArtistResource/ArtistResourceDeleted.php create mode 100644 app/Events/Pivot/ArtistResource/ArtistResourceUpdated.php create mode 100644 app/Events/Pivot/ArtistSong/ArtistSongCreated.php create mode 100644 app/Events/Pivot/ArtistSong/ArtistSongDeleted.php create mode 100644 app/Events/Pivot/ArtistSong/ArtistSongUpdated.php create mode 100644 app/Events/Pivot/StudioResource/StudioResourceCreated.php create mode 100644 app/Events/Pivot/StudioResource/StudioResourceDeleted.php create mode 100644 app/Events/Pivot/StudioResource/StudioResourceUpdated.php create mode 100644 app/Events/Wiki/Anime/AnimeCreated.php create mode 100644 app/Events/Wiki/Anime/AnimeDeleted.php create mode 100644 app/Events/Wiki/Anime/AnimeDeleting.php create mode 100644 app/Events/Wiki/Anime/AnimeRestored.php create mode 100644 app/Events/Wiki/Anime/AnimeUpdated.php create mode 100644 app/Events/Wiki/Anime/Synonym/SynonymCreated.php create mode 100644 app/Events/Wiki/Anime/Synonym/SynonymDeleted.php create mode 100644 app/Events/Wiki/Anime/Synonym/SynonymRestored.php create mode 100644 app/Events/Wiki/Anime/Synonym/SynonymUpdated.php create mode 100644 app/Events/Wiki/Anime/Theme/Entry/EntryCreated.php create mode 100644 app/Events/Wiki/Anime/Theme/Entry/EntryDeleted.php create mode 100644 app/Events/Wiki/Anime/Theme/Entry/EntryDeleting.php create mode 100644 app/Events/Wiki/Anime/Theme/Entry/EntryRestored.php create mode 100644 app/Events/Wiki/Anime/Theme/Entry/EntryUpdated.php create mode 100644 app/Events/Wiki/Anime/Theme/ThemeCreated.php create mode 100644 app/Events/Wiki/Anime/Theme/ThemeCreating.php create mode 100644 app/Events/Wiki/Anime/Theme/ThemeDeleted.php create mode 100644 app/Events/Wiki/Anime/Theme/ThemeDeleting.php create mode 100644 app/Events/Wiki/Anime/Theme/ThemeRestored.php create mode 100644 app/Events/Wiki/Anime/Theme/ThemeUpdated.php create mode 100644 app/Events/Wiki/Artist/ArtistCreated.php create mode 100644 app/Events/Wiki/Artist/ArtistDeleted.php create mode 100644 app/Events/Wiki/Artist/ArtistRestored.php create mode 100644 app/Events/Wiki/Artist/ArtistUpdated.php create mode 100644 app/Events/Wiki/ExternalResource/ExternalResourceCreated.php create mode 100644 app/Events/Wiki/ExternalResource/ExternalResourceDeleted.php create mode 100644 app/Events/Wiki/ExternalResource/ExternalResourceRestored.php create mode 100644 app/Events/Wiki/ExternalResource/ExternalResourceUpdated.php create mode 100644 app/Events/Wiki/Image/ImageCreated.php create mode 100644 app/Events/Wiki/Image/ImageDeleted.php create mode 100644 app/Events/Wiki/Image/ImageDeleting.php create mode 100644 app/Events/Wiki/Image/ImageRestored.php create mode 100644 app/Events/Wiki/Image/ImageUpdated.php create mode 100644 app/Events/Wiki/Series/SeriesCreated.php create mode 100644 app/Events/Wiki/Series/SeriesDeleted.php create mode 100644 app/Events/Wiki/Series/SeriesRestored.php create mode 100644 app/Events/Wiki/Series/SeriesUpdated.php create mode 100644 app/Events/Wiki/Song/SongCreated.php create mode 100644 app/Events/Wiki/Song/SongDeleted.php create mode 100644 app/Events/Wiki/Song/SongDeleting.php create mode 100644 app/Events/Wiki/Song/SongRestored.php create mode 100644 app/Events/Wiki/Song/SongUpdated.php create mode 100644 app/Events/Wiki/Studio/StudioCreated.php create mode 100644 app/Events/Wiki/Studio/StudioDeleted.php create mode 100644 app/Events/Wiki/Studio/StudioRestored.php create mode 100644 app/Events/Wiki/Studio/StudioUpdated.php create mode 100644 app/Events/Wiki/Video/VideoCreated.php create mode 100644 app/Events/Wiki/Video/VideoCreating.php create mode 100644 app/Events/Wiki/Video/VideoDeleted.php create mode 100644 app/Events/Wiki/Video/VideoRestored.php create mode 100644 app/Events/Wiki/Video/VideoUpdated.php delete mode 100644 app/Grills/Grill.php delete mode 100644 app/Grills/GrillFactory.php create mode 100644 app/Http/Api/Criteria/Field/Criteria.php create mode 100644 app/Http/Api/Criteria/Filter/Criteria.php create mode 100644 app/Http/Api/Criteria/Filter/Expression.php create mode 100644 app/Http/Api/Criteria/Filter/HasCriteria.php create mode 100644 app/Http/Api/Criteria/Filter/Predicate.php create mode 100644 app/Http/Api/Criteria/Filter/TrashedCriteria.php create mode 100644 app/Http/Api/Criteria/Filter/WhereCriteria.php create mode 100644 app/Http/Api/Criteria/Filter/WhereInCriteria.php create mode 100644 app/Http/Api/Criteria/Include/Criteria.php create mode 100644 app/Http/Api/Criteria/Include/ResourceCriteria.php create mode 100644 app/Http/Api/Criteria/Paging/Criteria.php create mode 100644 app/Http/Api/Criteria/Paging/LimitCriteria.php create mode 100644 app/Http/Api/Criteria/Paging/OffsetCriteria.php create mode 100644 app/Http/Api/Criteria/Search/Criteria.php create mode 100644 app/Http/Api/Criteria/Sort/Criteria.php create mode 100644 app/Http/Api/Criteria/Sort/FieldCriteria.php create mode 100644 app/Http/Api/Criteria/Sort/RandomCriteria.php create mode 100644 app/Http/Api/Criteria/Sort/RelationCriteria.php create mode 100644 app/Http/Api/Field/Admin/Announcement/AnnouncementContentField.php create mode 100644 app/Http/Api/Field/Base/CreatedAtField.php create mode 100644 app/Http/Api/Field/Base/DeletedAtField.php create mode 100644 app/Http/Api/Field/Base/IdField.php create mode 100644 app/Http/Api/Field/Base/UpdatedAtField.php create mode 100644 app/Http/Api/Field/Billing/Balance/BalanceDateField.php create mode 100644 app/Http/Api/Field/Billing/Balance/BalanceFrequencyField.php create mode 100644 app/Http/Api/Field/Billing/Balance/BalanceMonthToDateField.php create mode 100644 app/Http/Api/Field/Billing/Balance/BalanceServiceField.php create mode 100644 app/Http/Api/Field/Billing/Balance/BalanceUsageField.php create mode 100644 app/Http/Api/Field/Billing/Transaction/TransactionAmountField.php create mode 100644 app/Http/Api/Field/Billing/Transaction/TransactionDateField.php create mode 100644 app/Http/Api/Field/Billing/Transaction/TransactionDescriptionField.php create mode 100644 app/Http/Api/Field/Billing/Transaction/TransactionExternalIdField.php create mode 100644 app/Http/Api/Field/Billing/Transaction/TransactionServiceField.php create mode 100644 app/Http/Api/Field/BooleanField.php create mode 100644 app/Http/Api/Field/Config/Flags/FlagsAllowDiscordNotificationsField.php create mode 100644 app/Http/Api/Field/Config/Flags/FlagsAllowVideoStreamsField.php create mode 100644 app/Http/Api/Field/Config/Flags/FlagsAllowViewRecordingField.php create mode 100644 app/Http/Api/Field/Config/Wiki/WikiFeaturedThemeField.php create mode 100644 app/Http/Api/Field/DateField.php create mode 100644 app/Http/Api/Field/Document/Page/PageBodyField.php create mode 100644 app/Http/Api/Field/Document/Page/PageNameField.php create mode 100644 app/Http/Api/Field/Document/Page/PageSlugField.php create mode 100644 app/Http/Api/Field/EnumField.php create mode 100644 app/Http/Api/Field/Field.php create mode 100644 app/Http/Api/Field/FloatField.php create mode 100644 app/Http/Api/Field/IntField.php create mode 100644 app/Http/Api/Field/StringField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/AnimeAsField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/AnimeNameField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/AnimeSeasonField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/AnimeSlugField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/AnimeSynopsisField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/AnimeYearField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Synonym/SynonymAnimeIdField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Synonym/SynonymTextField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/Entry/EntryEpisodesField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/Entry/EntryNotesField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/Entry/EntryNsfwField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/Entry/EntrySpoilerField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/Entry/EntryThemeIdField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/Entry/EntryVersionField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/ThemeAnimeIdField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/ThemeGroupField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/ThemeSequenceField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/ThemeSlugField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/ThemeSongIdField.php create mode 100644 app/Http/Api/Field/Wiki/Anime/Theme/ThemeTypeField.php create mode 100644 app/Http/Api/Field/Wiki/Artist/ArtistAsField.php create mode 100644 app/Http/Api/Field/Wiki/Artist/ArtistNameField.php create mode 100644 app/Http/Api/Field/Wiki/Artist/ArtistSlugField.php create mode 100644 app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceAsField.php create mode 100644 app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceIdField.php create mode 100644 app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceLinkField.php create mode 100644 app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceSiteField.php create mode 100644 app/Http/Api/Field/Wiki/Image/ImageFacetField.php create mode 100644 app/Http/Api/Field/Wiki/Image/ImageFileField.php create mode 100644 app/Http/Api/Field/Wiki/Image/ImageLinkField.php create mode 100644 app/Http/Api/Field/Wiki/Image/ImagePathField.php create mode 100644 app/Http/Api/Field/Wiki/Search/SearchAnimeField.php create mode 100644 app/Http/Api/Field/Wiki/Search/SearchArtistField.php create mode 100644 app/Http/Api/Field/Wiki/Search/SearchSeriesField.php create mode 100644 app/Http/Api/Field/Wiki/Search/SearchSongField.php create mode 100644 app/Http/Api/Field/Wiki/Search/SearchStudioField.php create mode 100644 app/Http/Api/Field/Wiki/Search/SearchThemeField.php create mode 100644 app/Http/Api/Field/Wiki/Search/SearchVideoField.php create mode 100644 app/Http/Api/Field/Wiki/Series/SeriesNameField.php create mode 100644 app/Http/Api/Field/Wiki/Series/SeriesSlugField.php create mode 100644 app/Http/Api/Field/Wiki/Song/SongAsField.php create mode 100644 app/Http/Api/Field/Wiki/Song/SongTitleField.php create mode 100644 app/Http/Api/Field/Wiki/Studio/StudioAsField.php create mode 100644 app/Http/Api/Field/Wiki/Studio/StudioNameField.php create mode 100644 app/Http/Api/Field/Wiki/Studio/StudioSlugField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoBasenameField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoFilenameField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoLinkField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoLyricsField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoMimeTypeField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoNcField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoOverlapField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoPathField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoResolutionField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoSizeField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoSourceField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoSubbedField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoTagsField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoUncenField.php create mode 100644 app/Http/Api/Filter/BooleanFilter.php create mode 100644 app/Http/Api/Filter/DateFilter.php create mode 100644 app/Http/Api/Filter/EnumFilter.php create mode 100644 app/Http/Api/Filter/Filter.php create mode 100644 app/Http/Api/Filter/FloatFilter.php create mode 100644 app/Http/Api/Filter/HasFilter.php create mode 100644 app/Http/Api/Filter/IntFilter.php create mode 100644 app/Http/Api/Filter/StringFilter.php create mode 100644 app/Http/Api/Filter/TrashedFilter.php create mode 100644 app/Http/Api/Include/AllowedInclude.php create mode 100644 app/Http/Api/Parser/FieldParser.php create mode 100644 app/Http/Api/Parser/FilterParser.php create mode 100644 app/Http/Api/Parser/IncludeParser.php create mode 100644 app/Http/Api/Parser/PagingParser.php create mode 100644 app/Http/Api/Parser/Parser.php create mode 100644 app/Http/Api/Parser/SearchParser.php create mode 100644 app/Http/Api/Parser/SortParser.php create mode 100644 app/Http/Api/Query/Admin/AnnouncementReadQuery.php create mode 100644 app/Http/Api/Query/Admin/AnnouncementWriteQuery.php create mode 100644 app/Http/Api/Query/Base/EloquentReadQuery.php create mode 100644 app/Http/Api/Query/Base/EloquentWriteQuery.php create mode 100644 app/Http/Api/Query/Billing/Balance/BalanceReadQuery.php create mode 100644 app/Http/Api/Query/Billing/Balance/BalanceWriteQuery.php create mode 100644 app/Http/Api/Query/Billing/Transaction/TransactionReadQuery.php create mode 100644 app/Http/Api/Query/Billing/Transaction/TransactionWriteQuery.php create mode 100644 app/Http/Api/Query/Config/FlagsReadQuery.php create mode 100644 app/Http/Api/Query/Config/WikiReadQuery.php create mode 100644 app/Http/Api/Query/Document/Page/PageReadQuery.php create mode 100644 app/Http/Api/Query/Document/Page/PageWriteQuery.php create mode 100644 app/Http/Api/Query/ReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/AnimeReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/AnimeWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/Synonym/SynonymReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/Synonym/SynonymWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/Theme/Entry/EntryReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/Theme/Entry/EntryWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/Theme/ThemeReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Anime/Theme/ThemeWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Artist/ArtistReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Artist/ArtistWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/ExternalResource/ExternalResourceReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/ExternalResource/ExternalResourceWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Image/ImageReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Image/ImageWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/SearchReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Series/SeriesReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Series/SeriesWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Song/SongReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Song/SongWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Studio/StudioReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Studio/StudioWriteQuery.php create mode 100644 app/Http/Api/Query/Wiki/Video/VideoReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Video/VideoWriteQuery.php create mode 100644 app/Http/Api/Query/WriteQuery.php create mode 100644 app/Http/Api/Schema/Admin/AnnouncementSchema.php create mode 100644 app/Http/Api/Schema/Billing/BalanceSchema.php create mode 100644 app/Http/Api/Schema/Billing/TransactionSchema.php create mode 100644 app/Http/Api/Schema/Config/FlagsSchema.php create mode 100644 app/Http/Api/Schema/Config/WikiSchema.php create mode 100644 app/Http/Api/Schema/Document/PageSchema.php create mode 100644 app/Http/Api/Schema/EloquentSchema.php create mode 100644 app/Http/Api/Schema/Schema.php create mode 100644 app/Http/Api/Schema/Wiki/Anime/SynonymSchema.php create mode 100644 app/Http/Api/Schema/Wiki/Anime/Theme/EntrySchema.php create mode 100644 app/Http/Api/Schema/Wiki/Anime/ThemeSchema.php create mode 100644 app/Http/Api/Schema/Wiki/AnimeSchema.php create mode 100644 app/Http/Api/Schema/Wiki/ArtistSchema.php create mode 100644 app/Http/Api/Schema/Wiki/ExternalResourceSchema.php create mode 100644 app/Http/Api/Schema/Wiki/ImageSchema.php create mode 100644 app/Http/Api/Schema/Wiki/SearchSchema.php create mode 100644 app/Http/Api/Schema/Wiki/SeriesSchema.php create mode 100644 app/Http/Api/Schema/Wiki/SongSchema.php create mode 100644 app/Http/Api/Schema/Wiki/StudioSchema.php create mode 100644 app/Http/Api/Schema/Wiki/VideoSchema.php create mode 100644 app/Http/Api/Scope/GlobalScope.php create mode 100644 app/Http/Api/Scope/RelationScope.php create mode 100644 app/Http/Api/Scope/Scope.php create mode 100644 app/Http/Api/Scope/ScopeParser.php create mode 100644 app/Http/Api/Scope/TypeScope.php create mode 100644 app/Http/Api/Sort/RandomSort.php create mode 100644 app/Http/Api/Sort/Sort.php create mode 100644 app/Http/Controllers/Api/Admin/AnnouncementController.php create mode 100644 app/Http/Controllers/Api/Billing/BalanceController.php create mode 100644 app/Http/Controllers/Api/Billing/TransactionController.php create mode 100644 app/Http/Controllers/Api/Config/FlagsController.php create mode 100644 app/Http/Controllers/Api/Config/WikiController.php create mode 100644 app/Http/Controllers/Api/Document/PageController.php create mode 100644 app/Http/Controllers/Api/Wiki/Anime/SynonymController.php create mode 100644 app/Http/Controllers/Api/Wiki/Anime/Theme/EntryController.php create mode 100644 app/Http/Controllers/Api/Wiki/Anime/ThemeController.php create mode 100644 app/Http/Controllers/Api/Wiki/Anime/YearController.php create mode 100644 app/Http/Controllers/Api/Wiki/AnimeController.php create mode 100644 app/Http/Controllers/Api/Wiki/ArtistController.php create mode 100644 app/Http/Controllers/Api/Wiki/ExternalResourceController.php create mode 100644 app/Http/Controllers/Api/Wiki/ImageController.php create mode 100644 app/Http/Controllers/Api/Wiki/SearchController.php create mode 100644 app/Http/Controllers/Api/Wiki/SeriesController.php create mode 100644 app/Http/Controllers/Api/Wiki/SongController.php create mode 100644 app/Http/Controllers/Api/Wiki/StudioController.php create mode 100644 app/Http/Controllers/Api/Wiki/VideoController.php create mode 100644 app/Http/Controllers/Auth/RegisterController.php create mode 100644 app/Http/Controllers/Billing/TransparencyController.php delete mode 100644 app/Http/Controllers/SitemapController.php delete mode 100644 app/Http/Controllers/VideosController.php delete mode 100644 app/Http/Controllers/WelcomeController.php create mode 100644 app/Http/Controllers/Wiki/VideoController.php delete mode 100644 app/Http/Middleware/CheckForMaintenanceMode.php delete mode 100644 app/Http/Middleware/EncryptCookies.php create mode 100644 app/Http/Middleware/HasOpenInvitation.php create mode 100644 app/Http/Middleware/IsVideoStreamingAllowed.php create mode 100644 app/Http/Middleware/LogRequest.php delete mode 100644 app/Http/Middleware/LoggerMiddleware.php create mode 100644 app/Http/Middleware/RecordView.php create mode 100644 app/Http/Middleware/SetAcceptJsonHeader.php create mode 100644 app/Http/Middleware/ThrottleRequestsWithService.php create mode 100644 app/Http/Middleware/TrustHosts.php delete mode 100644 app/Http/Middleware/VerifyCsrfToken.php create mode 100644 app/Http/Middleware/WithoutTrashed.php create mode 100644 app/Http/Requests/Api/Admin/AnnouncementDestroyRequest.php create mode 100644 app/Http/Requests/Api/Admin/AnnouncementForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Admin/AnnouncementIndexRequest.php create mode 100644 app/Http/Requests/Api/Admin/AnnouncementRestoreRequest.php create mode 100644 app/Http/Requests/Api/Admin/AnnouncementShowRequest.php create mode 100644 app/Http/Requests/Api/Admin/AnnouncementStoreRequest.php create mode 100644 app/Http/Requests/Api/Admin/AnnouncementUpdateRequest.php create mode 100644 app/Http/Requests/Api/Base/EloquentDestroyRequest.php create mode 100644 app/Http/Requests/Api/Base/EloquentForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Base/EloquentIndexRequest.php create mode 100644 app/Http/Requests/Api/Base/EloquentRestoreRequest.php create mode 100644 app/Http/Requests/Api/Base/EloquentShowRequest.php create mode 100644 app/Http/Requests/Api/Base/EloquentStoreRequest.php create mode 100644 app/Http/Requests/Api/Base/EloquentUpdateRequest.php create mode 100644 app/Http/Requests/Api/BaseRequest.php create mode 100644 app/Http/Requests/Api/Billing/Balance/BalanceDestroyRequest.php create mode 100644 app/Http/Requests/Api/Billing/Balance/BalanceForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Billing/Balance/BalanceIndexRequest.php create mode 100644 app/Http/Requests/Api/Billing/Balance/BalanceRestoreRequest.php create mode 100644 app/Http/Requests/Api/Billing/Balance/BalanceShowRequest.php create mode 100644 app/Http/Requests/Api/Billing/Balance/BalanceStoreRequest.php create mode 100644 app/Http/Requests/Api/Billing/Balance/BalanceUpdateRequest.php create mode 100644 app/Http/Requests/Api/Billing/Transaction/TransactionDestroyRequest.php create mode 100644 app/Http/Requests/Api/Billing/Transaction/TransactionForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Billing/Transaction/TransactionIndexRequest.php create mode 100644 app/Http/Requests/Api/Billing/Transaction/TransactionRestoreRequest.php create mode 100644 app/Http/Requests/Api/Billing/Transaction/TransactionShowRequest.php create mode 100644 app/Http/Requests/Api/Billing/Transaction/TransactionStoreRequest.php create mode 100644 app/Http/Requests/Api/Billing/Transaction/TransactionUpdateRequest.php create mode 100644 app/Http/Requests/Api/Config/FlagsRequest.php create mode 100644 app/Http/Requests/Api/Config/WikiRequest.php create mode 100644 app/Http/Requests/Api/DestroyRequest.php create mode 100644 app/Http/Requests/Api/Document/Page/PageDestroyRequest.php create mode 100644 app/Http/Requests/Api/Document/Page/PageForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Document/Page/PageIndexRequest.php create mode 100644 app/Http/Requests/Api/Document/Page/PageRestoreRequest.php create mode 100644 app/Http/Requests/Api/Document/Page/PageShowRequest.php create mode 100644 app/Http/Requests/Api/Document/Page/PageStoreRequest.php create mode 100644 app/Http/Requests/Api/Document/Page/PageUpdateRequest.php create mode 100644 app/Http/Requests/Api/ForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/IndexRequest.php create mode 100644 app/Http/Requests/Api/ReadRequest.php create mode 100644 app/Http/Requests/Api/RestoreRequest.php create mode 100644 app/Http/Requests/Api/ShowRequest.php create mode 100644 app/Http/Requests/Api/StoreRequest.php create mode 100644 app/Http/Requests/Api/UpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/AnimeDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/AnimeForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/AnimeIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/AnimeRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/AnimeShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/AnimeStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/AnimeUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/ThemeDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/ThemeForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/ThemeIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/ThemeRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/ThemeShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/ThemeStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/Theme/ThemeUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/YearIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Anime/YearShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Artist/ArtistDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Artist/ArtistForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Artist/ArtistIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Artist/ArtistRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Artist/ArtistShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Artist/ArtistStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Artist/ArtistUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Image/ImageDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Image/ImageForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Image/ImageIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Image/ImageRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Image/ImageShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Image/ImageStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Image/ImageUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/SearchRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Series/SeriesDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Series/SeriesForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Series/SeriesIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Series/SeriesRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Series/SeriesShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Series/SeriesStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Series/SeriesUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Song/SongDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Song/SongForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Song/SongIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Song/SongRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Song/SongShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Song/SongStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Song/SongUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Studio/StudioDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Studio/StudioForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Studio/StudioIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Studio/StudioRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Studio/StudioShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Studio/StudioStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Studio/StudioUpdateRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Video/VideoDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Video/VideoForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Video/VideoIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Video/VideoRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Video/VideoShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Video/VideoStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Video/VideoUpdateRequest.php create mode 100644 app/Http/Requests/Api/WriteRequest.php create mode 100644 app/Http/Requests/Billing/TransparencyRequest.php create mode 100644 app/Http/Resources/Admin/Collection/AnnouncementCollection.php create mode 100644 app/Http/Resources/Admin/Resource/AnnouncementResource.php create mode 100644 app/Http/Resources/BaseCollection.php create mode 100644 app/Http/Resources/BaseResource.php create mode 100644 app/Http/Resources/Billing/Collection/BalanceCollection.php create mode 100644 app/Http/Resources/Billing/Collection/TransactionCollection.php create mode 100644 app/Http/Resources/Billing/Resource/BalanceResource.php create mode 100644 app/Http/Resources/Billing/Resource/TransactionResource.php create mode 100644 app/Http/Resources/Config/Resource/FlagsResource.php create mode 100644 app/Http/Resources/Config/Resource/WikiResource.php create mode 100644 app/Http/Resources/Document/Collection/PageCollection.php create mode 100644 app/Http/Resources/Document/Resource/PageResource.php create mode 100644 app/Http/Resources/Pivot/Resource/AnimeThemeEntryVideoResource.php create mode 100644 app/Http/Resources/Wiki/Anime/Collection/SynonymCollection.php create mode 100644 app/Http/Resources/Wiki/Anime/Collection/ThemeCollection.php create mode 100644 app/Http/Resources/Wiki/Anime/Resource/SynonymResource.php create mode 100644 app/Http/Resources/Wiki/Anime/Resource/ThemeResource.php create mode 100644 app/Http/Resources/Wiki/Anime/Theme/Collection/EntryCollection.php create mode 100644 app/Http/Resources/Wiki/Anime/Theme/Resource/EntryResource.php create mode 100644 app/Http/Resources/Wiki/Collection/AnimeCollection.php create mode 100644 app/Http/Resources/Wiki/Collection/ArtistCollection.php create mode 100644 app/Http/Resources/Wiki/Collection/ExternalResourceCollection.php create mode 100644 app/Http/Resources/Wiki/Collection/ImageCollection.php create mode 100644 app/Http/Resources/Wiki/Collection/SeriesCollection.php create mode 100644 app/Http/Resources/Wiki/Collection/SongCollection.php create mode 100644 app/Http/Resources/Wiki/Collection/StudioCollection.php create mode 100644 app/Http/Resources/Wiki/Collection/VideoCollection.php create mode 100644 app/Http/Resources/Wiki/Resource/AnimeResource.php create mode 100644 app/Http/Resources/Wiki/Resource/ArtistResource.php create mode 100644 app/Http/Resources/Wiki/Resource/ExternalResourceResource.php create mode 100644 app/Http/Resources/Wiki/Resource/ImageResource.php create mode 100644 app/Http/Resources/Wiki/Resource/SearchResource.php create mode 100644 app/Http/Resources/Wiki/Resource/SeriesResource.php create mode 100644 app/Http/Resources/Wiki/Resource/SongResource.php create mode 100644 app/Http/Resources/Wiki/Resource/StudioResource.php create mode 100644 app/Http/Resources/Wiki/Resource/VideoResource.php create mode 100644 app/Jobs/Middleware/RateLimited.php create mode 100644 app/Jobs/SendDiscordNotificationJob.php create mode 100644 app/Listeners/Auth/Invitation/SendInvitationMail.php create mode 100644 app/Listeners/Auth/LogAttempting.php create mode 100644 app/Listeners/Auth/LogCurrentDeviceLogout.php create mode 100644 app/Listeners/Auth/LogFailed.php create mode 100644 app/Listeners/Auth/LogLockout.php create mode 100644 app/Listeners/Auth/LogLogin.php create mode 100644 app/Listeners/Auth/LogLogout.php create mode 100644 app/Listeners/Auth/LogOtherDeviceLogout.php create mode 100644 app/Listeners/Auth/LogPasswordReset.php create mode 100644 app/Listeners/Auth/LogRegistered.php create mode 100644 app/Listeners/CascadesDeletes.php create mode 100644 app/Listeners/CascadesRestores.php create mode 100644 app/Listeners/NotifiesNovaUsers.php create mode 100644 app/Listeners/SendDiscordNotification.php create mode 100644 app/Listeners/UpdateRelatedIndices.php create mode 100644 app/Listeners/Wiki/Anime/Theme/CreateThemeSlug.php create mode 100644 app/Listeners/Wiki/Image/RemoveImageFromStorage.php create mode 100644 app/Listeners/Wiki/Video/InitializeVideoTags.php create mode 100644 app/Mail/InvitationMail.php create mode 100644 app/Models/Admin/Announcement.php delete mode 100644 app/Models/Announcement.php create mode 100644 app/Models/Auth/Invitation.php create mode 100644 app/Models/Auth/Permission.php create mode 100644 app/Models/Auth/Role.php create mode 100644 app/Models/Auth/User.php create mode 100644 app/Models/BaseModel.php create mode 100644 app/Models/Billing/Balance.php create mode 100644 app/Models/Billing/Transaction.php create mode 100644 app/Models/Document/Page.php delete mode 100644 app/Models/User.php delete mode 100644 app/Models/Video.php create mode 100644 app/Models/Wiki/Anime.php create mode 100644 app/Models/Wiki/Anime/AnimeSynonym.php create mode 100644 app/Models/Wiki/Anime/AnimeTheme.php create mode 100644 app/Models/Wiki/Anime/Theme/AnimeThemeEntry.php create mode 100644 app/Models/Wiki/Artist.php create mode 100644 app/Models/Wiki/ExternalResource.php create mode 100644 app/Models/Wiki/Image.php create mode 100644 app/Models/Wiki/Series.php create mode 100644 app/Models/Wiki/Song.php create mode 100644 app/Models/Wiki/Studio.php create mode 100644 app/Models/Wiki/Video.php create mode 100644 app/Notifications/DiscordNotification.php create mode 100644 app/Nova/Actions/Auth/Invitation/ResendInvitationAction.php create mode 100644 app/Nova/Actions/Auth/Permission/GiveRoleAction.php create mode 100644 app/Nova/Actions/Auth/Permission/RevokeRoleAction.php create mode 100644 app/Nova/Actions/Auth/Role/GivePermissionAction.php create mode 100644 app/Nova/Actions/Auth/Role/RevokePermissionAction.php create mode 100644 app/Nova/Actions/Auth/User/GivePermissionAction.php create mode 100644 app/Nova/Actions/Auth/User/GiveRoleAction.php create mode 100644 app/Nova/Actions/Auth/User/RevokePermissionAction.php create mode 100644 app/Nova/Actions/Auth/User/RevokeRoleAction.php create mode 100644 app/Nova/Actions/Wiki/Anime/BackfillAnimeAction.php create mode 100644 app/Nova/Actions/Wiki/Anime/CreateExternalResourceSiteForAnimeAction.php create mode 100644 app/Nova/Actions/Wiki/Artist/CreateExternalResourceSiteForArtistAction.php create mode 100644 app/Nova/Actions/Wiki/Studio/CreateExternalResourceSiteForStudioAction.php create mode 100644 app/Nova/Dashboards/Main.php create mode 100644 app/Nova/Lenses/Anime/AnimeAniDbResourceLens.php create mode 100644 app/Nova/Lenses/Anime/AnimeAnilistResourceLens.php create mode 100644 app/Nova/Lenses/Anime/AnimeAnnResourceLens.php create mode 100644 app/Nova/Lenses/Anime/AnimeCoverLargeLens.php create mode 100644 app/Nova/Lenses/Anime/AnimeCoverSmallLens.php create mode 100644 app/Nova/Lenses/Anime/AnimeKitsuResourceLens.php create mode 100644 app/Nova/Lenses/Anime/AnimeMalResourceLens.php create mode 100644 app/Nova/Lenses/Anime/AnimePlanetResourceLens.php create mode 100644 app/Nova/Lenses/Anime/AnimeStudioLens.php create mode 100644 app/Nova/Lenses/Artist/ArtistAniDbResourceLens.php create mode 100644 app/Nova/Lenses/Artist/ArtistAnilistResourceLens.php create mode 100644 app/Nova/Lenses/Artist/ArtistAnnResourceLens.php create mode 100644 app/Nova/Lenses/Artist/ArtistCoverLargeLens.php create mode 100644 app/Nova/Lenses/Artist/ArtistCoverSmallLens.php create mode 100644 app/Nova/Lenses/Artist/ArtistMalResourceLens.php create mode 100644 app/Nova/Lenses/Artist/ArtistSongLens.php create mode 100644 app/Nova/Lenses/BaseLens.php create mode 100644 app/Nova/Lenses/ExternalResource/ExternalResourceUnlinkedLens.php create mode 100644 app/Nova/Lenses/Image/ImageUnlinkedLens.php create mode 100644 app/Nova/Lenses/Song/SongArtistLens.php create mode 100644 app/Nova/Lenses/Studio/StudioAniDbResourceLens.php create mode 100644 app/Nova/Lenses/Studio/StudioAnilistResourceLens.php create mode 100644 app/Nova/Lenses/Studio/StudioAnimePlanetResourceLens.php create mode 100644 app/Nova/Lenses/Studio/StudioAnnResourceLens.php create mode 100644 app/Nova/Lenses/Studio/StudioMalResourceLens.php create mode 100644 app/Nova/Lenses/Studio/StudioUnlinkedLens.php create mode 100644 app/Nova/Lenses/Video/VideoResolutionLens.php create mode 100644 app/Nova/Lenses/Video/VideoSourceLens.php create mode 100644 app/Nova/Lenses/Video/VideoUnlinkedLens.php create mode 100644 app/Nova/Metrics/Anime/AnimePerDay.php create mode 100644 app/Nova/Metrics/Anime/NewAnime.php create mode 100644 app/Nova/Metrics/Artist/ArtistsPerDay.php create mode 100644 app/Nova/Metrics/Artist/NewArtists.php create mode 100644 app/Nova/Metrics/Series/NewSeries.php create mode 100644 app/Nova/Metrics/Series/SeriesPerDay.php create mode 100644 app/Nova/Metrics/Video/NewVideos.php create mode 100644 app/Nova/Metrics/Video/VideosPerDay.php create mode 100644 app/Nova/Resources/Admin/Announcement.php create mode 100644 app/Nova/Resources/Auth/Invitation.php create mode 100644 app/Nova/Resources/Auth/Permission.php create mode 100644 app/Nova/Resources/Auth/Role.php create mode 100644 app/Nova/Resources/Auth/User.php create mode 100644 app/Nova/Resources/Billing/Balance.php create mode 100644 app/Nova/Resources/Billing/Transaction.php create mode 100644 app/Nova/Resources/Document/Page.php create mode 100644 app/Nova/Resources/Resource.php create mode 100644 app/Nova/Resources/Wiki/Anime.php create mode 100644 app/Nova/Resources/Wiki/Anime/Synonym.php create mode 100644 app/Nova/Resources/Wiki/Anime/Theme.php create mode 100644 app/Nova/Resources/Wiki/Anime/Theme/Entry.php create mode 100644 app/Nova/Resources/Wiki/Artist.php create mode 100644 app/Nova/Resources/Wiki/ExternalResource.php create mode 100644 app/Nova/Resources/Wiki/Image.php create mode 100644 app/Nova/Resources/Wiki/Series.php create mode 100644 app/Nova/Resources/Wiki/Song.php create mode 100644 app/Nova/Resources/Wiki/Studio.php create mode 100644 app/Nova/Resources/Wiki/Video.php create mode 100644 app/Pipes/Wiki/Anime/BackfillAnimeImage.php create mode 100644 app/Pipes/Wiki/Anime/BackfillAnimePipe.php create mode 100644 app/Pipes/Wiki/Anime/BackfillAnimeResource.php create mode 100644 app/Pipes/Wiki/Anime/Image/BackfillLargeCoverImage.php create mode 100644 app/Pipes/Wiki/Anime/Image/BackfillSmallCoverImage.php create mode 100644 app/Pipes/Wiki/Anime/Resource/BackfillAnidbResource.php create mode 100644 app/Pipes/Wiki/Anime/Resource/BackfillAnilistResource.php create mode 100644 app/Pipes/Wiki/Anime/Resource/BackfillAnnResource.php create mode 100644 app/Pipes/Wiki/Anime/Resource/BackfillKitsuResource.php create mode 100644 app/Pipes/Wiki/Anime/Resource/BackfillMalResource.php create mode 100644 app/Pipes/Wiki/Anime/Studio/BackfillAnimeStudios.php create mode 100644 app/Pivots/AnimeImage.php create mode 100644 app/Pivots/AnimeResource.php create mode 100644 app/Pivots/AnimeSeries.php create mode 100644 app/Pivots/AnimeStudio.php create mode 100644 app/Pivots/AnimeThemeEntryVideo.php create mode 100644 app/Pivots/ArtistImage.php create mode 100644 app/Pivots/ArtistMember.php create mode 100644 app/Pivots/ArtistResource.php create mode 100644 app/Pivots/ArtistSong.php create mode 100644 app/Pivots/BasePivot.php create mode 100644 app/Pivots/StudioResource.php create mode 100644 app/Policies/Admin/AnnouncementPolicy.php create mode 100644 app/Policies/Auth/InvitationPolicy.php create mode 100644 app/Policies/Auth/PermissionPolicy.php create mode 100644 app/Policies/Auth/RolePolicy.php create mode 100644 app/Policies/Auth/UserPolicy.php create mode 100644 app/Policies/Billing/BalancePolicy.php create mode 100644 app/Policies/Billing/TransactionPolicy.php create mode 100644 app/Policies/Document/PagePolicy.php create mode 100644 app/Policies/Wiki/Anime/AnimeSynonymPolicy.php create mode 100644 app/Policies/Wiki/Anime/AnimeThemePolicy.php create mode 100644 app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php create mode 100644 app/Policies/Wiki/AnimePolicy.php create mode 100644 app/Policies/Wiki/ArtistPolicy.php create mode 100644 app/Policies/Wiki/ExternalResourcePolicy.php create mode 100644 app/Policies/Wiki/ImagePolicy.php create mode 100644 app/Policies/Wiki/SeriesPolicy.php create mode 100644 app/Policies/Wiki/SongPolicy.php create mode 100644 app/Policies/Wiki/StudioPolicy.php create mode 100644 app/Policies/Wiki/VideoPolicy.php create mode 100644 app/Providers/FortifyServiceProvider.php create mode 100644 app/Providers/HorizonServiceProvider.php create mode 100644 app/Providers/JetstreamServiceProvider.php create mode 100644 app/Providers/NovaServiceProvider.php create mode 100644 app/Providers/TelescopeServiceProvider.php create mode 100644 app/Repositories/Eloquent/Billing/DigitalOceanBalanceRepository.php create mode 100644 app/Repositories/Eloquent/Billing/DigitalOceanTransactionRepository.php create mode 100644 app/Repositories/Eloquent/EloquentRepository.php create mode 100644 app/Repositories/Eloquent/Wiki/VideoRepository.php create mode 100644 app/Repositories/Service/DigitalOcean/Billing/DigitalOceanBalanceRepository.php create mode 100644 app/Repositories/Service/DigitalOcean/Billing/DigitalOceanTransactionRepository.php create mode 100644 app/Repositories/Service/DigitalOcean/VideoRepository.php create mode 100644 app/Rules/Api/DistinctIgnoringDirectionRule.php create mode 100644 app/Rules/Api/EnumDescriptionRule.php create mode 100644 app/Rules/Api/IsValidBoolean.php create mode 100644 app/Rules/Api/RandomSoleRule.php create mode 100644 app/Rules/Billing/TransparencyDateRule.php create mode 100644 app/Rules/Wiki/ResourceLinkMatchesSiteRule.php create mode 100644 app/Rules/Wiki/ResourceSiteMatchesLinkRule.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Filter/Criteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Filter/WhereCriteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Filter/WhereInCriteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Paging/Criteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Paging/LimitCriteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Paging/OffsetCriteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Sort/Criteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Sort/FieldCriteria.php create mode 100644 app/Scout/Elasticsearch/Api/Criteria/Sort/RelationCriteria.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Base/CreatedAtField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Base/DeletedAtField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Base/IdField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Base/UpdatedAtField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/BooleanField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/DateField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/EnumField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Field.php create mode 100644 app/Scout/Elasticsearch/Api/Field/FloatField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/IntField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/StringField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeNameField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeSeasonField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeSlugField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeSynopsisField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeYearField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Synonym/SynonymTextField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/Entry/EntryEpisodesField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/Entry/EntryNotesField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/Entry/EntryNsfwField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/Entry/EntrySpoilerField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/Entry/EntryVersionField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/ThemeGroupField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/ThemeSequenceField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/ThemeSlugField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Anime/Theme/ThemeTypeField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Artist/ArtistNameField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Artist/ArtistSlugField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Series/SeriesNameField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Series/SeriesSlugField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Song/SongTitleField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Studio/StudioNameField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Studio/StudioSlugField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoBasenameField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoFilenameField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoLyricsField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoMimeTypeField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoNcField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoOverlapField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoPathField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoResolutionField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoSizeField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoSourceField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoSubbedField.php create mode 100644 app/Scout/Elasticsearch/Api/Field/Wiki/Video/VideoUncenField.php create mode 100644 app/Scout/Elasticsearch/Api/Parser/FilterParser.php create mode 100644 app/Scout/Elasticsearch/Api/Parser/PagingParser.php create mode 100644 app/Scout/Elasticsearch/Api/Parser/SortParser.php create mode 100644 app/Scout/Elasticsearch/Api/Query/Anime/SynonymQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/Anime/Theme/EntryQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/Anime/ThemeQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/AnimeQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/ArtistQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/ElasticQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/SeriesQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/SongQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/StudioQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Query/VideoQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Schema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/Anime/SynonymSchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/Anime/Theme/EntrySchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/Anime/ThemeSchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/AnimeSchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/ArtistSchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/SeriesSchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/SongSchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/StudioSchema.php create mode 100644 app/Scout/Elasticsearch/Api/Schema/Wiki/VideoSchema.php create mode 100644 app/Scout/Elasticsearch/DiscoverElasticQueryPayload.php create mode 100644 app/Scout/Elasticsearch/Elasticsearch.php create mode 100644 app/Scout/Search.php create mode 100644 app/View/Components/AppLayout.php create mode 100644 app/View/Components/GuestLayout.php create mode 100644 config/api.php create mode 100644 config/audit.php create mode 100644 config/elastic.client.php create mode 100644 config/elastic.migrations.php create mode 100644 config/elastic.scout_driver.php create mode 100644 config/eloquent-viewable.php create mode 100644 config/flags.php create mode 100644 config/fortify.php create mode 100644 config/horizon.php create mode 100644 config/jetstream.php create mode 100644 config/nova.php create mode 100644 config/permission.php create mode 100644 config/route-discovery.php create mode 100644 config/sanctum.php create mode 100644 config/scout.php create mode 100644 config/secure-headers.php create mode 100644 config/telescope.php create mode 100644 config/video.php create mode 100644 config/wiki.php create mode 100644 database/factories/Admin/AnnouncementFactory.php create mode 100644 database/factories/Auth/InvitationFactory.php create mode 100644 database/factories/Auth/UserFactory.php create mode 100644 database/factories/Billing/BalanceFactory.php create mode 100644 database/factories/Billing/TransactionFactory.php create mode 100644 database/factories/Document/PageFactory.php create mode 100644 database/factories/Pivots/AnimeImageFactory.php create mode 100644 database/factories/Pivots/AnimeResourceFactory.php create mode 100644 database/factories/Pivots/AnimeSeriesFactory.php create mode 100644 database/factories/Pivots/AnimeStudioFactory.php create mode 100644 database/factories/Pivots/AnimeThemeEntryVideoFactory.php create mode 100644 database/factories/Pivots/ArtistImageFactory.php create mode 100644 database/factories/Pivots/ArtistMemberFactory.php create mode 100644 database/factories/Pivots/ArtistResourceFactory.php create mode 100644 database/factories/Pivots/ArtistSongFactory.php create mode 100644 database/factories/Pivots/StudioResourceFactory.php delete mode 100644 database/factories/UserFactory.php create mode 100644 database/factories/Wiki/Anime/AnimeSynonymFactory.php create mode 100644 database/factories/Wiki/Anime/AnimeThemeFactory.php create mode 100644 database/factories/Wiki/Anime/Theme/AnimeThemeEntryFactory.php create mode 100644 database/factories/Wiki/AnimeFactory.php create mode 100644 database/factories/Wiki/ArtistFactory.php create mode 100644 database/factories/Wiki/ExternalResourceFactory.php create mode 100644 database/factories/Wiki/ImageFactory.php create mode 100644 database/factories/Wiki/SeriesFactory.php create mode 100644 database/factories/Wiki/SongFactory.php create mode 100644 database/factories/Wiki/StudioFactory.php create mode 100644 database/factories/Wiki/VideoFactory.php create mode 100644 database/migrations/2014_10_12_100000_create_password_resets_table.php create mode 100644 database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php delete mode 100644 database/migrations/2017_12_26_221942_create_natural_sort.php delete mode 100644 database/migrations/2018_03_28_023041_add_basename_to_videos_table.php create mode 100644 database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php create mode 100644 database/migrations/2020_05_05_044925_create_anime_table.php create mode 100644 database/migrations/2020_05_05_044939_create_series_table.php create mode 100644 database/migrations/2020_05_05_044953_create_songs_table.php create mode 100644 database/migrations/2020_05_05_045006_create_artists_table.php create mode 100644 database/migrations/2020_05_05_045017_create_resources_table.php create mode 100644 database/migrations/2020_05_05_045028_create_anime_themes_table.php create mode 100644 database/migrations/2020_05_05_045037_create_anime_theme_entries_table.php create mode 100644 database/migrations/2020_05_05_051400_create_anime_synonyms_table.php create mode 100644 database/migrations/2020_05_05_054036_create_anime_series.php create mode 100644 database/migrations/2020_05_05_055804_create_anime_theme_entry_video.php create mode 100644 database/migrations/2020_05_05_060307_create_anime_resource.php create mode 100644 database/migrations/2020_05_05_062655_create_artist_song.php create mode 100644 database/migrations/2020_05_05_063144_create_artist_resource.php create mode 100644 database/migrations/2020_05_06_024632_create_audits_table.php create mode 100644 database/migrations/2020_05_24_220046_create_invitations_table.php create mode 100644 database/migrations/2020_06_10_050701_create_artist_member.php delete mode 100644 database/migrations/2020_09_09_195220_add_uuid_to_failed_jobs.php create mode 100644 database/migrations/2020_09_16_183751_create_sessions_table.php create mode 100644 database/migrations/2020_11_05_220613_create_configs_table.php delete mode 100644 database/migrations/2020_11_13_204613_add_size_to_videos_table.php create mode 100644 database/migrations/2020_11_24_231534_create_image_table.php create mode 100644 database/migrations/2020_11_24_232439_create_anime_image.php create mode 100644 database/migrations/2020_11_24_232525_create_artist_image.php create mode 100644 database/migrations/2020_12_08_034959_create_views_table.php create mode 100644 database/migrations/2021_03_25_022128_update_telescope_entries_table.php create mode 100644 database/migrations/2021_05_13_192052_create_transactions_table.php create mode 100644 database/migrations/2021_05_13_194800_create_balances_table.php create mode 100644 database/migrations/2021_08_20_185746_create_studios_table.php create mode 100644 database/migrations/2021_08_20_213018_create_anime_studio.php create mode 100644 database/migrations/2021_11_03_003056_create_studio_resource.php create mode 100644 database/migrations/2022_02_14_175203_create_page_table.php create mode 100644 database/migrations/2022_04_09_234337_create_jobs_table.php create mode 100644 database/migrations/2022_05_09_032230_create_permission_tables.php create mode 100644 database/migrations/2022_05_12_165551_create_cache_table.php create mode 100644 database/seeders/AniDbResourceSeeder.php create mode 100644 database/seeders/AnilistAnimeResourceSeeder.php create mode 100644 database/seeders/AnilistArtistResourceSeeder.php create mode 100644 database/seeders/ArtistCoverSeeder.php create mode 100644 database/seeders/DatabaseSeeder.php create mode 100644 database/seeders/DigitalOceanTransactionSeeder.php create mode 100644 database/seeders/KitsuResourceSeeder.php create mode 100644 database/seeders/PermissionSeeder.php create mode 100644 database/seeders/StudioSeeder.php create mode 100644 database/seeders/SynopsisCoverSeeder.php create mode 100644 database/seeders/VideoSeeder.php delete mode 100644 database/seeds/DatabaseSeeder.php delete mode 100644 database/seeds/VideosTableSeeder.php create mode 100644 elastic/migrations/2020_12_21_225415_create_anime_index.php create mode 100644 elastic/migrations/2020_12_22_033019_create_artist_index.php create mode 100644 elastic/migrations/2020_12_22_034505_create_entry_index.php create mode 100644 elastic/migrations/2020_12_22_034544_create_series_index.php create mode 100644 elastic/migrations/2020_12_22_034549_create_song_index.php create mode 100644 elastic/migrations/2020_12_22_034553_create_synonym_index.php create mode 100644 elastic/migrations/2020_12_22_034557_create_theme_index.php create mode 100644 elastic/migrations/2020_12_22_034601_create_video_index.php create mode 100644 elastic/migrations/2021_08_20_213637_create_studio_index.php create mode 100644 phpstan.neon delete mode 100644 public/css/pomf.css delete mode 100644 public/img/bg.png create mode 100644 public/img/logo.svg delete mode 100644 public/js/pomf.js delete mode 100644 public/manifest.js create mode 100644 public/vendor/horizon/app-dark.css create mode 100644 public/vendor/horizon/app.css create mode 100644 public/vendor/horizon/app.js create mode 100644 public/vendor/horizon/img/favicon.png create mode 100644 public/vendor/horizon/img/horizon.svg create mode 100644 public/vendor/horizon/img/sprite.svg create mode 100644 public/vendor/horizon/mix-manifest.json create mode 100644 public/vendor/nova/app.css create mode 100644 public/vendor/nova/app.css.map create mode 100644 public/vendor/nova/app.js create mode 100644 public/vendor/nova/app.js.map create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g1pty10iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g1pty14iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g1pty1ciurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g1pty1miurt9w6c.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g1pty1wiurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g35sy10iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g35sy14iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g35sy1ciurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g35sy1miurt9w6c.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g35sy1wiurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g3joy10iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g3joy14iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g3joy1ciurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g3joy1miurt9w6c.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4g3joy1wiurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gwzuy10iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gwzuy14iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gwzuy1ciurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gwzuy1miurt9w6c.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gwzuy1wiurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gxzry10iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gxzry14iurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gxzry1ciurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gxzry1miurt9w6c.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe01mimslybiv1o4x1m8cce4gxzry1wiurt9w6fk2a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8-bm5gu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8-bm5iu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8-bm5ju1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8-bm5pu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8-bm5tu1ecvzl.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8abc5gu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8abc5iu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8abc5ju1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8abc5pu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8abc5tu1ecvzl.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8wac5gu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8wac5iu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8wac5ju1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8wac5pu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc8wac5tu1ecvzl.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9ib85gu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9ib85iu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9ib85ju1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9ib85pu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9ib85tu1ecvzl.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9yas5gu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9yas5iu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9yas5ju1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9yas5pu1ecvzl-86y.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe03mimslybiv1o4x1m8cc9yas5tu1ecvzl.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0omimslybiv1o4x1m8cce4e91kdn4qx5fhyg.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0omimslybiv1o4x1m8cce4e9dkdn4qx5fhyg.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0omimslybiv1o4x1m8cce4e9lkdn4qx5e.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0omimslybiv1o4x1m8cce4e9rkdn4qx5fhyg.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0omimslybiv1o4x1m8cce4e9zkdn4qx5fhyg.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0qmimslybiv1o4x1m8cce5i9tacvwob5a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0qmimslybiv1o4x1m8cce9i9tacvwo.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0qmimslybiv1o4x1m8ccewi9tacvwob5a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0qmimslybiv1o4x1m8cceyi9tacvwob5a.woff2 create mode 100644 public/vendor/nova/fonts/snunitosansv11pe0qmimslybiv1o4x1m8ccezi9tacvwob5a.woff2 create mode 100644 public/vendor/nova/manifest.js create mode 100644 public/vendor/nova/manifest.js.map create mode 100644 public/vendor/nova/mix-manifest.json create mode 100644 public/vendor/nova/vendor.js create mode 100644 public/vendor/nova/vendor.js.LICENSE.txt create mode 100644 public/vendor/nova/vendor.js.map create mode 100644 public/vendor/telescope/app-dark.css create mode 100644 public/vendor/telescope/app.css create mode 100644 public/vendor/telescope/app.js create mode 100644 public/vendor/telescope/favicon.ico create mode 100644 public/vendor/telescope/mix-manifest.json create mode 100644 resources/css/app.css create mode 100644 resources/lang/en/enums.php create mode 100644 resources/lang/en/nova.php create mode 100644 resources/lang/vendor/exception/en/exceptions.php create mode 100644 resources/lang/vendor/exception/pt-br/exceptions.php create mode 100644 resources/lang/vendor/laravelEnum/en/messages.php create mode 100644 resources/lang/vendor/nova/en.json create mode 100644 resources/lang/vendor/nova/en/validation.php create mode 100644 resources/markdown/policy.md create mode 100644 resources/markdown/terms.md delete mode 100644 resources/sass/app.scss create mode 100644 resources/views/api/api-token-manager.blade.php create mode 100644 resources/views/api/index.blade.php create mode 100644 resources/views/auth/confirm-password.blade.php create mode 100644 resources/views/auth/forgot-password.blade.php create mode 100644 resources/views/auth/login.blade.php create mode 100644 resources/views/auth/register.blade.php create mode 100644 resources/views/auth/reset-password.blade.php create mode 100644 resources/views/auth/two-factor-challenge.blade.php create mode 100644 resources/views/auth/verify-email.blade.php create mode 100644 resources/views/billing/transparency.blade.php create mode 100644 resources/views/dashboard.blade.php delete mode 100644 resources/views/layouts/analytics.blade.php delete mode 100644 resources/views/layouts/announcements.blade.php delete mode 100644 resources/views/layouts/grill.blade.php create mode 100644 resources/views/layouts/guest.blade.php delete mode 100644 resources/views/layouts/nav.blade.php delete mode 100644 resources/views/layouts/webm.blade.php create mode 100644 resources/views/mail/invitation.blade.php create mode 100644 resources/views/navigation-menu.blade.php create mode 100644 resources/views/policy.blade.php create mode 100644 resources/views/profile/delete-user-form.blade.php create mode 100644 resources/views/profile/logout-other-browser-sessions-form.blade.php create mode 100644 resources/views/profile/show.blade.php create mode 100644 resources/views/profile/two-factor-authentication-form.blade.php create mode 100644 resources/views/profile/update-password-form.blade.php create mode 100644 resources/views/profile/update-profile-information-form.blade.php delete mode 100644 resources/views/sitemap/index.blade.php delete mode 100644 resources/views/sitemap/videos.blade.php create mode 100644 resources/views/terms.blade.php create mode 100644 resources/views/vendor/jetstream/components/application-logo.blade.php create mode 100644 resources/views/vendor/jetstream/components/application-mark.blade.php create mode 100644 resources/views/vendor/jetstream/components/authentication-card-logo.blade.php delete mode 100644 resources/views/videos.blade.php delete mode 100644 resources/views/welcome.blade.php create mode 100644 routes/video.php delete mode 100644 server.php create mode 100644 storage/db-dumps/.gitignore create mode 100644 tailwind.config.js create mode 100644 tests/Feature/Actions/Fortify/AuthenticationTest.php create mode 100644 tests/Feature/Actions/Fortify/EmailVerificationTest.php create mode 100644 tests/Feature/Actions/Fortify/PasswordConfirmationTest.php create mode 100644 tests/Feature/Actions/Fortify/PasswordResetTest.php create mode 100644 tests/Feature/Actions/Jetstream/BrowserSessionsTest.php create mode 100644 tests/Feature/Actions/Jetstream/CreateApiTokenTest.php create mode 100644 tests/Feature/Actions/Jetstream/DeleteAccountTest.php create mode 100644 tests/Feature/Actions/Jetstream/DeleteApiTokenTest.php create mode 100644 tests/Feature/Actions/Jetstream/ProfileInformationTest.php create mode 100644 tests/Feature/Actions/Jetstream/TwoFactorAuthenticationSettingsTest.php create mode 100644 tests/Feature/Actions/Jetstream/UpdatePasswordTest.php create mode 100644 tests/Feature/Console/Commands/Billing/BalanceReconcileTest.php create mode 100644 tests/Feature/Console/Commands/Billing/TransactionReconcileTest.php create mode 100644 tests/Feature/Console/Commands/Document/DocumentDatabaseDumpTest.php create mode 100644 tests/Feature/Console/Commands/Wiki/PruneDatabaseDumpsTest.php create mode 100644 tests/Feature/Console/Commands/Wiki/VideoReconcileTest.php create mode 100644 tests/Feature/Console/Commands/Wiki/WikiDatabaseDumpTest.php create mode 100644 tests/Feature/Events/Admin/AnnouncementTest.php create mode 100644 tests/Feature/Events/Auth/InvitationTest.php create mode 100644 tests/Feature/Events/Auth/UserTest.php create mode 100644 tests/Feature/Events/Billing/BalanceTest.php create mode 100644 tests/Feature/Events/Billing/TransactionTest.php create mode 100644 tests/Feature/Events/Document/PageTest.php create mode 100644 tests/Feature/Events/Pivot/AnimeImageTest.php create mode 100644 tests/Feature/Events/Pivot/AnimeResourceTest.php create mode 100644 tests/Feature/Events/Pivot/AnimeSeriesTest.php create mode 100644 tests/Feature/Events/Pivot/AnimeStudioTest.php create mode 100644 tests/Feature/Events/Pivot/AnimeThemeEntryVideoTest.php create mode 100644 tests/Feature/Events/Pivot/ArtistImageTest.php create mode 100644 tests/Feature/Events/Pivot/ArtistMemberTest.php create mode 100644 tests/Feature/Events/Pivot/ArtistResourceTest.php create mode 100644 tests/Feature/Events/Pivot/ArtistSongTest.php create mode 100644 tests/Feature/Events/Pivot/StudioResourceTest.php create mode 100644 tests/Feature/Events/Wiki/Anime/SynonymTest.php create mode 100644 tests/Feature/Events/Wiki/Anime/Theme/EntryTest.php create mode 100644 tests/Feature/Events/Wiki/Anime/ThemeTest.php create mode 100644 tests/Feature/Events/Wiki/AnimeTest.php create mode 100644 tests/Feature/Events/Wiki/ArtistTest.php create mode 100644 tests/Feature/Events/Wiki/ExternalResourceTest.php create mode 100644 tests/Feature/Events/Wiki/ImageTest.php create mode 100644 tests/Feature/Events/Wiki/SeriesTest.php create mode 100644 tests/Feature/Events/Wiki/SongTest.php create mode 100644 tests/Feature/Events/Wiki/StudioTest.php create mode 100644 tests/Feature/Events/Wiki/VideoTest.php delete mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/Feature/Http/Api/Admin/Announcement/AnnouncementDestroyTest.php create mode 100644 tests/Feature/Http/Api/Admin/Announcement/AnnouncementForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Admin/Announcement/AnnouncementIndexTest.php create mode 100644 tests/Feature/Http/Api/Admin/Announcement/AnnouncementRestoreTest.php create mode 100644 tests/Feature/Http/Api/Admin/Announcement/AnnouncementShowTest.php create mode 100644 tests/Feature/Http/Api/Admin/Announcement/AnnouncementStoreTest.php create mode 100644 tests/Feature/Http/Api/Admin/Announcement/AnnouncementUpdateTest.php create mode 100644 tests/Feature/Http/Api/Billing/Balance/BalanceDestroyTest.php create mode 100644 tests/Feature/Http/Api/Billing/Balance/BalanceForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Billing/Balance/BalanceIndexTest.php create mode 100644 tests/Feature/Http/Api/Billing/Balance/BalanceRestoreTest.php create mode 100644 tests/Feature/Http/Api/Billing/Balance/BalanceShowTest.php create mode 100644 tests/Feature/Http/Api/Billing/Balance/BalanceStoreTest.php create mode 100644 tests/Feature/Http/Api/Billing/Balance/BalanceUpdateTest.php create mode 100644 tests/Feature/Http/Api/Billing/Transaction/TransactionDestroyTest.php create mode 100644 tests/Feature/Http/Api/Billing/Transaction/TransactionForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Billing/Transaction/TransactionIndexTest.php create mode 100644 tests/Feature/Http/Api/Billing/Transaction/TransactionRestoreTest.php create mode 100644 tests/Feature/Http/Api/Billing/Transaction/TransactionShowTest.php create mode 100644 tests/Feature/Http/Api/Billing/Transaction/TransactionStoreTest.php create mode 100644 tests/Feature/Http/Api/Billing/Transaction/TransactionUpdateTest.php create mode 100644 tests/Feature/Http/Api/Config/FlagsShowTest.php create mode 100644 tests/Feature/Http/Api/Config/WikiShowTest.php create mode 100644 tests/Feature/Http/Api/Document/Page/PageDestroyTest.php create mode 100644 tests/Feature/Http/Api/Document/Page/PageForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Document/Page/PageIndexTest.php create mode 100644 tests/Feature/Http/Api/Document/Page/PageRestoreTest.php create mode 100644 tests/Feature/Http/Api/Document/Page/PageShowTest.php create mode 100644 tests/Feature/Http/Api/Document/Page/PageStoreTest.php create mode 100644 tests/Feature/Http/Api/Document/Page/PageUpdateTest.php create mode 100644 tests/Feature/Http/Api/FallbackTest.php create mode 100644 tests/Feature/Http/Api/ThrottleTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/AnimeDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/AnimeForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/AnimeIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/AnimeRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/AnimeShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/AnimeStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/AnimeUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Synonym/SynonymDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Synonym/SynonymForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Synonym/SynonymIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Synonym/SynonymRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Synonym/SynonymShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Synonym/SynonymStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Synonym/SynonymUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/Entry/EntryDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/Entry/EntryForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/Entry/EntryIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/Entry/EntryRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/Entry/EntryShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/Entry/EntryStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/Entry/EntryUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/ThemeDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/ThemeForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/ThemeIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/ThemeRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/ThemeShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/ThemeStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/Theme/ThemeUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/YearIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Anime/YearShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Artist/ArtistDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Artist/ArtistForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Artist/ArtistIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Artist/ArtistRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Artist/ArtistShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Artist/ArtistStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Artist/ArtistUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/ExternalResource/ExternalResourceDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/ExternalResource/ExternalResourceForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/ExternalResource/ExternalResourceIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/ExternalResource/ExternalResourceRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/ExternalResource/ExternalResourceShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/ExternalResource/ExternalResourceStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/ExternalResource/ExternalResourceUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Image/ImageDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Image/ImageForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Image/ImageIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Image/ImageRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Image/ImageShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Image/ImageStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Image/ImageUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/SearchTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Series/SeriesDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Series/SeriesForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Series/SeriesIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Series/SeriesRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Series/SeriesShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Series/SeriesStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Series/SeriesUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Song/SongDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Song/SongForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Song/SongIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Song/SongRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Song/SongShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Song/SongStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Song/SongUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Studio/StudioDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Studio/StudioForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Studio/StudioIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Studio/StudioRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Studio/StudioShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Studio/StudioStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Studio/StudioUpdateTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Video/VideoDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Video/VideoForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Video/VideoIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Video/VideoRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Video/VideoShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Video/VideoStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Video/VideoUpdateTest.php create mode 100644 tests/Feature/Http/Auth/RegistrationTest.php create mode 100644 tests/Feature/Http/Billing/TransparencyTest.php create mode 100644 tests/Feature/Http/Wiki/VideoTest.php create mode 100644 tests/Feature/Jobs/Admin/AnnouncementTest.php create mode 100644 tests/Feature/Jobs/Auth/InvitationTest.php create mode 100644 tests/Feature/Jobs/Auth/UserTest.php create mode 100644 tests/Feature/Jobs/Billing/BalanceTest.php create mode 100644 tests/Feature/Jobs/Billing/TransactionTest.php create mode 100644 tests/Feature/Jobs/Document/PageTest.php create mode 100644 tests/Feature/Jobs/Pivot/AnimeImageTest.php create mode 100644 tests/Feature/Jobs/Pivot/AnimeResourceTest.php create mode 100644 tests/Feature/Jobs/Pivot/AnimeSeriesTest.php create mode 100644 tests/Feature/Jobs/Pivot/AnimeStudioTest.php create mode 100644 tests/Feature/Jobs/Pivot/AnimeThemeEntryVideoTest.php create mode 100644 tests/Feature/Jobs/Pivot/ArtistImageTest.php create mode 100644 tests/Feature/Jobs/Pivot/ArtistMemberTest.php create mode 100644 tests/Feature/Jobs/Pivot/ArtistResourceTest.php create mode 100644 tests/Feature/Jobs/Pivot/ArtistSongTest.php create mode 100644 tests/Feature/Jobs/Pivot/StudioResourceTest.php create mode 100644 tests/Feature/Jobs/SendDiscordNotificationTest.php create mode 100644 tests/Feature/Jobs/Wiki/Anime/SynonymTest.php create mode 100644 tests/Feature/Jobs/Wiki/Anime/Theme/EntryTest.php create mode 100644 tests/Feature/Jobs/Wiki/Anime/ThemeTest.php create mode 100644 tests/Feature/Jobs/Wiki/AnimeTest.php create mode 100644 tests/Feature/Jobs/Wiki/ArtistTest.php create mode 100644 tests/Feature/Jobs/Wiki/ExternalResourceTest.php create mode 100644 tests/Feature/Jobs/Wiki/ImageTest.php create mode 100644 tests/Feature/Jobs/Wiki/SeriesTest.php create mode 100644 tests/Feature/Jobs/Wiki/SongTest.php create mode 100644 tests/Feature/Jobs/Wiki/StudioTest.php create mode 100644 tests/Feature/Jobs/Wiki/VideoTest.php create mode 100644 tests/Feature/Listeners/SendDiscordNotificationTest.php create mode 100644 tests/Feature/Listeners/Wiki/InitializeVideoTagsTest.php create mode 100644 tests/Unit/Enums/BaseEnumTest.php create mode 100644 tests/Unit/Events/CascadesDeletesTest.php create mode 100644 tests/Unit/Events/CascadesRestoresTest.php create mode 100644 tests/Unit/Events/NotifiesNovaUsersTest.php create mode 100644 tests/Unit/Events/UpdateRelatedIndicesTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Field/CriteriaTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Filter/CriteriaTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Filter/FakeCriteria.php create mode 100644 tests/Unit/Http/Api/Criteria/Filter/HasCriteriaTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Filter/TrashedCriteriaTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Filter/WhereCriteriaTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Filter/WhereInCriteriaTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Paging/CriteriaTest.php create mode 100644 tests/Unit/Http/Api/Criteria/Sort/CriteriaTest.php create mode 100644 tests/Unit/Http/Api/Filter/BooleanFilterTest.php create mode 100644 tests/Unit/Http/Api/Filter/DateFilterTest.php create mode 100644 tests/Unit/Http/Api/Filter/EnumFilterTest.php create mode 100644 tests/Unit/Http/Api/Filter/FilterTest.php create mode 100644 tests/Unit/Http/Api/Filter/FloatFilterTest.php create mode 100644 tests/Unit/Http/Api/Filter/HasFilterTest.php create mode 100644 tests/Unit/Http/Api/Filter/IntFilterTest.php create mode 100644 tests/Unit/Http/Api/Parser/FieldParserTest.php create mode 100644 tests/Unit/Http/Api/Parser/FilterParserTest.php create mode 100644 tests/Unit/Http/Api/Parser/IncludeParserTest.php create mode 100644 tests/Unit/Http/Api/Parser/PagingParserTest.php create mode 100644 tests/Unit/Http/Api/Parser/SearchParserTest.php create mode 100644 tests/Unit/Http/Api/Parser/SortParserTest.php create mode 100644 tests/Unit/Http/Api/Query/FakeReadQuery.php create mode 100644 tests/Unit/Http/Api/Query/QueryTest.php create mode 100644 tests/Unit/Http/Api/Scope/GlobalScopeTest.php create mode 100644 tests/Unit/Http/Api/Scope/RelationScopeTest.php create mode 100644 tests/Unit/Http/Api/Scope/ScopeParserTest.php create mode 100644 tests/Unit/Http/Api/Scope/TypeScopeTest.php create mode 100644 tests/Unit/Http/Api/Sort/RandomSortTest.php create mode 100644 tests/Unit/Http/Api/Sort/SortTest.php create mode 100644 tests/Unit/Models/Admin/AnnouncementTest.php create mode 100644 tests/Unit/Models/Auth/InvitationTest.php create mode 100644 tests/Unit/Models/Auth/UserTest.php create mode 100644 tests/Unit/Models/Billing/BalanceTest.php create mode 100644 tests/Unit/Models/Billing/TransactionTest.php create mode 100644 tests/Unit/Models/Document/PageTest.php create mode 100644 tests/Unit/Models/Wiki/Anime/AnimeSynonymTest.php create mode 100644 tests/Unit/Models/Wiki/Anime/AnimeThemeTest.php create mode 100644 tests/Unit/Models/Wiki/Anime/Theme/AnimeThemeEntryTest.php create mode 100644 tests/Unit/Models/Wiki/AnimeTest.php create mode 100644 tests/Unit/Models/Wiki/ArtistTest.php create mode 100644 tests/Unit/Models/Wiki/ExternalResourceTest.php create mode 100644 tests/Unit/Models/Wiki/ImageTest.php create mode 100644 tests/Unit/Models/Wiki/SeriesTest.php create mode 100644 tests/Unit/Models/Wiki/SongTest.php create mode 100644 tests/Unit/Models/Wiki/StudioTest.php create mode 100644 tests/Unit/Models/Wiki/VideoTest.php create mode 100644 tests/Unit/Notifications/DiscordNotificationTest.php create mode 100644 tests/Unit/Pivots/AnimeImageTest.php create mode 100644 tests/Unit/Pivots/AnimeResourceTest.php create mode 100644 tests/Unit/Pivots/AnimeSeriesTest.php create mode 100644 tests/Unit/Pivots/AnimeStudioTest.php create mode 100644 tests/Unit/Pivots/AnimeThemeEntryVideoTest.php create mode 100644 tests/Unit/Pivots/ArtistImageTest.php create mode 100644 tests/Unit/Pivots/ArtistMemberTest.php create mode 100644 tests/Unit/Pivots/ArtistResourceTest.php create mode 100644 tests/Unit/Pivots/ArtistSongTest.php create mode 100644 tests/Unit/Pivots/StudioResourceTest.php create mode 100644 tests/Unit/Rules/Api/DistinctIgnoringDirectionTest.php create mode 100644 tests/Unit/Rules/Api/EnumDescriptionTest.php create mode 100644 tests/Unit/Rules/Api/IsValidBooleanTest.php create mode 100644 tests/Unit/Rules/Api/RandomSoleTest.php create mode 100644 tests/Unit/Rules/Billing/TransparencyDateTest.php create mode 100644 tests/Unit/Rules/Wiki/ResourceLinkMatchesSiteTest.php create mode 100644 tests/Unit/Rules/Wiki/ResourceSiteMatchesLinkTest.php create mode 100644 tests/Unit/Services/Discord/DiscordEmbedFieldTest.php create mode 100644 tests/Unit/Services/Elasticsearch/Api/Parser/FilterParserTest.php create mode 100644 tests/Unit/Services/Elasticsearch/Api/Parser/PagingParserTest.php create mode 100644 tests/Unit/Services/Elasticsearch/Api/Parser/SortParserTest.php diff --git a/.editorconfig b/.editorconfig index 6537ca467..1671c9b9d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,6 @@ trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.env.example b/.env.example index a508af7ea..95678126d 100644 --- a/.env.example +++ b/.env.example @@ -1,50 +1,196 @@ -APP_NAME=Laravel +# api +API_PATH=/api +API_URL= + +# app +APP_NAME=AnimeThemes APP_ENV=local -APP_KEY= APP_DEBUG=true APP_URL=http://localhost +ASSET_URL=null +APP_KEY= -LOG_CHANNEL=stack +# audit +AUDITING_ENABLED=true +# broadcasting +BROADCAST_DRIVER=log +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_APP_ID= +PUSHER_APP_CLUSTER=mt1 +ABLY_KEY= +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +# cache +CACHE_DRIVER=file +MEMCACHED_PERSISTENT_ID= +MEMCACHED_USERNAME= +MEMCACHED_PASSWORD= +MEMCACHED_HOST=127.0.0.1 +MEMCACHED_PORT=11211 +DYNAMODB_CACHE_TABLE=cache +DYNAMODB_ENDPOINT= +CACHE_PREFIX=animethemes + +# database DB_CONNECTION=mysql -DB_HOST=127.0.0.1 +DATABASE_URL= +DB_HOST_READ=127.0.0.1 +DB_HOST_WRITE=127.0.0.1 +DB_STICKY=false DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD= +DB_SOCKET= +MYSQL_ATTR_SSL_CA=null -BROADCAST_DRIVER=log -CACHE_DRIVER=file -QUEUE_CONNECTION=sync -SESSION_DRIVER=file -SESSION_LIFETIME=120 - +REDIS_CLIENT=predis +REDIS_CLUSTER=redis +REDIS_URL=null REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 +REDIS_SCHEME=tcp +REDIS_DB=0 +REDIS_CACHE_DB=1 -MAIL_MAILER=smtp -MAIL_HOST=smtp.mailtrap.io -MAIL_PORT=2525 +# elastic client +ELASTIC_HOST=localhost +ELASTIC_PORT=9200 +ELASTIC_SCHEME=http +ELASTIC_USER= +ELASTIC_PASS= + +# elastic driver +ELASTIC_SCOUT_DRIVER_REFRESH_DOCUMENTS=false + +# elastic migrations +ELASTIC_MIGRATIONS_TABLE=elastic_migrations + +# enlightn +ENLIGHTN_SKIP_ENVIRONMENT_SPECIFIC=false +ENLIGHTN_USERNAME= +ENLIGHTN_API_TOKEN= +ENLIGHTN_GITHUB_REPO= + +# feature flags +ALLOW_VIDEO_STREAMS=false +ALLOW_DISCORD_NOTIFICATIONS=false +ALLOW_VIEW_RECORDING=false + +# filesystems +FILESYSTEM_DISK=local + +IMAGE_ACCESS_KEY_ID= +IMAGE_SECRET_ACCESS_KEY= +IMAGE_DEFAULT_REGION= +IMAGE_ENDPOINT= +IMAGE_BUCKET= +IMAGE_STREAM_READS= +IMAGE_DISABLE_ASSERTS= +IMAGE_VISIBILITY= +IMAGE_URL= + +VIDEO_ACCESS_KEY_ID= +VIDEO_SECRET_ACCESS_KEY= +VIDEO_DEFAULT_REGION= +VIDEO_ENDPOINT= +VIDEO_BUCKET= +VIDEO_STREAM_READS= +VIDEO_DISABLE_ASSERTS= +VIDEO_VISIBILITY= + +# hashing +BCRYPT_ROUNDS=10 + +# horizon +HORIZON_DOMAIN= +HORIZON_PATH=horizon + +# jetstream +TEAM_CREATOR_ID= + +# logging +LOG_CHANNEL=daily +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +# mail +MAIL_MAILER=log +MAIL_HOST=smtp.mailgun.org +MAIL_PORT=587 +MAIL_ENCRYPTION=tls MAIL_USERNAME=null MAIL_PASSWORD=null -MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" +MAIL_SENDMAIL_PATH=/usr/sbin/sendmail +MAIL_LOG_CHANNEL= -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -AWS_DEFAULT_REGION=us-east-1 -AWS_BUCKET= -AWS_ENDPOINT= -AWS_STREAM_READS= +# nova +NOVA_DOMAIN_NAME=null +NOVA_GUARD=null +NOVA_LICENSE_KEY= +NOVA_PASSWORDS=null +NOVA_PATH=/admin -PUSHER_APP_ID= -PUSHER_APP_KEY= -PUSHER_APP_SECRET= -PUSHER_APP_CLUSTER=mt1 +# queue +QUEUE_CONNECTION=sync +REDIS_QUEUE=default +QUEUE_FAILED_DRIVER=database-uuids -MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" -MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" +# scout +SCOUT_DRIVER=null +SCOUT_PREFIX= +SCOUT_QUEUE=false +SCOUT_IDENTIFY=false +ALGOLIA_APP_ID= +ALGOLIA_SECRET= -GLOBAL_SITE_TAG= +# secure headers +ENABLE_CLEAR_SITE_DATA=false +ENABLE_HSTS=false +ENABLE_EXPECT_CT=false +ENABLE_PERMISSIONS_POLICY=false +ENABLE_CSP=false + +# services +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MAILGUN_ENDPOINT=api.mailgun.net +POSTMARK_TOKEN= +DISCORD_BOT_API_TOKEN= +DB_UPDATES_DISCORD_CHANNEL= +ADMIN_DISCORD_CHANNEL= +MAL_CLIENT_ID=null +DO_BEARER_TOKEN=null + +# session +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_CONNECTION=null +SESSION_STORE=null +SESSION_DOMAIN=null +SESSION_SECURE_COOKIE= + +# teams +HORIZON_TEAM_ID= +NOVA_TEAM_ID= +TELESCOPE_TEAM_ID= + +# telescope +TELESCOPE_DOMAIN=null +TELESCOPE_PATH=telescope +TELESCOPE_DRIVER=database +TELESCOPE_ENABLED=true + +# video +VIDEO_PATH=/video +VIDEO_URL= + +# wiki +WIKI_FEATURED_ENTRY= +WIKI_FEATURED_VIDEO= diff --git a/.gitattributes b/.gitattributes index 967315dd3..510d9961f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,10 @@ * text=auto -*.css linguist-vendored -*.scss linguist-vendored -*.js linguist-vendored + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore CHANGELOG.md export-ignore diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2c947ab10..55d1e891e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,7 +4,7 @@ Unless otherwise specified, any issue that does not have an assignee can be clai Please leave a comment in the issue indicating interest so that you can be designated as the assignee by a member. This is a requirement in Github to allow asssignment of contributors outside of the organization by members. -If further discussion is needed before work can be started, please make use of the `#design` channel on the discord. +If further discussion is needed before work can be started, please make use of the programming channels on the discord. ## Development Environment @@ -40,7 +40,7 @@ Switch to feature branch. Make changes in feature branch. Once completed, run tests. -`php artisan config:clear && php artisan test` +`php artisan config:clear && php artisan test --parallel` Execute static code analysis. @@ -77,7 +77,7 @@ Create Pull Request from your new feature branch to upstream release branch. Once the Pull Request is merged, delete the feature branch locally. -`git branch -d new-feature-branch` +`git branch --delete new-feature-branch` Delete the remote feature branch. diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..365125485 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,46 @@ +name: Static Analysis + +on: push + +jobs: + static-analysis: + + runs-on: ubuntu-latest + + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, phpunit + - uses: actions/checkout@v2 + - name: Copy .env + run: php -r "file_exists('.env') || copy('.env.example', '.env');" + - name: Install Dependencies + env: + NOVA_USERNAME: ${{ secrets.NOVA_USERNAME }} + NOVA_PASSWORD: ${{ secrets.NOVA_PASSWORD }} + ENLIGHTN_USERNAME: ${{ secrets.ENLIGHTN_USERNAME }} + ENLIGHTN_PASSWORD: ${{ secrets.ENLIGHTN_PASSWORD }} + run: | + composer config "http-basic.nova.laravel.com" "$NOVA_USERNAME" "$NOVA_PASSWORD" + composer config "http-basic.satis.laravel-enlightn.com" "$ENLIGHTN_USERNAME" "$ENLIGHTN_PASSWORD" + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + - name: Generate key + run: php artisan key:generate + - name: Install NPM dependencies + run: npm install + - name: Compile assets + run: npm run production + - name: Directory Permissions + run: chmod -R 777 storage bootstrap/cache + - name: Create Database + run: | + mkdir -p database + touch database/database.sqlite + - name: Execute tests (Unit and Feature tests) via PHPUnit + env: + DB_CONNECTION: sqlite + DB_DATABASE: database/database.sqlite + run: | + php artisan optimize:clear + ./vendor/bin/phpstan analyse --memory-limit=-1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..ec15bd98a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +name: Tests + +on: push + +jobs: + test: + + runs-on: ubuntu-latest + + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, phpunit + - uses: actions/checkout@v2 + - name: Copy .env + run: php -r "file_exists('.env') || copy('.env.example', '.env');" + - name: Install Dependencies + env: + NOVA_USERNAME: ${{ secrets.NOVA_USERNAME }} + NOVA_PASSWORD: ${{ secrets.NOVA_PASSWORD }} + ENLIGHTN_USERNAME: ${{ secrets.ENLIGHTN_USERNAME }} + ENLIGHTN_PASSWORD: ${{ secrets.ENLIGHTN_PASSWORD }} + run: | + composer config "http-basic.nova.laravel.com" "$NOVA_USERNAME" "$NOVA_PASSWORD" + composer config "http-basic.satis.laravel-enlightn.com" "$ENLIGHTN_USERNAME" "$ENLIGHTN_PASSWORD" + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + - name: Generate key + run: php artisan key:generate + - name: Install NPM dependencies + run: npm install + - name: Compile assets + run: npm run production + - name: Directory Permissions + run: chmod -R 777 storage bootstrap/cache + - name: Create Database + run: | + mkdir -p database + touch database/database.sqlite + - name: Execute tests (Unit and Feature tests) via PHPUnit + env: + DB_CONNECTION: sqlite + DB_DATABASE: database/database.sqlite + run: | + php artisan optimize:clear + php artisan test diff --git a/.gitignore b/.gitignore index e82d98c9c..e24eb0a45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /node_modules /public/hot /public/storage -/public/img/grill /storage/*.key /vendor .env @@ -11,4 +10,10 @@ Homestead.json Homestead.yaml npm-debug.log yarn-error.log -.vscode \ No newline at end of file +.vscode +.idea +auth.json +*.sqlite +*.sqlite3 +public/css/app.css +public/js/app.js diff --git a/.styleci.yml b/.styleci.yml index c006fecfd..df3a5282d 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,7 +1,5 @@ +version: 8.1 preset: laravel -disabled: - - unused_use finder: not-name: - index.php - - server.php diff --git a/README.md b/README.md index eedf58067..e1dd09a2b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@

-StyleCI +tests +static-analysis +StyleCI - +

@@ -10,6 +12,8 @@ This is the repository for the server application that is responsible for AnimeThemes.moe resource management, API, and other services related to serving the AnimeThemes database. +This project is powered by [**Laravel**](https://laravel.com/), a PHP framework for web artisans. + ## Installation Please review the [**Installation Guide**](https://github.com/AnimeThemes/animethemes-server/wiki/Installation) in the wiki for detailed instructions. diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php new file mode 100644 index 000000000..23e2ece7e --- /dev/null +++ b/app/Actions/Fortify/CreateNewUser.php @@ -0,0 +1,46 @@ + ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique(User::TABLE)], + 'password' => Password::required(), + 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['required'] : '', + ])->validate(); + + return User::query()->create([ + User::ATTRIBUTE_NAME => Arr::get($input, 'name'), + User::ATTRIBUTE_EMAIL => Arr::get($input, 'email'), + User::ATTRIBUTE_PASSWORD => Hash::make(Arr::get($input, 'password')), + ]); + } +} diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php new file mode 100644 index 000000000..f14a982d0 --- /dev/null +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -0,0 +1,39 @@ + Password::required(), + ])->validate(); + + $user->forceFill([ + User::ATTRIBUTE_PASSWORD => Hash::make(Arr::get($input, 'password')), + ])->save(); + } +} diff --git a/app/Actions/Fortify/UpdateUserPassword.php b/app/Actions/Fortify/UpdateUserPassword.php new file mode 100644 index 000000000..4388a908f --- /dev/null +++ b/app/Actions/Fortify/UpdateUserPassword.php @@ -0,0 +1,46 @@ + ['required', 'string'], + 'password' => Password::required(), + ])->after(function (IlluminateValidator $validator) use ($user, $input) { + if (! isset($input['current_password']) || ! Hash::check($input['current_password'], $user->password)) { + $validator->errors() + ->add('current_password', __('The provided password does not match your current password.')); + } + })->validateWithBag('updatePassword'); + + $user->forceFill([ + User::ATTRIBUTE_PASSWORD => Hash::make(Arr::get($input, 'password')), + ])->save(); + } +} diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php new file mode 100644 index 000000000..64125e614 --- /dev/null +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -0,0 +1,68 @@ + ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'max:255', Rule::unique(User::TABLE)->ignore($user->id)], + 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], + ])->validateWithBag('updateProfileInformation'); + + if (isset($input['photo'])) { + $user->updateProfilePhoto($input['photo']); + } + + if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) { + $this->updateVerifiedUser($user, $input); + } else { + $user->forceFill([ + User::ATTRIBUTE_NAME => Arr::get($input, 'name'), + User::ATTRIBUTE_EMAIL => Arr::get($input, 'email'), + ])->save(); + } + } + + /** + * Update the given verified user's profile information. + * + * @param mixed $user + * @param array $input + * @return void + */ + protected function updateVerifiedUser(mixed $user, array $input): void + { + $user->forceFill([ + User::ATTRIBUTE_NAME => Arr::get($input, 'name'), + User::ATTRIBUTE_EMAIL => Arr::get($input, 'email'), + User::ATTRIBUTE_EMAIL_VERIFIED_AT => null, + ])->save(); + + $user->sendEmailVerificationNotification(); + } +} diff --git a/app/Actions/Jetstream/DeleteUser.php b/app/Actions/Jetstream/DeleteUser.php new file mode 100644 index 000000000..fb5384eeb --- /dev/null +++ b/app/Actions/Jetstream/DeleteUser.php @@ -0,0 +1,28 @@ +tokens->each->delete(); + $user->delete(); + }); + } +} diff --git a/app/Concerns/DetectsRedis.php b/app/Concerns/DetectsRedis.php new file mode 100644 index 000000000..b49f355cd --- /dev/null +++ b/app/Concerns/DetectsRedis.php @@ -0,0 +1,26 @@ +newInstance($model->getOriginal()); + + $changedAttributes = $this->getChangedAttributes($model); + + $this->addEmbedFields($original, $model, $changedAttributes); + } + + /** + * Get changed attributes. + * + * @param Model $model + * @return Collection + */ + protected function getChangedAttributes(Model $model): Collection + { + return collect($model->getChanges()) + ->forget($model->getCreatedAtColumn()) + ->forget($model->getUpdatedAtColumn()) + ->keys(); + } + + /** + * Get model attribute value. + * + * @param Model $model + * @param mixed $attribute + * @return mixed + */ + protected function getAttributeValue(Model $model, mixed $attribute): mixed + { + // Hide field from embed by obscuring the values + if (in_array($attribute, $model->getHidden())) { + return DiscordEmbedField::DEFAULT_FIELD_VALUE; + } + + return $model->getAttribute($attribute); + } + + /** + * Add Embed Fields. + * + * @param Model $original + * @param Model $changed + * @param Collection $changedAttributes + * @return void + */ + protected function addEmbedFields(Model $original, Model $changed, Collection $changedAttributes): void + { + foreach ($changedAttributes as $attribute) { + $this->addEmbedField(new DiscordEmbedField('Attribute', $attribute, true)); + $this->addEmbedField(new DiscordEmbedField('Old', $this->getAttributeValue($original, $attribute), true)); + $this->addEmbedField(new DiscordEmbedField('New', $this->getAttributeValue($changed, $attribute), true)); + } + } +} diff --git a/app/Concerns/Discord/HasDiscordEmbedFields.php b/app/Concerns/Discord/HasDiscordEmbedFields.php new file mode 100644 index 000000000..7046fd560 --- /dev/null +++ b/app/Concerns/Discord/HasDiscordEmbedFields.php @@ -0,0 +1,41 @@ +embedFields[] = $embedField; + } + + /** + * Get discord embed fields. + * + * @return DiscordEmbedField[] + */ + protected function getEmbedFields(): array + { + return $this->embedFields; + } +} diff --git a/app/Concerns/Http/Requests/Api/ValidatesConditionally.php b/app/Concerns/Http/Requests/Api/ValidatesConditionally.php new file mode 100644 index 000000000..438af73d5 --- /dev/null +++ b/app/Concerns/Http/Requests/Api/ValidatesConditionally.php @@ -0,0 +1,53 @@ +handleConditionalValidation($validator); + } + + /** + * Configure conditional validation. + * + * @param Validator $validator + * @return void + */ + protected function handleConditionalValidation(Validator $validator): void + { + $this->conditionallyRestrictAllowedFilterValues($validator); + } + + /** + * Filters shall be validated based on values. + * If the value contains a separator, we assume this is a multi-value filter that builds a where in clause. + * Otherwise, we assume this is a single-value filter that builds a where clause. + * Logical operators apply to specific clauses, so we must check formatted filter parameters against filter values. + * + * @param Validator $validator + * @return void + */ + protected function conditionallyRestrictAllowedFilterValues(Validator $validator): void + { + // + } +} diff --git a/app/Concerns/Http/Requests/Api/ValidatesFields.php b/app/Concerns/Http/Requests/Api/ValidatesFields.php new file mode 100644 index 000000000..04cdc9585 --- /dev/null +++ b/app/Concerns/Http/Requests/Api/ValidatesFields.php @@ -0,0 +1,33 @@ + + */ + protected function restrictAllowedFieldValues(SchemaInterface $schema): array + { + return $this->restrictAllowedValues( + Str::of(FieldParser::param())->append('.')->append($schema->type())->__toString(), + Arr::map($schema->fields(), fn (FieldInterface $field) => $field->getKey()) + ); + } +} diff --git a/app/Concerns/Http/Requests/Api/ValidatesFilters.php b/app/Concerns/Http/Requests/Api/ValidatesFilters.php new file mode 100644 index 000000000..257a9257f --- /dev/null +++ b/app/Concerns/Http/Requests/Api/ValidatesFilters.php @@ -0,0 +1,140 @@ +filters() as $filter) { + $schemaFilters = array_merge( + $schemaFilters, + $this->getFilterFormats($filter, BinaryLogicalOperator::getInstances()), + $this->getFilterFormats($filter, UnaryLogicalOperator::getInstances()) + ); + } + + return array_unique(array_filter($schemaFilters)); + } + + /** + * Get the allowed list of filter keys with possible conditions. + * + * @param Filter $filter + * @param LogicalOperator[] $logicalOperators + * @return string[] + */ + protected function getFilterFormats(Filter $filter, array $logicalOperators): array + { + $formattedFilters = []; + + foreach ($logicalOperators as $binaryLogicalOperator) { + foreach ($filter->getAllowedComparisonOperators() as $allowedComparisonOperator) { + $formattedFilters[] = $filter->format($binaryLogicalOperator, $allowedComparisonOperator); + } + $formattedFilters[] = $filter->format($binaryLogicalOperator); + } + + foreach ($filter->getAllowedComparisonOperators() as $allowedComparisonOperator) { + $formattedFilters[] = $filter->format(null, $allowedComparisonOperator); + } + + $formattedFilters[] = $filter->format(); + + return array_unique(array_filter($formattedFilters)); + } + + /** + * Get possible qualified parameter values for formatted filter. + * + * @param SchemaInterface $schema + * @param string $formattedFilter + * @return string[] + */ + protected function getFormattedParameters(SchemaInterface $schema, string $formattedFilter): array + { + return [ + Str::of(FilterParser::param()) + ->append('.') + ->append($formattedFilter) + ->__toString(), + + Str::of(FilterParser::param()) + ->append('.') + ->append($schema->type()) + ->append('.') + ->append($formattedFilter) + ->__toString(), + ]; + } + + /** + * Restrict filter based on allowed formats and provided values. + * + * @param Validator $validator + * @param SchemaInterface $schema + * @param Filter $filter + * @return void + */ + protected function conditionallyRestrictFilter(Validator $validator, SchemaInterface $schema, Filter $filter): void + { + $singleValueFilterFormats = $this->getFilterFormats($filter, BinaryLogicalOperator::getInstances()); + foreach ($singleValueFilterFormats as $singleValueFilterFormat) { + foreach ($this->getFormattedParameters($schema, $singleValueFilterFormat) as $formattedParameter) { + if (collect($validator->getRules())->keys()->doesntContain($formattedParameter)) { + $validator->sometimes( + $formattedParameter, + $filter->getRules(), + fn (Fluent $fluent) => is_string(Arr::get($fluent->toArray(), $formattedParameter)) && ! Str::of(Arr::get($fluent->toArray(), $formattedParameter))->contains(Criteria::VALUE_SEPARATOR) + ); + } + } + } + + $multiValueRules = []; + foreach ($filter->getRules() as $rule) { + $multiValueRules[] = new Delimited($rule); + } + + $multiValueFilterFormats = $this->getFilterFormats($filter, UnaryLogicalOperator::getInstances()); + foreach ($multiValueFilterFormats as $multiValueFilterFormat) { + foreach ($this->getFormattedParameters($schema, $multiValueFilterFormat) as $formattedParameter) { + if (collect($validator->getRules())->keys()->doesntContain($formattedParameter)) { + $validator->sometimes( + $formattedParameter, + $multiValueRules, + fn (Fluent $fluent) => is_string(Arr::get($fluent->toArray(), $formattedParameter)) && Str::of(Arr::get($fluent->toArray(), $formattedParameter))->contains(Criteria::VALUE_SEPARATOR) + ); + } + } + } + } +} diff --git a/app/Concerns/Http/Requests/Api/ValidatesIncludes.php b/app/Concerns/Http/Requests/Api/ValidatesIncludes.php new file mode 100644 index 000000000..e9aad3054 --- /dev/null +++ b/app/Concerns/Http/Requests/Api/ValidatesIncludes.php @@ -0,0 +1,32 @@ + + */ + protected function restrictAllowedIncludeValues(string $param, SchemaInterface $schema): array + { + return $this->restrictAllowedValues( + $param, + Arr::map($schema->allowedIncludes(), fn (AllowedInclude $include) => $include->path()) + ); + } +} diff --git a/app/Concerns/Http/Requests/Api/ValidatesPaging.php b/app/Concerns/Http/Requests/Api/ValidatesPaging.php new file mode 100644 index 000000000..204c8d79d --- /dev/null +++ b/app/Concerns/Http/Requests/Api/ValidatesPaging.php @@ -0,0 +1,71 @@ + + */ + protected function offset(): array + { + return array_merge( + $this->restrictAllowedTypes(PagingParser::param(), [OffsetCriteria::NUMBER_PARAM, OffsetCriteria::SIZE_PARAM]), + $this->min(Str::of(PagingParser::param())->append('.')->append(OffsetCriteria::NUMBER_PARAM)->__toString()), + $this->range(Str::of(PagingParser::param())->append('.')->append(OffsetCriteria::SIZE_PARAM)->__toString()), + ); + } + + /** + * Validate limit pagination. + * + * @return array + */ + protected function limit(): array + { + return array_merge( + $this->restrictAllowedTypes(PagingParser::param(), [LimitCriteria::PARAM]), + $this->range(Str::of(PagingParser::param())->append('.')->append(LimitCriteria::PARAM)->__toString()), + ); + } + + /** + * Validate minimum value for optional field. + * + * @param string $param + * @param int $min + * @return array + */ + protected function min(string $param, int $min = 1): array + { + return $this->optional($param, ['integer', "min:$min"]); + } + + /** + * Validate minimum and maximum value for optional field. + * + * @param string $param + * @param int $min + * @param int $max + * @return array + */ + protected function range(string $param, int $min = 1, int $max = Criteria::MAX_RESULTS): array + { + return $this->optional($param, ['integer', "min:$min", "max:$max"]); + } +} diff --git a/app/Concerns/Http/Requests/Api/ValidatesParameters.php b/app/Concerns/Http/Requests/Api/ValidatesParameters.php new file mode 100644 index 000000000..a25c058e5 --- /dev/null +++ b/app/Concerns/Http/Requests/Api/ValidatesParameters.php @@ -0,0 +1,105 @@ +|string[] $types + * @return array + */ + protected function restrictAllowedTypes(string $param, Arrayable|array $types): array + { + if ($types instanceof Arrayable) { + $types = $types->toArray(); + } + + $types = implode(',', $types); + + return [ + $param => [ + 'nullable', + "array:$types", + ], + ]; + } + + /** + * Restrict the allowed values for the parameter. + * + * @param string $param + * @param Arrayable|string[]|string $values + * @param array $customRules + * @return array + */ + protected function restrictAllowedValues(string $param, Arrayable|array|string $values, array $customRules = []): array + { + return [ + $param => array_merge( + ['bail', 'sometimes', 'required', 'string', new Delimited(Rule::in($values))], + $customRules, + ), + ]; + } + + /** + * Prohibit the parameter. + * + * @param string $param + * @return array + */ + protected function prohibit(string $param): array + { + return [ + $param => [ + 'prohibited', + ], + ]; + } + + /** + * Optional parameter. + * + * @param string $param + * @param array $customRules + * @return array + */ + protected function optional(string $param, array $customRules = []): array + { + return [ + $param => array_merge( + ['sometimes', 'required'], + $customRules, + ), + ]; + } + + /** + * Require the parameter. + * + * @param string $param + * @param array $customRules + * @return array + */ + protected function require(string $param, array $customRules = []): array + { + return [ + $param => array_merge( + ['required'], + $customRules, + ), + ]; + } +} diff --git a/app/Concerns/Http/Requests/Api/ValidatesSorts.php b/app/Concerns/Http/Requests/Api/ValidatesSorts.php new file mode 100644 index 000000000..207b2dab4 --- /dev/null +++ b/app/Concerns/Http/Requests/Api/ValidatesSorts.php @@ -0,0 +1,53 @@ +sorts() as $sort) { + foreach (Direction::getInstances() as $direction) { + $allowedSorts[] = $sort->format($direction); + } + } + + return array_unique($allowedSorts); + } + + /** + * Restrict allowed sorts for schema. + * + * @param string $param + * @param SchemaInterface $schema + * @return array + */ + protected function restrictAllowedSortValues(string $param, SchemaInterface $schema): array + { + return $this->restrictAllowedValues( + $param, + $this->formatAllowedSortValues($schema), + [new DistinctIgnoringDirectionRule(), new RandomSoleRule()] + ); + } +} diff --git a/app/Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php b/app/Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php new file mode 100644 index 000000000..8e7a3cb0f --- /dev/null +++ b/app/Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php @@ -0,0 +1,87 @@ + $first->date->format(AllowedDateFormat::YM) <=> $second->date->format(AllowedDateFormat::YM); + } + + /** + * The columns used for update set operation. + * + * @return string[] + */ + protected function columnsForUpdate(): array + { + return [ + Balance::ATTRIBUTE_BALANCE, + Balance::ATTRIBUTE_DATE, + Balance::ATTRIBUTE_ID, + Balance::ATTRIBUTE_SERVICE, + Balance::ATTRIBUTE_USAGE, + ]; + } + + /** + * Callback for update set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForUpdate(): Closure + { + return fn (Balance $first, Balance $second) => [$first->date->format(AllowedDateFormat::YMD), $first->usage, $first->balance] + <=> [$second->date->format(AllowedDateFormat::YMD), $second->usage, $second->balance]; + } + + /** + * Get source model that has been updated for destination model. + * + * @param Collection $sourceModels + * @param Model $destinationModel + * @return Model|null + */ + protected function resolveUpdatedModel(Collection $sourceModels, Model $destinationModel): ?Model + { + $formattedDestinationDate = $destinationModel->getAttribute(Balance::ATTRIBUTE_DATE)->format(AllowedDateFormat::YM); + + return $sourceModels->first( + fn (Balance $balance) => $balance->date->format(AllowedDateFormat::YM) === $formattedDestinationDate + ); + } +} diff --git a/app/Concerns/Repositories/Billing/ReconcilesTransactionRepositories.php b/app/Concerns/Repositories/Billing/ReconcilesTransactionRepositories.php new file mode 100644 index 000000000..31a59e292 --- /dev/null +++ b/app/Concerns/Repositories/Billing/ReconcilesTransactionRepositories.php @@ -0,0 +1,45 @@ + [$first->external_id, $first->date->format(AllowedDateFormat::YMD), $first->amount] + <=> [$second->external_id, $second->date->format(AllowedDateFormat::YMD), $second->amount]; + } +} diff --git a/app/Concerns/Repositories/ReconcilesRepositories.php b/app/Concerns/Repositories/ReconcilesRepositories.php new file mode 100644 index 000000000..c2f6a1bda --- /dev/null +++ b/app/Concerns/Repositories/ReconcilesRepositories.php @@ -0,0 +1,341 @@ +hasChanges() || $this->hasFailures(); + } + + /** + * Determines if any successful changes were made during reconciliation. + * + * @return bool + */ + protected function hasChanges(): bool + { + return $this->created > 0 || $this->deleted > 0 || $this->updated > 0; + } + + /** + * Determines if any unsuccessful changes were made during reconciliation. + * + * @return bool + */ + protected function hasFailures(): bool + { + return $this->createdFailed > 0 || $this->deletedFailed > 0 || $this->updatedFailed > 0; + } + + /** + * Perform set reconciliation between source and destination repositories. + * + * @param Repository $source + * @param Repository $destination + * @return void + */ + public function reconcileRepositories(Repository $source, Repository $destination): void + { + try { + $sourceModels = $source->get(); + + $destinationModels = $destination->get($this->columnsForCreateDelete()); + + $this->createModelsFromSource($destination, $sourceModels, $destinationModels); + + $this->deleteModelsFromDestination($destination, $sourceModels, $destinationModels); + + $destinationModels = $destination->get($this->columnsForUpdate()); + + $this->updateDestinationModels($destination, $sourceModels, $destinationModels); + } catch (Exception $exception) { + $this->handleException($exception); + } finally { + $this->postReconciliationTask(); + } + } + + /** + * The columns used for create and delete set operations. + * + * @return string[] + */ + protected function columnsForCreateDelete(): array + { + return ['*']; + } + + /** + * Callback for create and delete set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForCreateDelete(): Closure + { + return fn () => 0; + } + + /** + * Create models that exist in source but not in destination. + * + * @param Repository $destination + * @param Collection $sourceModels + * @param Collection $destinationModels + * @return void + */ + protected function createModelsFromSource( + Repository $destination, + Collection $sourceModels, + Collection $destinationModels + ): void { + $createModels = $sourceModels->diffUsing($destinationModels, $this->diffCallbackForCreateDelete()); + + foreach ($createModels as $createModel) { + $createResult = $destination->save($createModel); + if ($createResult) { + $this->created++; + $this->handleCreated($createModel); + } else { + $this->createdFailed++; + $this->handleFailedCreation($createModel); + } + } + } + + /** + * Delete models that exist in destination but not in source. + * + * @param Repository $destination + * @param Collection $sourceModels + * @param Collection $destinationModels + * @return void + */ + protected function deleteModelsFromDestination( + Repository $destination, + Collection $sourceModels, + Collection $destinationModels + ): void { + $deleteModels = $destinationModels->diffUsing($sourceModels, $this->diffCallbackForCreateDelete()); + + foreach ($deleteModels as $deleteModel) { + $deleteResult = $destination->delete($deleteModel); + if ($deleteResult) { + $this->deleted++; + $this->handleDeleted($deleteModel); + } else { + $this->deletedFailed++; + $this->handleFailedDeletion($deleteModel); + } + } + } + + /** + * The columns used for update set operation. + * + * @return string[] + */ + protected function columnsForUpdate(): array + { + return ['*']; + } + + /** + * Callback for update set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForUpdate(): Closure + { + return fn () => 0; + } + + /** + * Get source model that has been updated for destination model. + * + * @param Collection $sourceModels + * @param Model $destinationModel + * @return Model|null + */ + protected function resolveUpdatedModel(Collection $sourceModels, Model $destinationModel): ?Model + { + return null; + } + + /** + * Update destination models that have changed in source. + * + * @param Repository $destination + * @param Collection $sourceModels + * @param Collection $destinationModels + * @return void + */ + protected function updateDestinationModels( + Repository $destination, + Collection $sourceModels, + Collection $destinationModels + ): void { + $updatedModels = $destinationModels->diffUsing($sourceModels, $this->diffCallbackForUpdate()); + + foreach ($updatedModels as $updatedModel) { + $sourceModel = $this->resolveUpdatedModel($sourceModels, $updatedModel); + if ($sourceModel !== null) { + $updateResult = $destination->update($updatedModel, $sourceModel->toArray()); + if ($updateResult) { + $this->updated++; + $this->handleUpdated($updatedModel); + } else { + $this->updatedFailed++; + $this->handleFailedUpdate($updatedModel); + } + } + } + } +} diff --git a/app/Concerns/Repositories/Wiki/ReconcilesVideoRepositories.php b/app/Concerns/Repositories/Wiki/ReconcilesVideoRepositories.php new file mode 100644 index 000000000..67c41254d --- /dev/null +++ b/app/Concerns/Repositories/Wiki/ReconcilesVideoRepositories.php @@ -0,0 +1,82 @@ + $first->basename <=> $second->basename; + } + + /** + * The columns used for update set operation. + * + * @return string[] + */ + protected function columnsForUpdate(): array + { + return [ + Video::ATTRIBUTE_BASENAME, + Video::ATTRIBUTE_ID, + Video::ATTRIBUTE_PATH, + Video::ATTRIBUTE_SIZE, + ]; + } + + /** + * Callback for update set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForUpdate(): Closure + { + return fn (Video $first, Video $second) => [$first->basename, $first->path, $first->size] <=> [$second->basename, $second->path, $second->size]; + } + + /** + * Get source model that has been updated for destination model. + * + * @param Collection $sourceModels + * @param Model $destinationModel + * @return Model|null + */ + protected function resolveUpdatedModel(Collection $sourceModels, Model $destinationModel): ?Model + { + return $sourceModels->firstWhere( + Video::ATTRIBUTE_BASENAME, + $destinationModel->getAttribute(Video::ATTRIBUTE_BASENAME) + ); + } +} diff --git a/app/Console/Commands/AnnouncementCreateCommand.php b/app/Console/Commands/AnnouncementCreateCommand.php deleted file mode 100644 index fb3867810..000000000 --- a/app/Console/Commands/AnnouncementCreateCommand.php +++ /dev/null @@ -1,86 +0,0 @@ -option('alias'); - if (empty($alias)) { - LOG::error('alias is required'); - $this->error('alias is required'); - - return; - } - - // Content is required - $content = $this->option('content'); - if (empty($content)) { - LOG::error('content is required'); - $this->error('content is required'); - - return; - } - - // Alias should be unique - $announcement = Announcement::where('alias', $alias)->first(); - if ($announcement) { - LOG::error("Announcement '{$alias}' already exists"); - $this->error("Announcement '{$alias}' already exists"); - - return; - } - - // Create the Announcement - $result = Announcement::create([ - 'alias' => $alias, - 'content' => $content, - ]); - - // Confirm if Announcement was created - if ($result->exists()) { - LOG::info("Announcement '{$alias}' created"); - $this->info("Announcement '{$alias}' created"); - } else { - LOG::error("Announcement '{$alias}' was not created"); - $this->error("Announcement '{$alias}' was not created"); - } - } -} diff --git a/app/Console/Commands/AnnouncementDeleteCommand.php b/app/Console/Commands/AnnouncementDeleteCommand.php deleted file mode 100644 index a9c483d08..000000000 --- a/app/Console/Commands/AnnouncementDeleteCommand.php +++ /dev/null @@ -1,73 +0,0 @@ -option('alias'); - if (empty($alias)) { - LOG::error('alias is required'); - $this->error('alias is required'); - - return; - } - - // Announcement must exist to be deleted - $announcement = Announcement::where('alias', $alias)->first(); - if (! $announcement) { - LOG::error("Announcement '{$alias}' does not exist"); - $this->error("Announcement '{$alias}' does not exist"); - - return; - } - - // Delete the Announcement - $result = $announcement->delete(); - - // Confirm deletion of announcement - if ($result) { - LOG::info("Announcement '{$alias}' deleted"); - $this->info("Announcement '{$alias}' deleted"); - } else { - LOG::error("Announcement '{$alias}' was not deleted"); - $this->error("Announcement '{$alias}' was not deleted"); - } - } -} diff --git a/app/Console/Commands/AnnouncementListCommand.php b/app/Console/Commands/AnnouncementListCommand.php deleted file mode 100644 index 2cd5c12ba..000000000 --- a/app/Console/Commands/AnnouncementListCommand.php +++ /dev/null @@ -1,48 +0,0 @@ -toArray(); - - $this->table($headers, $announcements); - } -} diff --git a/app/Console/Commands/AnnouncementReadCommand.php b/app/Console/Commands/AnnouncementReadCommand.php deleted file mode 100644 index 23b64ab03..000000000 --- a/app/Console/Commands/AnnouncementReadCommand.php +++ /dev/null @@ -1,73 +0,0 @@ -option('alias'); - if (empty($alias)) { - LOG::error('alias is required'); - $this->error('alias is required'); - - return; - } - - // Announcement must exist to be read - $announcement = Announcement::where('alias', $alias)->first(); - if (! $announcement) { - LOG::error("Announcement '{$alias}' does not exist"); - $this->error("Announcement '{$alias}' does not exist"); - - return; - } - - // Display Announcement as table - $headers = ['Alias', 'Content']; - - $data = [ - [ - 'alias' => $announcement->alias, - 'content' => $announcement->content, - ], - ]; - - $this->table($headers, $data); - } -} diff --git a/app/Console/Commands/AnnouncementUpdateCommand.php b/app/Console/Commands/AnnouncementUpdateCommand.php deleted file mode 100644 index 3f1d45c2b..000000000 --- a/app/Console/Commands/AnnouncementUpdateCommand.php +++ /dev/null @@ -1,85 +0,0 @@ -option('alias'); - if (empty($alias)) { - LOG::error('alias is required'); - $this->error('alias is required'); - - return; - } - - // Content is not required - $content = $this->option('content'); - - // Announcement must exist to be updated - $announcement = Announcement::where('alias', $alias)->first(); - if (! $announcement) { - LOG::error("Announcement '{$alias}' does not exist"); - $this->error("Announcement '{$alias}' does not exist"); - - return; - } - - // Update Content if set - if (! empty($content)) { - $announcement->content = $content; - } - - // Save changes if needed - $result = false; - if ($announcement->isDirty()) { - $result = $announcement->save(); - } - - // Confirm if changes were made - if ($result) { - LOG::info("Announcement '{$alias}' updated"); - $this->info("Announcement '{$alias}' updated"); - } else { - LOG::error("Announcement '{$alias}' was not updated"); - $this->error("Announcement '{$alias}' was not updated"); - } - } -} diff --git a/app/Console/Commands/Billing/Balance/BalanceReconcileCommand.php b/app/Console/Commands/Billing/Balance/BalanceReconcileCommand.php new file mode 100644 index 000000000..5c145c595 --- /dev/null +++ b/app/Console/Commands/Billing/Balance/BalanceReconcileCommand.php @@ -0,0 +1,172 @@ +hasResults()) { + if ($this->hasChanges()) { + Log::info("$this->created Balances created, $this->deleted Balances deleted, $this->updated Balances updated"); + $this->info("$this->created Balances created, $this->deleted Balances deleted, $this->updated Balances updated"); + } + if ($this->hasFailures()) { + Log::error("Failed to create $this->createdFailed Balances, delete $this->deletedFailed Balances, update $this->updatedFailed Balances"); + $this->error("Failed to create $this->createdFailed Balances, delete $this->deletedFailed Balances, update $this->updatedFailed Balances"); + } + } else { + Log::info('No Balances created or deleted or updated'); + $this->info('No Balances created or deleted or updated'); + } + } + + /** + * Handler for successful balance creation. + * + * @param BaseModel $model + * @return void + */ + protected function handleCreated(BaseModel $model): void + { + Log::info("Balance '{$model->getName()}' created"); + $this->info("Balance '{$model->getName()}' created"); + } + + /** + * Handler for failed balance creation. + * + * @param BaseModel $model + * @return void + */ + protected function handleFailedCreation(BaseModel $model): void + { + Log::error("Balance '{$model->getName()}' was not created"); + $this->error("Balance '{$model->getName()}' was not created"); + } + + /** + * Handler for successful balance deletion. + * + * @param BaseModel $model + * @return void + */ + protected function handleDeleted(BaseModel $model): void + { + Log::info("Balance '{$model->getName()}' deleted"); + $this->info("Balance '{$model->getName()}' deleted"); + } + + /** + * Handler for failed balance deletion. + * + * @param BaseModel $model + * @return void + */ + protected function handleFailedDeletion(BaseModel $model): void + { + Log::error("Balance '{$model->getName()}' was not deleted"); + $this->error("Balance '{$model->getName()}' was not deleted"); + } + + /** + * Handler for successful balance update. + * + * @param BaseModel $model + * @return void + */ + protected function handleUpdated(BaseModel $model): void + { + Log::info("Balance '{$model->getName()}' updated"); + $this->info("Balance '{$model->getName()}' updated"); + } + + /** + * Handler for failed balance update. + * + * @param BaseModel $model + * @return void + */ + protected function handleFailedUpdate(BaseModel $model): void + { + Log::error("Balance '{$model->getName()}' was not updated"); + $this->error("Balance '{$model->getName()}' was not updated"); + } + + /** + * Handler for exception. + * + * @param Exception $exception + * @return void + */ + protected function handleException(Exception $exception): void + { + Log::error($exception->getMessage()); + $this->error($exception->getMessage()); + } + + /** + * Get source repository for service. + * + * @param Service $service + * @return Repository|null + */ + protected function getSourceRepository(Service $service): ?Repository + { + return match ($service->value) { + Service::DIGITALOCEAN => App::make(DigitalOceanSourceRepository::class), + default => null, + }; + } + + /** + * Get destination repository for service. + * + * @param Service $service + * @return Repository|null + */ + protected function getDestinationRepository(Service $service): ?Repository + { + return match ($service->value) { + Service::DIGITALOCEAN => App::make(DigitalOceanDestinationRepository::class), + default => null, + }; + } +} diff --git a/app/Console/Commands/Billing/ServiceReconcileCommand.php b/app/Console/Commands/Billing/ServiceReconcileCommand.php new file mode 100644 index 000000000..7a5cfaa4d --- /dev/null +++ b/app/Console/Commands/Billing/ServiceReconcileCommand.php @@ -0,0 +1,80 @@ +argument('service'); + $service = Service::coerce(Str::upper($key)); + + if ($service === null) { + Log::error("Invalid Service '$key'"); + $this->error("Invalid Service '$key'"); + + return 1; + } + + $sourceRepository = $this->getSourceRepository($service); + if ($sourceRepository === null) { + Log::error("No source repository implemented for Service '$key'"); + $this->error("No source repository implemented for Service '$key'"); + + return 1; + } + + $destinationRepository = $this->getDestinationRepository($service); + if ($destinationRepository === null) { + Log::error("No destination repository implemented for Service '$key'"); + $this->error("No destination repository implemented for Service '$key'"); + + return 1; + } + + $this->reconcileRepositories($sourceRepository, $destinationRepository); + + return 0; + } + + /** + * Perform set reconciliation between source and destination repositories. + * + * @param Repository $source + * @param Repository $destination + * @return void + */ + abstract public function reconcileRepositories(Repository $source, Repository $destination): void; + + /** + * Get source repository for service. + * + * @param Service $service + * @return Repository|null + */ + abstract protected function getSourceRepository(Service $service): ?Repository; + + /** + * Get destination repository for service. + * + * @param Service $service + * @return Repository|null + */ + abstract protected function getDestinationRepository(Service $service): ?Repository; +} diff --git a/app/Console/Commands/Billing/Transaction/TransactionReconcileCommand.php b/app/Console/Commands/Billing/Transaction/TransactionReconcileCommand.php new file mode 100644 index 000000000..88d36205d --- /dev/null +++ b/app/Console/Commands/Billing/Transaction/TransactionReconcileCommand.php @@ -0,0 +1,172 @@ +hasResults()) { + if ($this->hasChanges()) { + Log::info("$this->created Transactions created, $this->deleted Transactions deleted, $this->updated Transactions updated"); + $this->info("$this->created Transactions created, $this->deleted Transactions deleted, $this->updated Transactions updated"); + } + if ($this->hasFailures()) { + Log::error("Failed to create $this->createdFailed Transactions, delete $this->deletedFailed Transactions, update $this->updatedFailed Transactions"); + $this->error("Failed to create $this->createdFailed Transactions, delete $this->deletedFailed Transactions, update $this->updatedFailed Transactions"); + } + } else { + Log::info('No Transactions created or deleted or updated'); + $this->info('No Transactions created or deleted or updated'); + } + } + + /** + * Handler for successful transaction creation. + * + * @param Transaction $model + * @return void + */ + protected function handleCreated(Transaction $model): void + { + Log::info("Transaction '{$model->getName()}' created"); + $this->info("Transaction '{$model->getName()}' created"); + } + + /** + * Handler for failed transaction creation. + * + * @param Transaction $model + * @return void + */ + protected function handleFailedCreation(Transaction $model): void + { + Log::error("Transaction '{$model->getName()}' was not created"); + $this->error("Transaction '{$model->getName()}' was not created"); + } + + /** + * Handler for successful transaction deletion. + * + * @param Transaction $model + * @return void + */ + protected function handleDeleted(Transaction $model): void + { + Log::info("Transaction '{$model->getName()}' deleted"); + $this->info("Transaction '{$model->getName()}' deleted"); + } + + /** + * Handler for failed transaction deletion. + * + * @param Transaction $model + * @return void + */ + protected function handleFailedDeletion(Transaction $model): void + { + Log::error("Transaction '{$model->getName()}' was not deleted"); + $this->error("Transaction '{$model->getName()}' was not deleted"); + } + + /** + * Handler for successful transaction update. + * + * @param Transaction $model + * @return void + */ + protected function handleUpdated(Transaction $model): void + { + Log::info("Transaction '{$model->getName()}' updated"); + $this->info("Transaction '{$model->getName()}' updated"); + } + + /** + * Handler for failed transaction update. + * + * @param Transaction $model + * @return void + */ + protected function handleFailedUpdate(Transaction $model): void + { + Log::error("Transaction '{$model->getName()}' was not updated"); + $this->error("Transaction '{$model->getName()}' was not updated"); + } + + /** + * Handler for exception. + * + * @param Exception $exception + * @return void + */ + protected function handleException(Exception $exception): void + { + Log::error($exception->getMessage()); + $this->error($exception->getMessage()); + } + + /** + * Get source repository for service. + * + * @param Service $service + * @return Repository|null + */ + protected function getSourceRepository(Service $service): ?Repository + { + return match ($service->value) { + Service::DIGITALOCEAN => App::make(DigitalOceanSourceRepository::class), + default => null, + }; + } + + /** + * Get destination repository for service. + * + * @param Service $service + * @return Repository|null + */ + protected function getDestinationRepository(Service $service): ?Repository + { + return match ($service->value) { + Service::DIGITALOCEAN => App::make(DigitalOceanDestinationRepository::class), + default => null, + }; + } +} diff --git a/app/Console/Commands/DatabaseDumpCommand.php b/app/Console/Commands/DatabaseDumpCommand.php new file mode 100644 index 000000000..eaabb9fc6 --- /dev/null +++ b/app/Console/Commands/DatabaseDumpCommand.php @@ -0,0 +1,157 @@ +option('create'); + + try { + $connection = DB::connection(); + + $dumper = $this->getDumper($connection, $create); + if ($dumper === null) { + Log::error("Unrecognized connection '{$connection->getName()}'"); + $this->error("Unrecognized connection '{$connection->getName()}'"); + + return 1; + } + + if (! static::canIncludeTables($connection)) { + Log::error('DB connection does not support includeTables option'); + $this->error('DB connection does not support includeTables option'); + + return 1; + } + $dumper->includeTables($this->allowedTables()); + + $dumper->setDbName($connection->getDatabaseName()) + ->setUserName(strval($connection->getConfig('username'))) + ->setPassword(strval($connection->getConfig('password'))); + + $host = $connection->getConfig('host'); + if ($host !== null) { + $dumper->setHost(collect($host)->first()); + } + + $port = $connection->getConfig('port'); + if (is_int($port)) { + $dumper->setPort($port); + } + + $dumpFile = $this->getDumpFile($create); + + $dumper->dumpToFile($dumpFile); + + // Assume success if no exceptions were thrown + // The library will check if the file exists and is not empty + Log::info("Database dump '$dumpFile' has been created"); + $this->info("Database dump '$dumpFile' has been created"); + } catch (Exception $e) { + Log::error($e->getMessage()); + $this->error($e->getMessage()); + + return 1; + } + + return 0; + } + + /** + * Get the dumper for the database connection. + * + * @param ConnectionInterface $connection + * @param bool $create + * @return DbDumper|null + */ + protected function getDumper(ConnectionInterface $connection, bool $create): ?DbDumper + { + return match (get_class($connection)) { + SQLiteConnection::class => Sqlite::create(), + MySqlConnection::class => $create ? MySql::create() : MySql::create()->doNotCreateTables(), + PostgresConnection::class => $create ? PostgreSql::create() : PostgreSql::create()->doNotCreateTables(), + default => null, + }; + } + + /** + * Determine if the database connection supports table inclusion. + * + * @param ConnectionInterface $connection + * @return bool + */ + public static function canIncludeTables(ConnectionInterface $connection): bool + { + // Sqlite version 3.32.0 is required when using the includeTables option + if ($connection instanceof SQLiteConnection) { + return version_compare($connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), '3.32.0', '>='); + } + + return true; + } + + /** + * The target path for the database dump. + * Pattern: "/path/to/project/storage/db-dumps/{path}/animethemes-db-dump-{create?}-{year}-{month}-{day}.sql". + * + * @param bool $create + * @return string + */ + public function getDumpFile(bool $create = false): string + { + $filesystem = Storage::disk('db-dumps'); + + $filesystem->makeDirectory($this->getDumpFilePath()); + + return Str::of($filesystem->path($this->getDumpFilePath())) + ->append(DIRECTORY_SEPARATOR) + ->append('animethemes-db-dump-') + ->append($create ? 'create-' : '') + ->append(Date::now()->toDateString()) + ->append('.sql') + ->__toString(); + } + + /** + * The directory that the file should be dumped to. + * + * @return string + */ + abstract protected function getDumpFilePath(): string; + + /** + * The list of tables to include in the dump. + * + * @return array + */ + abstract protected function allowedTables(): array; +} diff --git a/app/Console/Commands/Document/DocumentDatabaseDumpCommand.php b/app/Console/Commands/Document/DocumentDatabaseDumpCommand.php new file mode 100644 index 000000000..f5e3ea499 --- /dev/null +++ b/app/Console/Commands/Document/DocumentDatabaseDumpCommand.php @@ -0,0 +1,50 @@ +option('hours'); + if (! is_numeric($hours)) { + Log::error("Invalid hours value '$hours'"); + $this->error("Invalid hours value '$hours'"); + + return 1; + } + + $this->prune( + Storage::disk('db-dumps'), + Date::now()->subHours(intval($hours)) + ); + + $this->printResults(); + + return 0; + } + + /** + * Prune database dumps in filesystem against date. + * + * @param Filesystem $filesystem + * @param Carbon $pruneDate + * @return void + */ + protected function prune(Filesystem $filesystem, Carbon $pruneDate): void + { + foreach ($filesystem->allFiles() as $path) { + $lastModified = Date::createFromTimestamp($filesystem->lastModified($path)); + if (Str::contains($path, 'animethemes-db-dump') && $lastModified->isBefore($pruneDate)) { + $result = $filesystem->delete($path); + if ($result) { + $this->deleted++; + Log::info("Deleted database dump '$path'"); + $this->info("Deleted database dump '$path'"); + } else { + $this->deletedFailed++; + Log::error("Failed to delete database dump '$path'"); + $this->error("Failed to delete database dump '$path'"); + } + } + } + } + + /** + * Print results to console and logs. + * + * @return void + */ + protected function printResults(): void + { + if ($this->hasResults()) { + if ($this->hasDeletions()) { + Log::info("$this->deleted database dumps deleted"); + $this->info("$this->deleted database dumps deleted"); + } + if ($this->hasFailures()) { + Log::error("Failed to delete $this->deletedFailed database dumps"); + $this->error("Failed to delete $this->deletedFailed database dumps"); + } + } else { + Log::info('No database dumps deleted'); + $this->info('No database dumps deleted'); + } + } + + /** + * Determines if any deletions, successful or not, were made during pruning. + * + * @return bool + */ + protected function hasResults(): bool + { + return $this->hasDeletions() || $this->hasFailures(); + } + + /** + * Determines if any successful deletions were made during pruning. + * + * @return bool + */ + protected function hasDeletions(): bool + { + return $this->deleted > 0; + } + + /** + * Determines if any unsuccessful deletions were attempted during pruning. + * + * @return bool + */ + protected function hasFailures(): bool + { + return $this->deletedFailed > 0; + } +} diff --git a/app/Console/Commands/VideoReconcileCommand.php b/app/Console/Commands/VideoReconcileCommand.php deleted file mode 100644 index 41a1b0670..000000000 --- a/app/Console/Commands/VideoReconcileCommand.php +++ /dev/null @@ -1,156 +0,0 @@ -listContents('', true); - - // Filter all objects for WebM metadata - // We don't want to filter on the remote filesystem for performance concerns - $files = array_filter($files, function ($fs_file) { - return $fs_file['type'] === 'file' && $fs_file['extension'] === 'webm'; - }); - - // Create videos from metadata that we can later save if needed - $fs_videos = array_map(function ($file) { - $fs_video = new Video; - $fs_video->fill($file); - - return $fs_video; - }, $files); - - // Existing videos as array - $db_videos = Video::all()->all(); - - // Create videos that exist in storage but not in the database - $create_videos = array_udiff($fs_videos, $db_videos, [VideoReconcileCommand::class, 'compareVideos']); - foreach ($create_videos as $create_video) { - $create_result = $create_video->save(); - if ($create_result) { - $this->created++; - Log::info("Video '{$create_video->basename}' created"); - $this->info("Video '{$create_video->basename}' created"); - } else { - $this->created_failed++; - Log::error("Video '{$create_video->basename}' was not created"); - $this->error("Video '{$create_video->basename}' was not created"); - } - } - - // Delete videos that no longer exist in storage - $delete_videos = array_udiff($db_videos, $fs_videos, [VideoReconcileCommand::class, 'compareVideos']); - foreach ($delete_videos as $delete_video) { - $delete_result = $delete_video->delete(); - if ($delete_result) { - $this->deleted++; - Log::info("Video '{$delete_video->basename}' deleted"); - $this->info("Video '{$delete_video->basename}' deleted"); - } else { - $this->deleted_failed++; - Log::error("Video '{$delete_video->basename}' was not deleted"); - $this->error("Video '{$delete_video->basename}' was not deleted"); - } - } - } catch (S3Exception $exception) { - Log::error($exception); - $this->error($exception->getMessage()); - } finally { - // Output reconcilation results - $this->printResults(); - } - } - - // Callback for video comparison in set operation - public static function compareVideos($a, $b) - { - return strcmp(VideoReconcileCommand::reconciliationString($a), VideoReconcileCommand::reconciliationString($b)); - } - - // Represent video with attributes that correspond to WebM metadata - // For reconciliation purposes, other attributes such as ID and timestamps do not apply - public static function reconciliationString($video) - { - return "basename:{$video->basename},filename:{$video->filename},path:{$video->path},size:{$video->size}"; - } - - // Reconciliation Results - - public function hasResults() - { - return $this->hasChanges() || $this->hasFailures(); - } - - public function hasChanges() - { - return $this->created > 0 || $this->deleted > 0; - } - - public function hasFailures() - { - return $this->created_failed > 0 || $this->deleted_failed > 0; - } - - public function printResults() - { - if ($this->hasResults()) { - if ($this->hasChanges()) { - Log::info("{$this->created} Videos created, {$this->deleted} Videos deleted"); - $this->info("{$this->created} Videos created, {$this->deleted} Videos deleted"); - } - if ($this->hasFailures()) { - Log::error("Failed to create {$this->created_failed} Videos, delete {$this->deleted_failed} Videos"); - $this->error("Failed to create {$this->created_failed} Videos, delete {$this->deleted_failed} Videos"); - } - } else { - Log::info('No Videos created or deleted'); - $this->info('No Videos created or deleted'); - } - } -} diff --git a/app/Console/Commands/Wiki/Video/VideoReconcileCommand.php b/app/Console/Commands/Wiki/Video/VideoReconcileCommand.php new file mode 100644 index 000000000..e4cf3e278 --- /dev/null +++ b/app/Console/Commands/Wiki/Video/VideoReconcileCommand.php @@ -0,0 +1,171 @@ +option('path'); + if ($path !== null) { + if (! $sourceRepository->validateFilter('path', $path) || ! $destinationRepository->validateFilter('path', $path)) { + $this->error("Invalid path '$path'"); + + return 1; + } + + $sourceRepository->handleFilter('path', $path); + $destinationRepository->handleFilter('path', $path); + } + + $this->reconcileRepositories($sourceRepository, $destinationRepository); + + return 0; + } + + /** + * Print the result to console and log the results to the app log. + * + * @return void + */ + protected function postReconciliationTask(): void + { + if ($this->hasResults()) { + if ($this->hasChanges()) { + Log::info("$this->created Videos created, $this->deleted Videos deleted, $this->updated Videos updated"); + $this->info("$this->created Videos created, $this->deleted Videos deleted, $this->updated Videos updated"); + } + if ($this->hasFailures()) { + Log::error("Failed to create $this->createdFailed Videos, delete $this->deletedFailed Videos, update $this->updatedFailed Videos"); + $this->error("Failed to create $this->createdFailed Videos, delete $this->deletedFailed Videos, update $this->updatedFailed Videos"); + } + } else { + Log::info('No Videos created or deleted or updated'); + $this->info('No Videos created or deleted or updated'); + } + } + + /** + * Handler for successful video creation. + * + * @param BaseModel $model + * @return void + */ + protected function handleCreated(BaseModel $model): void + { + Log::info("Video '{$model->getName()}' created"); + $this->info("Video '{$model->getName()}' created"); + } + + /** + * Handler for failed video creation. + * + * @param BaseModel $model + * @return void + */ + protected function handleFailedCreation(BaseModel $model): void + { + Log::error("Video '{$model->getName()}' was not created"); + $this->error("Video '{$model->getName()}' was not created"); + } + + /** + * Handler for successful video deletion. + * + * @param BaseModel $model + * @return void + */ + protected function handleDeleted(BaseModel $model): void + { + Log::info("Video '{$model->getName()}' deleted"); + $this->info("Video '{$model->getName()}' deleted"); + } + + /** + * Handler for failed video deletion. + * + * @param BaseModel $model + * @return void + */ + protected function handleFailedDeletion(BaseModel $model): void + { + Log::error("Video '{$model->getName()}' was not deleted"); + $this->error("Video '{$model->getName()}' was not deleted"); + } + + /** + * Handler for successful video update. + * + * @param BaseModel $model + * @return void + */ + protected function handleUpdated(BaseModel $model): void + { + Log::info("Video '{$model->getName()}' updated"); + $this->info("Video '{$model->getName()}' updated"); + } + + /** + * Handler for failed video update. + * + * @param BaseModel $model + * @return void + */ + protected function handleFailedUpdate(BaseModel $model): void + { + Log::error("Video '{$model->getName()}' was not updated"); + $this->error("Video '{$model->getName()}' was not updated"); + } + + /** + * Handler for exception. + * + * @param Exception $exception + * @return void + */ + protected function handleException(Exception $exception): void + { + Log::error($exception->getMessage()); + $this->error($exception->getMessage()); + } +} diff --git a/app/Console/Commands/Wiki/WikiDatabaseDumpCommand.php b/app/Console/Commands/Wiki/WikiDatabaseDumpCommand.php new file mode 100644 index 000000000..55c315b23 --- /dev/null +++ b/app/Console/Commands/Wiki/WikiDatabaseDumpCommand.php @@ -0,0 +1,90 @@ +command(VideoReconcileCommand::class)->hourly(); + $schedule->command(BalanceReconcileCommand::class, [Service::DIGITALOCEAN()->key]) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->hourly(); + + $schedule->command(DocumentDatabaseDumpCommand::class) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->daily(); + + $schedule->command(WikiDatabaseDumpCommand::class) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->daily(); + + $schedule->command(WikiDatabaseDumpCommand::class, ['--create']) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->daily(); + + $schedule->command(PruneDatabaseDumpsCommand::class) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->dailyAt('00:15'); + + $schedule->command(PruneExpired::class) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->daily(); + + $schedule->command(PruneFailedJobsCommand::class) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->daily(); + + $schedule->command(PruneModelsCommand::class, ['--except' => [BaseModel::class]]) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->daily(); + + $schedule->command(PruneTelescopeEntriesCommand::class) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->daily(); + + $schedule->command(SnapshotCommand::class) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->everyFiveMinutes(); + + $schedule->command(TransactionReconcileCommand::class, [Service::DIGITALOCEAN()->key]) + ->withoutOverlapping() + ->runInBackground() + ->storeOutput() + ->hourly(); } /** * Register the commands for the application. * * @return void + * + * @noinspection PhpMissingParentCallCommonInspection */ - protected function commands() + protected function commands(): void { $this->load(__DIR__.'/Commands'); diff --git a/app/Constants/Config/FlagConstants.php b/app/Constants/Config/FlagConstants.php new file mode 100644 index 000000000..e19e81f0f --- /dev/null +++ b/app/Constants/Config/FlagConstants.php @@ -0,0 +1,23 @@ +value = $this->formatEmbedFieldValue($value); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'value' => $this->value, + 'inline' => $this->inline, + ]; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * Format embed value to circumvent exceptions caused by empty or null values. + * + * @param mixed $value + * @return string + */ + protected function formatEmbedFieldValue(mixed $value): string + { + // Use description for enums + if ($value instanceof Enum) { + return $value->description; + } + + // Pretty print booleans + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + // Encode to json for all other non-empty scalar values + if (is_scalar($value) && Str::length($value) > 0) { + return strval($value); + } + + return self::DEFAULT_FIELD_VALUE; + } +} diff --git a/app/Enums/BaseEnum.php b/app/Enums/BaseEnum.php new file mode 100644 index 000000000..1557049b0 --- /dev/null +++ b/app/Enums/BaseEnum.php @@ -0,0 +1,30 @@ + Str::lower($enum->description) === Str::lower($description) + ); + } +} diff --git a/app/Enums/Discord/EmbedColor.php b/app/Enums/Discord/EmbedColor.php new file mode 100644 index 000000000..4c62aff57 --- /dev/null +++ b/app/Enums/Discord/EmbedColor.php @@ -0,0 +1,21 @@ +'; + public const LT = '<'; + public const GT = '>'; + public const LTE = '<='; + public const GTE = '>='; + public const LIKE = 'like'; + public const NOTLIKE = 'not like'; +} diff --git a/app/Enums/Http/Api/Filter/LogicalOperator.php b/app/Enums/Http/Api/Filter/LogicalOperator.php new file mode 100644 index 000000000..edfab4bf8 --- /dev/null +++ b/app/Enums/Http/Api/Filter/LogicalOperator.php @@ -0,0 +1,15 @@ + 'twitter.com', + self::ANIDB => 'anidb.net', + self::ANILIST => 'anilist.co', + self::ANIME_PLANET => 'anime-planet.com', + self::ANN => 'animenewsnetwork.com', + self::KITSU => 'kitsu.io', + self::MAL => 'myanimelist.net', + self::WIKI => 'wikipedia.org', + default => null, + }; + } + + /** + * Get resource site by link, matching expected domain. + * + * @param string $link the link to test + * @return ResourceSite|null + */ + public static function valueOf(string $link): ?ResourceSite + { + $parsedHost = parse_url($link, PHP_URL_HOST); + + return Arr::first( + ResourceSite::getInstances(), + fn (ResourceSite $site) => $parsedHost === ResourceSite::getDomain($site->value) + ); + } + + /** + * Attempt to parse External ID from Site Link. + * + * @param string $link + * @return string|null + */ + public static function parseIdFromLink(string $link): ?string + { + $site = ResourceSite::valueOf($link); + + return match ($site?->value) { + ResourceSite::ANIDB, + ResourceSite::ANILIST, + ResourceSite::ANN, + ResourceSite::MAL => Str::match('/\d+/', $link), + default => null, + }; + } + + /** + * Get the URL of the site for anime resources. + * + * @param ResourceSite $site + * @param int $id + * @param string|null $slug + * @return string|null + */ + public static function formatAnimeResourceLink(ResourceSite $site, int $id, ?string $slug = null): ?string + { + return match ($site->value) { + ResourceSite::ANIDB => "https://anidb.net/anime/$id/", + ResourceSite::ANILIST => "https://anilist.co/anime/$id/", + ResourceSite::ANIME_PLANET => "https://anime-planet.com/anime/$slug", + ResourceSite::ANN => "https://animenewsnetwork.com/encyclopedia/anime.php?id=$id", + ResourceSite::KITSU => "https://kitsu.io/anime/$slug", + ResourceSite::MAL => "https://myanimelist.net/anime/$id/", + default => null, + }; + } + + /** + * Get the URL of the site for studio resources. + * + * @param ResourceSite $site + * @param int $id + * @param string|null $slug + * @return string|null + */ + public static function formatStudioResourceLink(ResourceSite $site, int $id, ?string $slug = null): ?string + { + return match ($site->value) { + ResourceSite::ANIDB => "https://anidb.net/creator/$id/", + ResourceSite::ANILIST => "https://anilist.co/studio/$id/", + ResourceSite::ANIME_PLANET => "https://anime-planet.com/anime/studios/$slug", + ResourceSite::ANN => "https://animenewsnetwork.com/encyclopedia/company.php?id=$id", + ResourceSite::MAL => "https://myanimelist.net/anime/producer/$id/", + default => null, + }; + } +} diff --git a/app/Enums/Models/Wiki/ThemeType.php b/app/Enums/Models/Wiki/ThemeType.php new file mode 100644 index 000000000..4543af7fe --- /dev/null +++ b/app/Enums/Models/Wiki/ThemeType.php @@ -0,0 +1,16 @@ + + */ +class AnnouncementCreated extends AdminCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Announcement $announcement + */ + public function __construct(Announcement $announcement) + { + parent::__construct($announcement); + } + + /** + * Get the model that has fired this event. + * + * @return Announcement + */ + public function getModel(): Announcement + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Announcement '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Admin/Announcement/AnnouncementDeleted.php b/app/Events/Admin/Announcement/AnnouncementDeleted.php new file mode 100644 index 000000000..41d1c128d --- /dev/null +++ b/app/Events/Admin/Announcement/AnnouncementDeleted.php @@ -0,0 +1,46 @@ + + */ +class AnnouncementDeleted extends AdminDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Announcement $announcement + */ + public function __construct(Announcement $announcement) + { + parent::__construct($announcement); + } + + /** + * Get the model that has fired this event. + * + * @return Announcement + */ + public function getModel(): Announcement + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Announcement '**{$this->getModel()->getName()}**' has been deleted."; + } +} diff --git a/app/Events/Admin/Announcement/AnnouncementRestored.php b/app/Events/Admin/Announcement/AnnouncementRestored.php new file mode 100644 index 000000000..821460522 --- /dev/null +++ b/app/Events/Admin/Announcement/AnnouncementRestored.php @@ -0,0 +1,46 @@ + + */ +class AnnouncementRestored extends AdminRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Announcement $announcement + */ + public function __construct(Announcement $announcement) + { + parent::__construct($announcement); + } + + /** + * Get the model that has fired this event. + * + * @return Announcement + */ + public function getModel(): Announcement + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Announcement '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Admin/Announcement/AnnouncementUpdated.php b/app/Events/Admin/Announcement/AnnouncementUpdated.php new file mode 100644 index 000000000..5a6bd5653 --- /dev/null +++ b/app/Events/Admin/Announcement/AnnouncementUpdated.php @@ -0,0 +1,47 @@ + + */ +class AnnouncementUpdated extends AdminUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Announcement $announcement + */ + public function __construct(Announcement $announcement) + { + parent::__construct($announcement); + $this->initializeEmbedFields($announcement); + } + + /** + * Get the model that has fired this event. + * + * @return Announcement + */ + public function getModel(): Announcement + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Announcement '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Auth/Invitation/InvitationCreated.php b/app/Events/Auth/Invitation/InvitationCreated.php new file mode 100644 index 000000000..e3055eb85 --- /dev/null +++ b/app/Events/Auth/Invitation/InvitationCreated.php @@ -0,0 +1,46 @@ + + */ +class InvitationCreated extends AdminCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Invitation $invitation + */ + public function __construct(Invitation $invitation) + { + parent::__construct($invitation); + } + + /** + * Get the model that has fired this event. + * + * @return Invitation + */ + public function getModel(): Invitation + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Invitation '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Auth/Invitation/InvitationDeleted.php b/app/Events/Auth/Invitation/InvitationDeleted.php new file mode 100644 index 000000000..107d0f3c9 --- /dev/null +++ b/app/Events/Auth/Invitation/InvitationDeleted.php @@ -0,0 +1,46 @@ + + */ +class InvitationDeleted extends AdminDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Invitation $invitation + */ + public function __construct(Invitation $invitation) + { + parent::__construct($invitation); + } + + /** + * Get the model that has fired this event. + * + * @return Invitation + */ + public function getModel(): Invitation + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Invitation '**{$this->getModel()->getName()}**' has been deleted."; + } +} diff --git a/app/Events/Auth/Invitation/InvitationRestored.php b/app/Events/Auth/Invitation/InvitationRestored.php new file mode 100644 index 000000000..b21c6b42b --- /dev/null +++ b/app/Events/Auth/Invitation/InvitationRestored.php @@ -0,0 +1,46 @@ + + */ +class InvitationRestored extends AdminRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Invitation $invitation + */ + public function __construct(Invitation $invitation) + { + parent::__construct($invitation); + } + + /** + * Get the model that has fired this event. + * + * @return Invitation + */ + public function getModel(): Invitation + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Invitation '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Auth/Invitation/InvitationUpdated.php b/app/Events/Auth/Invitation/InvitationUpdated.php new file mode 100644 index 000000000..728bc4fd2 --- /dev/null +++ b/app/Events/Auth/Invitation/InvitationUpdated.php @@ -0,0 +1,47 @@ + + */ +class InvitationUpdated extends AdminUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Invitation $invitation + */ + public function __construct(Invitation $invitation) + { + parent::__construct($invitation); + $this->initializeEmbedFields($invitation); + } + + /** + * Get the model that has fired this event. + * + * @return Invitation + */ + public function getModel(): Invitation + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Invitation '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Auth/User/UserCreated.php b/app/Events/Auth/User/UserCreated.php new file mode 100644 index 000000000..cfee8ad1c --- /dev/null +++ b/app/Events/Auth/User/UserCreated.php @@ -0,0 +1,47 @@ +getUser(); + + return DiscordMessage::create('', [ + 'description' => "User '**{$user->getName()}**' has been created.", + 'color' => EmbedColor::GREEN, + ]); + } + + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Auth/User/UserDeleted.php b/app/Events/Auth/User/UserDeleted.php new file mode 100644 index 000000000..b8a2795be --- /dev/null +++ b/app/Events/Auth/User/UserDeleted.php @@ -0,0 +1,45 @@ +getUser(); + + return DiscordMessage::create('', [ + 'description' => "User '**{$user->getName()}**' has been deleted.", + 'color' => EmbedColor::RED, + ]); + } + + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Auth/User/UserEvent.php b/app/Events/Auth/User/UserEvent.php new file mode 100644 index 000000000..b9d90665a --- /dev/null +++ b/app/Events/Auth/User/UserEvent.php @@ -0,0 +1,33 @@ +user; + } +} diff --git a/app/Events/Auth/User/UserRestored.php b/app/Events/Auth/User/UserRestored.php new file mode 100644 index 000000000..53452516e --- /dev/null +++ b/app/Events/Auth/User/UserRestored.php @@ -0,0 +1,47 @@ +getUser(); + + return DiscordMessage::create('', [ + 'description' => "User '**{$user->getName()}**' has been restored.", + 'color' => EmbedColor::GREEN, + ]); + } + + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Auth/User/UserUpdated.php b/app/Events/Auth/User/UserUpdated.php new file mode 100644 index 000000000..7f1866f0e --- /dev/null +++ b/app/Events/Auth/User/UserUpdated.php @@ -0,0 +1,61 @@ +initializeEmbedFields($user); + } + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + $user = $this->getUser(); + + return DiscordMessage::create('', [ + 'description' => "User '**{$user->getName()}**' has been updated.", + 'fields' => $this->getEmbedFields(), + '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); + } +} diff --git a/app/Events/Base/Admin/AdminCreatedEvent.php b/app/Events/Base/Admin/AdminCreatedEvent.php new file mode 100644 index 000000000..bf98595ce --- /dev/null +++ b/app/Events/Base/Admin/AdminCreatedEvent.php @@ -0,0 +1,28 @@ + + */ +abstract class AdminCreatedEvent extends BaseCreatedEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Base/Admin/AdminDeletedEvent.php b/app/Events/Base/Admin/AdminDeletedEvent.php new file mode 100644 index 000000000..b78fac12e --- /dev/null +++ b/app/Events/Base/Admin/AdminDeletedEvent.php @@ -0,0 +1,28 @@ + + */ +abstract class AdminDeletedEvent extends BaseDeletedEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Base/Admin/AdminRestoredEvent.php b/app/Events/Base/Admin/AdminRestoredEvent.php new file mode 100644 index 000000000..63c69a78d --- /dev/null +++ b/app/Events/Base/Admin/AdminRestoredEvent.php @@ -0,0 +1,28 @@ + + */ +abstract class AdminRestoredEvent extends BaseRestoredEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Base/Admin/AdminUpdatedEvent.php b/app/Events/Base/Admin/AdminUpdatedEvent.php new file mode 100644 index 000000000..15e2d4dac --- /dev/null +++ b/app/Events/Base/Admin/AdminUpdatedEvent.php @@ -0,0 +1,28 @@ + + */ +abstract class AdminUpdatedEvent extends BaseUpdatedEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Base/BaseCreatedEvent.php b/app/Events/Base/BaseCreatedEvent.php new file mode 100644 index 000000000..cbc4dcbe8 --- /dev/null +++ b/app/Events/Base/BaseCreatedEvent.php @@ -0,0 +1,44 @@ + + */ +abstract class BaseCreatedEvent extends BaseEvent implements DiscordMessageEvent +{ + use Dispatchable; + use SerializesModels; + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + return DiscordMessage::create('', [ + 'description' => $this->getDiscordMessageDescription(), + 'color' => EmbedColor::GREEN, + ]); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + abstract protected function getDiscordMessageDescription(): string; +} diff --git a/app/Events/Base/BaseDeletedEvent.php b/app/Events/Base/BaseDeletedEvent.php new file mode 100644 index 000000000..30afb875a --- /dev/null +++ b/app/Events/Base/BaseDeletedEvent.php @@ -0,0 +1,42 @@ + + */ +abstract class BaseDeletedEvent extends BaseEvent implements DiscordMessageEvent +{ + use Dispatchable; + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + return DiscordMessage::create('', [ + 'description' => $this->getDiscordMessageDescription(), + 'color' => EmbedColor::RED, + ]); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + abstract protected function getDiscordMessageDescription(): string; +} diff --git a/app/Events/Base/BaseRestoredEvent.php b/app/Events/Base/BaseRestoredEvent.php new file mode 100644 index 000000000..5d219eb6d --- /dev/null +++ b/app/Events/Base/BaseRestoredEvent.php @@ -0,0 +1,44 @@ + + */ +abstract class BaseRestoredEvent extends BaseEvent implements DiscordMessageEvent +{ + use Dispatchable; + use SerializesModels; + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + return DiscordMessage::create('', [ + 'description' => $this->getDiscordMessageDescription(), + 'color' => EmbedColor::GREEN, + ]); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + abstract protected function getDiscordMessageDescription(): string; +} diff --git a/app/Events/Base/BaseUpdatedEvent.php b/app/Events/Base/BaseUpdatedEvent.php new file mode 100644 index 000000000..f2d0a8d23 --- /dev/null +++ b/app/Events/Base/BaseUpdatedEvent.php @@ -0,0 +1,45 @@ + + */ +abstract class BaseUpdatedEvent extends BaseEvent implements DiscordMessageEvent +{ + use Dispatchable; + use HasAttributeUpdateEmbedFields; + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + return DiscordMessage::create('', [ + 'description' => $this->getDiscordMessageDescription(), + 'fields' => $this->getEmbedFields(), + 'color' => EmbedColor::YELLOW, + ]); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + abstract protected function getDiscordMessageDescription(): string; +} diff --git a/app/Events/Base/Pivot/PivotCreatedEvent.php b/app/Events/Base/Pivot/PivotCreatedEvent.php new file mode 100644 index 000000000..587d7451e --- /dev/null +++ b/app/Events/Base/Pivot/PivotCreatedEvent.php @@ -0,0 +1,37 @@ + + */ +abstract class PivotCreatedEvent extends BasePivotEvent +{ + use Dispatchable; + use SerializesModels; + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + return DiscordMessage::create('', [ + 'description' => $this->getDiscordMessageDescription(), + 'color' => EmbedColor::GREEN, + ]); + } +} diff --git a/app/Events/Base/Pivot/PivotDeletedEvent.php b/app/Events/Base/Pivot/PivotDeletedEvent.php new file mode 100644 index 000000000..dfb15afcd --- /dev/null +++ b/app/Events/Base/Pivot/PivotDeletedEvent.php @@ -0,0 +1,37 @@ + + */ +abstract class PivotDeletedEvent extends BasePivotEvent +{ + use Dispatchable; + use SerializesModels; + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + return DiscordMessage::create('', [ + 'description' => $this->getDiscordMessageDescription(), + 'color' => EmbedColor::GREEN, + ]); + } +} diff --git a/app/Events/Base/Pivot/PivotUpdatedEvent.php b/app/Events/Base/Pivot/PivotUpdatedEvent.php new file mode 100644 index 000000000..87f00cf28 --- /dev/null +++ b/app/Events/Base/Pivot/PivotUpdatedEvent.php @@ -0,0 +1,38 @@ + + */ +abstract class PivotUpdatedEvent extends BasePivotEvent +{ + use Dispatchable; + use HasAttributeUpdateEmbedFields; + + /** + * Get Discord message payload. + * + * @return DiscordMessage + */ + public function getDiscordMessage(): DiscordMessage + { + return DiscordMessage::create('', [ + 'description' => $this->getDiscordMessageDescription(), + 'fields' => $this->getEmbedFields(), + 'color' => EmbedColor::YELLOW, + ]); + } +} diff --git a/app/Events/Base/Wiki/WikiCreatedEvent.php b/app/Events/Base/Wiki/WikiCreatedEvent.php new file mode 100644 index 000000000..0ba95e685 --- /dev/null +++ b/app/Events/Base/Wiki/WikiCreatedEvent.php @@ -0,0 +1,28 @@ + + */ +abstract class WikiCreatedEvent extends BaseCreatedEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::DB_UPDATES_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Base/Wiki/WikiDeletedEvent.php b/app/Events/Base/Wiki/WikiDeletedEvent.php new file mode 100644 index 000000000..83709793d --- /dev/null +++ b/app/Events/Base/Wiki/WikiDeletedEvent.php @@ -0,0 +1,85 @@ + + */ +abstract class WikiDeletedEvent extends BaseDeletedEvent implements NovaNotificationEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::DB_UPDATES_DISCORD_CHANNEL_QUALIFIED); + } + + /** + * Determine if the notifications should be sent. + * + * @return bool + */ + public function shouldSend(): bool + { + $model = $this->getModel(); + + return ! $model->isForceDeleting(); + } + + /** + * Get the nova notification. + * + * @return NovaNotification + */ + public function getNotification(): NovaNotification + { + return NovaNotification::make() + ->icon('flag') + ->message($this->getNotificationMessage()) + ->type(NovaNotification::INFO_TYPE) + ->url($this->getNotificationUrl()); + } + + /** + * Get the users to notify. + * + * @return Collection + */ + public function getUsers(): Collection + { + return User::query() + ->whereRelation(User::RELATION_ROLES, Role::ATTRIBUTE_NAME, 'Admin') + ->get(); + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + abstract protected function getNotificationMessage(): string; + + /** + * Get the URL for the nova notification. + * + * @return string + */ + abstract protected function getNotificationUrl(): string; +} diff --git a/app/Events/Base/Wiki/WikiRestoredEvent.php b/app/Events/Base/Wiki/WikiRestoredEvent.php new file mode 100644 index 000000000..184463080 --- /dev/null +++ b/app/Events/Base/Wiki/WikiRestoredEvent.php @@ -0,0 +1,28 @@ + + */ +abstract class WikiRestoredEvent extends BaseRestoredEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::DB_UPDATES_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/Base/Wiki/WikiUpdatedEvent.php b/app/Events/Base/Wiki/WikiUpdatedEvent.php new file mode 100644 index 000000000..bd1887e17 --- /dev/null +++ b/app/Events/Base/Wiki/WikiUpdatedEvent.php @@ -0,0 +1,28 @@ + + */ +abstract class WikiUpdatedEvent extends BaseUpdatedEvent +{ + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::DB_UPDATES_DISCORD_CHANNEL_QUALIFIED); + } +} diff --git a/app/Events/BaseEvent.php b/app/Events/BaseEvent.php new file mode 100644 index 000000000..4f4e4b572 --- /dev/null +++ b/app/Events/BaseEvent.php @@ -0,0 +1,31 @@ +related; + } + + /** + * Get the foreign model. + * + * @return TModelForeign + */ + public function getForeign(): BaseModel + { + return $this->foreign; + } + + /** + * Get Discord channel the message will be sent to. + * + * @return string + */ + public function getDiscordChannel(): string + { + return Config::get(ServiceConstants::DB_UPDATES_DISCORD_CHANNEL_QUALIFIED); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + abstract protected function getDiscordMessageDescription(): string; +} diff --git a/app/Events/Billing/Balance/BalanceCreated.php b/app/Events/Billing/Balance/BalanceCreated.php new file mode 100644 index 000000000..5ebd8eef2 --- /dev/null +++ b/app/Events/Billing/Balance/BalanceCreated.php @@ -0,0 +1,46 @@ + + */ +class BalanceCreated extends AdminCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Balance $balance + */ + public function __construct(Balance $balance) + { + parent::__construct($balance); + } + + /** + * Get the model that has fired this event. + * + * @return Balance + */ + public function getModel(): Balance + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Balance '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Billing/Balance/BalanceDeleted.php b/app/Events/Billing/Balance/BalanceDeleted.php new file mode 100644 index 000000000..1af65bacf --- /dev/null +++ b/app/Events/Billing/Balance/BalanceDeleted.php @@ -0,0 +1,46 @@ + + */ +class BalanceDeleted extends AdminDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Balance $balance + */ + public function __construct(Balance $balance) + { + parent::__construct($balance); + } + + /** + * Get the model that has fired this event. + * + * @return Balance + */ + public function getModel(): Balance + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Balance '**{$this->getModel()->getName()}**' has been deleted."; + } +} diff --git a/app/Events/Billing/Balance/BalanceRestored.php b/app/Events/Billing/Balance/BalanceRestored.php new file mode 100644 index 000000000..d44705762 --- /dev/null +++ b/app/Events/Billing/Balance/BalanceRestored.php @@ -0,0 +1,46 @@ + + */ +class BalanceRestored extends AdminRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Balance $balance + */ + public function __construct(Balance $balance) + { + parent::__construct($balance); + } + + /** + * Get the model that has fired this event. + * + * @return Balance + */ + public function getModel(): Balance + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Balance '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Billing/Balance/BalanceUpdated.php b/app/Events/Billing/Balance/BalanceUpdated.php new file mode 100644 index 000000000..16c82e42a --- /dev/null +++ b/app/Events/Billing/Balance/BalanceUpdated.php @@ -0,0 +1,47 @@ + + */ +class BalanceUpdated extends AdminUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Balance $balance + */ + public function __construct(Balance $balance) + { + parent::__construct($balance); + $this->initializeEmbedFields($balance); + } + + /** + * Get the model that has fired this event. + * + * @return Balance + */ + public function getModel(): Balance + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Balance '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Billing/Transaction/TransactionCreated.php b/app/Events/Billing/Transaction/TransactionCreated.php new file mode 100644 index 000000000..7169c1016 --- /dev/null +++ b/app/Events/Billing/Transaction/TransactionCreated.php @@ -0,0 +1,46 @@ + + */ +class TransactionCreated extends AdminCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Transaction $transaction + */ + public function __construct(Transaction $transaction) + { + parent::__construct($transaction); + } + + /** + * Get the model that has fired this event. + * + * @return Transaction + */ + public function getModel(): Transaction + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Transaction '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Billing/Transaction/TransactionDeleted.php b/app/Events/Billing/Transaction/TransactionDeleted.php new file mode 100644 index 000000000..b3168600d --- /dev/null +++ b/app/Events/Billing/Transaction/TransactionDeleted.php @@ -0,0 +1,46 @@ + + */ +class TransactionDeleted extends AdminDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Transaction $transaction + */ + public function __construct(Transaction $transaction) + { + parent::__construct($transaction); + } + + /** + * Get the model that has fired this event. + * + * @return Transaction + */ + public function getModel(): Transaction + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Transaction '**{$this->getModel()->getName()}**' has been deleted."; + } +} diff --git a/app/Events/Billing/Transaction/TransactionRestored.php b/app/Events/Billing/Transaction/TransactionRestored.php new file mode 100644 index 000000000..4cc1bbe97 --- /dev/null +++ b/app/Events/Billing/Transaction/TransactionRestored.php @@ -0,0 +1,46 @@ + + */ +class TransactionRestored extends AdminRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Transaction $transaction + */ + public function __construct(Transaction $transaction) + { + parent::__construct($transaction); + } + + /** + * Get the model that has fired this event. + * + * @return Transaction + */ + public function getModel(): Transaction + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Transaction '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Billing/Transaction/TransactionUpdated.php b/app/Events/Billing/Transaction/TransactionUpdated.php new file mode 100644 index 000000000..c7862f66f --- /dev/null +++ b/app/Events/Billing/Transaction/TransactionUpdated.php @@ -0,0 +1,46 @@ + + */ +class TransactionUpdated extends AdminUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Transaction $transaction + */ + public function __construct(Transaction $transaction) + { + parent::__construct($transaction); + } + + /** + * Get the model that has fired this event. + * + * @return Transaction + */ + public function getModel(): Transaction + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Transaction '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Document/Page/PageCreated.php b/app/Events/Document/Page/PageCreated.php new file mode 100644 index 000000000..f5264af20 --- /dev/null +++ b/app/Events/Document/Page/PageCreated.php @@ -0,0 +1,46 @@ + + */ +class PageCreated extends WikiCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Page $page + */ + public function __construct(Page $page) + { + parent::__construct($page); + } + + /** + * Get the model that has fired this event. + * + * @return Page + */ + public function getModel(): Page + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Page '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Document/Page/PageDeleted.php b/app/Events/Document/Page/PageDeleted.php new file mode 100644 index 000000000..8edc36a16 --- /dev/null +++ b/app/Events/Document/Page/PageDeleted.php @@ -0,0 +1,69 @@ + + */ +class PageDeleted extends WikiDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Page $page + */ + public function __construct(Page $page) + { + parent::__construct($page); + } + + /** + * Get the model that has fired this event. + * + * @return Page + */ + public function getModel(): Page + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Page '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Page '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = PageResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Document/Page/PageRestored.php b/app/Events/Document/Page/PageRestored.php new file mode 100644 index 000000000..59ea967f8 --- /dev/null +++ b/app/Events/Document/Page/PageRestored.php @@ -0,0 +1,46 @@ + + */ +class PageRestored extends WikiRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Page $page + */ + public function __construct(Page $page) + { + parent::__construct($page); + } + + /** + * Get the model that has fired this event. + * + * @return Page + */ + public function getModel(): Page + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Page '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Document/Page/PageUpdated.php b/app/Events/Document/Page/PageUpdated.php new file mode 100644 index 000000000..41e017890 --- /dev/null +++ b/app/Events/Document/Page/PageUpdated.php @@ -0,0 +1,47 @@ + + */ +class PageUpdated extends WikiUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Page $page + */ + public function __construct(Page $page) + { + parent::__construct($page); + $this->initializeEmbedFields($page); + } + + /** + * Get the model that has fired this event. + * + * @return Page + */ + public function getModel(): Page + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Page '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Pivot/AnimeImage/AnimeImageCreated.php b/app/Events/Pivot/AnimeImage/AnimeImageCreated.php new file mode 100644 index 000000000..32e0de7c4 --- /dev/null +++ b/app/Events/Pivot/AnimeImage/AnimeImageCreated.php @@ -0,0 +1,41 @@ + + */ +class AnimeImageCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeImage $animeImage + */ + public function __construct(AnimeImage $animeImage) + { + parent::__construct($animeImage->anime, $animeImage->image); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Image '**{$foreign->getName()}**' has been attached to Anime '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeImage/AnimeImageDeleted.php b/app/Events/Pivot/AnimeImage/AnimeImageDeleted.php new file mode 100644 index 000000000..7c3029a7e --- /dev/null +++ b/app/Events/Pivot/AnimeImage/AnimeImageDeleted.php @@ -0,0 +1,41 @@ + + */ +class AnimeImageDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeImage $animeImage + */ + public function __construct(AnimeImage $animeImage) + { + parent::__construct($animeImage->anime, $animeImage->image); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Image '**{$foreign->getName()}**' has been detached from Anime '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeResource/AnimeResourceCreated.php b/app/Events/Pivot/AnimeResource/AnimeResourceCreated.php new file mode 100644 index 000000000..cbe1350fe --- /dev/null +++ b/app/Events/Pivot/AnimeResource/AnimeResourceCreated.php @@ -0,0 +1,41 @@ + + */ +class AnimeResourceCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeResource $animeResource + */ + public function __construct(AnimeResource $animeResource) + { + parent::__construct($animeResource->anime, $animeResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been attached to Anime '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeResource/AnimeResourceDeleted.php b/app/Events/Pivot/AnimeResource/AnimeResourceDeleted.php new file mode 100644 index 000000000..802374989 --- /dev/null +++ b/app/Events/Pivot/AnimeResource/AnimeResourceDeleted.php @@ -0,0 +1,41 @@ + + */ +class AnimeResourceDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeResource $animeResource + */ + public function __construct(AnimeResource $animeResource) + { + parent::__construct($animeResource->anime, $animeResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been detached from Anime '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeResource/AnimeResourceUpdated.php b/app/Events/Pivot/AnimeResource/AnimeResourceUpdated.php new file mode 100644 index 000000000..38a55800e --- /dev/null +++ b/app/Events/Pivot/AnimeResource/AnimeResourceUpdated.php @@ -0,0 +1,42 @@ + + */ +class AnimeResourceUpdated extends PivotUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeResource $animeResource + */ + public function __construct(AnimeResource $animeResource) + { + parent::__construct($animeResource->anime, $animeResource->resource); + $this->initializeEmbedFields($animeResource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' for Anime '**{$related->getName()}**' has been updated."; + } +} diff --git a/app/Events/Pivot/AnimeSeries/AnimeSeriesCreated.php b/app/Events/Pivot/AnimeSeries/AnimeSeriesCreated.php new file mode 100644 index 000000000..0991f0cd7 --- /dev/null +++ b/app/Events/Pivot/AnimeSeries/AnimeSeriesCreated.php @@ -0,0 +1,41 @@ + + */ +class AnimeSeriesCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeSeries $animeSeries + */ + public function __construct(AnimeSeries $animeSeries) + { + parent::__construct($animeSeries->series, $animeSeries->anime); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Anime '**{$foreign->getName()}**' has been attached to Series '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeSeries/AnimeSeriesDeleted.php b/app/Events/Pivot/AnimeSeries/AnimeSeriesDeleted.php new file mode 100644 index 000000000..1fea40513 --- /dev/null +++ b/app/Events/Pivot/AnimeSeries/AnimeSeriesDeleted.php @@ -0,0 +1,41 @@ + + */ +class AnimeSeriesDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeSeries $animeSeries + */ + public function __construct(AnimeSeries $animeSeries) + { + parent::__construct($animeSeries->series, $animeSeries->anime); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Anime '**{$foreign->getName()}**' has been detached from Series '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeStudio/AnimeStudioCreated.php b/app/Events/Pivot/AnimeStudio/AnimeStudioCreated.php new file mode 100644 index 000000000..e0dc34a5c --- /dev/null +++ b/app/Events/Pivot/AnimeStudio/AnimeStudioCreated.php @@ -0,0 +1,41 @@ + + */ +class AnimeStudioCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeStudio $animeStudio + */ + public function __construct(AnimeStudio $animeStudio) + { + parent::__construct($animeStudio->studio, $animeStudio->anime); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Anime '**{$foreign->getName()}**' has been attached to Studio '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeStudio/AnimeStudioDeleted.php b/app/Events/Pivot/AnimeStudio/AnimeStudioDeleted.php new file mode 100644 index 000000000..29a6d2cea --- /dev/null +++ b/app/Events/Pivot/AnimeStudio/AnimeStudioDeleted.php @@ -0,0 +1,41 @@ + + */ +class AnimeStudioDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param AnimeStudio $animeStudio + */ + public function __construct(AnimeStudio $animeStudio) + { + parent::__construct($animeStudio->studio, $animeStudio->anime); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Anime '**{$foreign->getName()}**' has been detached from Studio '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeCreatedVideo.php b/app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeCreatedVideo.php new file mode 100644 index 000000000..faea5636d --- /dev/null +++ b/app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeCreatedVideo.php @@ -0,0 +1,54 @@ + + */ +class AnimeThemeEntryAnimeThemeCreatedVideo extends PivotCreatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeThemeEntryVideo $entryVideo + */ + public function __construct(AnimeThemeEntryVideo $entryVideo) + { + parent::__construct($entryVideo->animethemeentry, $entryVideo->video); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Video '**{$foreign->getName()}**' has been attached to Entry '**{$related->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + // refresh video document + $video = $this->getForeign(); + $video->searchable(); + } +} diff --git a/app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeDeletedVideo.php b/app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeDeletedVideo.php new file mode 100644 index 000000000..b7c91fd4e --- /dev/null +++ b/app/Events/Pivot/AnimeThemeEntryVideo/AnimeThemeEntryAnimeThemeDeletedVideo.php @@ -0,0 +1,54 @@ + + */ +class AnimeThemeEntryAnimeThemeDeletedVideo extends PivotDeletedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeThemeEntryVideo $entryVideo + */ + public function __construct(AnimeThemeEntryVideo $entryVideo) + { + parent::__construct($entryVideo->animethemeentry, $entryVideo->video); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Video '**{$foreign->getName()}**' has been detached from Entry '**{$related->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + // refresh video document + $video = $this->getForeign(); + $video->searchable(); + } +} diff --git a/app/Events/Pivot/ArtistImage/ArtistImageCreated.php b/app/Events/Pivot/ArtistImage/ArtistImageCreated.php new file mode 100644 index 000000000..a6956aba7 --- /dev/null +++ b/app/Events/Pivot/ArtistImage/ArtistImageCreated.php @@ -0,0 +1,41 @@ + + */ +class ArtistImageCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistImage $artistImage + */ + public function __construct(ArtistImage $artistImage) + { + parent::__construct($artistImage->artist, $artistImage->image); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Image '**{$foreign->getName()}**' has been attached to Artist '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/ArtistImage/ArtistImageDeleted.php b/app/Events/Pivot/ArtistImage/ArtistImageDeleted.php new file mode 100644 index 000000000..25b5efa37 --- /dev/null +++ b/app/Events/Pivot/ArtistImage/ArtistImageDeleted.php @@ -0,0 +1,41 @@ + + */ +class ArtistImageDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistImage $artistImage + */ + public function __construct(ArtistImage $artistImage) + { + parent::__construct($artistImage->artist, $artistImage->image); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Image '**{$foreign->getName()}**' has been detached from Artist '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/ArtistMember/ArtistMemberCreated.php b/app/Events/Pivot/ArtistMember/ArtistMemberCreated.php new file mode 100644 index 000000000..18e3d93fa --- /dev/null +++ b/app/Events/Pivot/ArtistMember/ArtistMemberCreated.php @@ -0,0 +1,40 @@ + + */ +class ArtistMemberCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistMember $artistMember + */ + public function __construct(ArtistMember $artistMember) + { + parent::__construct($artistMember->artist, $artistMember->member); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Member '**{$foreign->getName()}**' has been attached to Artist '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/ArtistMember/ArtistMemberDeleted.php b/app/Events/Pivot/ArtistMember/ArtistMemberDeleted.php new file mode 100644 index 000000000..952445ebb --- /dev/null +++ b/app/Events/Pivot/ArtistMember/ArtistMemberDeleted.php @@ -0,0 +1,40 @@ + + */ +class ArtistMemberDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistMember $artistMember + */ + public function __construct(ArtistMember $artistMember) + { + parent::__construct($artistMember->artist, $artistMember->member); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Member '**{$foreign->getName()}**' has been detached from Artist '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/ArtistMember/ArtistMemberUpdated.php b/app/Events/Pivot/ArtistMember/ArtistMemberUpdated.php new file mode 100644 index 000000000..27e213eb0 --- /dev/null +++ b/app/Events/Pivot/ArtistMember/ArtistMemberUpdated.php @@ -0,0 +1,41 @@ + + */ +class ArtistMemberUpdated extends PivotUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistMember $artistMember + */ + public function __construct(ArtistMember $artistMember) + { + parent::__construct($artistMember->artist, $artistMember->member); + $this->initializeEmbedFields($artistMember); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Member '**{$foreign->getName()}**' for Artist '**{$related->getName()}**' has been updated."; + } +} diff --git a/app/Events/Pivot/ArtistResource/ArtistResourceCreated.php b/app/Events/Pivot/ArtistResource/ArtistResourceCreated.php new file mode 100644 index 000000000..db3dd0280 --- /dev/null +++ b/app/Events/Pivot/ArtistResource/ArtistResourceCreated.php @@ -0,0 +1,41 @@ + + */ +class ArtistResourceCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistResource $artistResource + */ + public function __construct(ArtistResource $artistResource) + { + parent::__construct($artistResource->artist, $artistResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been attached to Artist '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/ArtistResource/ArtistResourceDeleted.php b/app/Events/Pivot/ArtistResource/ArtistResourceDeleted.php new file mode 100644 index 000000000..6659fad63 --- /dev/null +++ b/app/Events/Pivot/ArtistResource/ArtistResourceDeleted.php @@ -0,0 +1,41 @@ + + */ +class ArtistResourceDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistResource $artistResource + */ + public function __construct(ArtistResource $artistResource) + { + parent::__construct($artistResource->artist, $artistResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been detached from Artist '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/ArtistResource/ArtistResourceUpdated.php b/app/Events/Pivot/ArtistResource/ArtistResourceUpdated.php new file mode 100644 index 000000000..fbce965a5 --- /dev/null +++ b/app/Events/Pivot/ArtistResource/ArtistResourceUpdated.php @@ -0,0 +1,42 @@ + + */ +class ArtistResourceUpdated extends PivotUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param ArtistResource $artistResource + */ + public function __construct(ArtistResource $artistResource) + { + parent::__construct($artistResource->artist, $artistResource->resource); + $this->initializeEmbedFields($artistResource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' for Artist '**{$related->getName()}**' has been updated."; + } +} diff --git a/app/Events/Pivot/ArtistSong/ArtistSongCreated.php b/app/Events/Pivot/ArtistSong/ArtistSongCreated.php new file mode 100644 index 000000000..be60a96be --- /dev/null +++ b/app/Events/Pivot/ArtistSong/ArtistSongCreated.php @@ -0,0 +1,54 @@ + + */ +class ArtistSongCreated extends PivotCreatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param ArtistSong $artistSong + */ + public function __construct(ArtistSong $artistSong) + { + parent::__construct($artistSong->artist, $artistSong->song); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Song '**{$foreign->getName()}**' has been attached to Artist '**{$related->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + // refresh artist document + $artist = $this->getRelated(); + $artist->searchable(); + } +} diff --git a/app/Events/Pivot/ArtistSong/ArtistSongDeleted.php b/app/Events/Pivot/ArtistSong/ArtistSongDeleted.php new file mode 100644 index 000000000..689b49df6 --- /dev/null +++ b/app/Events/Pivot/ArtistSong/ArtistSongDeleted.php @@ -0,0 +1,54 @@ + + */ +class ArtistSongDeleted extends PivotDeletedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param ArtistSong $artistSong + */ + public function __construct(ArtistSong $artistSong) + { + parent::__construct($artistSong->artist, $artistSong->song); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Song '**{$foreign->getName()}**' has been detached from Artist '**{$related->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + // refresh artist document + $artist = $this->getRelated(); + $artist->searchable(); + } +} diff --git a/app/Events/Pivot/ArtistSong/ArtistSongUpdated.php b/app/Events/Pivot/ArtistSong/ArtistSongUpdated.php new file mode 100644 index 000000000..9eefab833 --- /dev/null +++ b/app/Events/Pivot/ArtistSong/ArtistSongUpdated.php @@ -0,0 +1,55 @@ + + */ +class ArtistSongUpdated extends PivotUpdatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param ArtistSong $artistSong + */ + public function __construct(ArtistSong $artistSong) + { + parent::__construct($artistSong->artist, $artistSong->song); + $this->initializeEmbedFields($artistSong); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Song '**{$foreign->getName()}**' for Artist '**{$related->getName()}**' has been updated."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + // refresh artist document + $artist = $this->getRelated(); + $artist->searchable(); + } +} diff --git a/app/Events/Pivot/StudioResource/StudioResourceCreated.php b/app/Events/Pivot/StudioResource/StudioResourceCreated.php new file mode 100644 index 000000000..afc7dad31 --- /dev/null +++ b/app/Events/Pivot/StudioResource/StudioResourceCreated.php @@ -0,0 +1,41 @@ + + */ +class StudioResourceCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param StudioResource $studioResource + */ + public function __construct(StudioResource $studioResource) + { + parent::__construct($studioResource->studio, $studioResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been attached to Studio '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/StudioResource/StudioResourceDeleted.php b/app/Events/Pivot/StudioResource/StudioResourceDeleted.php new file mode 100644 index 000000000..3156f65e1 --- /dev/null +++ b/app/Events/Pivot/StudioResource/StudioResourceDeleted.php @@ -0,0 +1,41 @@ + + */ +class StudioResourceDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param StudioResource $studioResource + */ + public function __construct(StudioResource $studioResource) + { + parent::__construct($studioResource->studio, $studioResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been detached from Studio '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/StudioResource/StudioResourceUpdated.php b/app/Events/Pivot/StudioResource/StudioResourceUpdated.php new file mode 100644 index 000000000..2c0c2b731 --- /dev/null +++ b/app/Events/Pivot/StudioResource/StudioResourceUpdated.php @@ -0,0 +1,42 @@ + + */ +class StudioResourceUpdated extends PivotUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param StudioResource $studioResource + */ + public function __construct(StudioResource $studioResource) + { + parent::__construct($studioResource->studio, $studioResource->resource); + $this->initializeEmbedFields($studioResource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' for Studio '**{$related->getName()}**' has been updated."; + } +} diff --git a/app/Events/Wiki/Anime/AnimeCreated.php b/app/Events/Wiki/Anime/AnimeCreated.php new file mode 100644 index 000000000..0f095addf --- /dev/null +++ b/app/Events/Wiki/Anime/AnimeCreated.php @@ -0,0 +1,68 @@ + + */ +class AnimeCreated extends WikiCreatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param Anime $anime + */ + public function __construct(Anime $anime) + { + parent::__construct($anime); + } + + /** + * Get the model that has fired this event. + * + * @return Anime + */ + public function getModel(): Anime + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Anime '**{$this->getModel()->getName()}**' has been created."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $anime = $this->getModel()->load(Anime::RELATION_VIDEOS); + + $anime->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/AnimeDeleted.php b/app/Events/Wiki/Anime/AnimeDeleted.php new file mode 100644 index 000000000..62b4f30b3 --- /dev/null +++ b/app/Events/Wiki/Anime/AnimeDeleted.php @@ -0,0 +1,69 @@ + + */ +class AnimeDeleted extends WikiDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Anime $anime + */ + public function __construct(Anime $anime) + { + parent::__construct($anime); + } + + /** + * Get the model that has fired this event. + * + * @return Anime + */ + public function getModel(): Anime + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Anime '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Anime '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = AnimeResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/Anime/AnimeDeleting.php b/app/Events/Wiki/Anime/AnimeDeleting.php new file mode 100644 index 000000000..d2b589be7 --- /dev/null +++ b/app/Events/Wiki/Anime/AnimeDeleting.php @@ -0,0 +1,66 @@ + + */ +class AnimeDeleting extends BaseEvent implements CascadesDeletesEvent +{ + /** + * Create a new event instance. + * + * @param Anime $anime + */ + public function __construct(Anime $anime) + { + parent::__construct($anime); + } + + /** + * Get the model that has fired this event. + * + * @return Anime + */ + public function getModel(): Anime + { + return $this->model; + } + + /** + * Perform cascading deletes. + * + * @return void + */ + public function cascadeDeletes(): void + { + $anime = $this->getModel()->load([Anime::RELATION_SYNONYMS, Anime::RELATION_VIDEOS]); + + $anime->animesynonyms->each(function (AnimeSynonym $synonym) { + AnimeSynonym::withoutEvents(function () use ($synonym) { + $synonym->unsearchable(); + $synonym->delete(); + }); + }); + + $anime->animethemes->each(function (AnimeTheme $theme) { + AnimeTheme::withoutEvents(function () use ($theme) { + Event::until(new ThemeDeleting($theme)); + $theme->unsearchable(); + $theme->delete(); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/AnimeRestored.php b/app/Events/Wiki/Anime/AnimeRestored.php new file mode 100644 index 000000000..e47368288 --- /dev/null +++ b/app/Events/Wiki/Anime/AnimeRestored.php @@ -0,0 +1,83 @@ + + */ +class AnimeRestored extends WikiRestoredEvent implements CascadesRestoresEvent +{ + /** + * Create a new event instance. + * + * @param Anime $anime + */ + public function __construct(Anime $anime) + { + parent::__construct($anime); + } + + /** + * Get the model that has fired this event. + * + * @return Anime + */ + public function getModel(): Anime + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Anime '**{$this->getModel()->getName()}**' has been restored."; + } + + /** + * Perform cascading restores. + * + * @return void + */ + public function cascadeRestores(): void + { + $anime = $this->getModel(); + + $anime->animesynonyms()->withoutGlobalScope(SoftDeletingScope::class)->get()->each(function (AnimeSynonym $synonym) { + AnimeSynonym::withoutEvents(function () use ($synonym) { + $synonym->restore(); + $synonym->searchable(); + }); + }); + + $anime->animethemes()->withoutGlobalScope(SoftDeletingScope::class)->get()->each(function (AnimeTheme $theme) { + AnimeTheme::withoutEvents(function () use ($theme) { + $theme->restore(); + $theme->searchable(); + $theme->animethemeentries()->withoutGlobalScope(SoftDeletingScope::class)->get()->each(function (AnimeThemeEntry $entry) { + AnimeThemeEntry::withoutEvents(function () use ($entry) { + $entry->restore(); + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/AnimeUpdated.php b/app/Events/Wiki/Anime/AnimeUpdated.php new file mode 100644 index 000000000..ad80f9d71 --- /dev/null +++ b/app/Events/Wiki/Anime/AnimeUpdated.php @@ -0,0 +1,69 @@ + + */ +class AnimeUpdated extends WikiUpdatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param Anime $anime + */ + public function __construct(Anime $anime) + { + parent::__construct($anime); + $this->initializeEmbedFields($anime); + } + + /** + * Get the model that has fired this event. + * + * @return Anime + */ + public function getModel(): Anime + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Anime '**{$this->getModel()->getName()}**' has been updated."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $anime = $this->getModel()->load(Anime::RELATION_VIDEOS); + + $anime->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/Synonym/SynonymCreated.php b/app/Events/Wiki/Anime/Synonym/SynonymCreated.php new file mode 100644 index 000000000..80e60e2a3 --- /dev/null +++ b/app/Events/Wiki/Anime/Synonym/SynonymCreated.php @@ -0,0 +1,78 @@ + + */ +class SynonymCreated extends WikiCreatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * The anime that the synonym belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeSynonym $synonym + */ + public function __construct(AnimeSynonym $synonym) + { + parent::__construct($synonym); + $this->anime = $synonym->anime; + } + + /** + * Get the model that has fired this event. + * + * @return AnimeSynonym + */ + public function getModel(): AnimeSynonym + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Synonym '**{$this->getModel()->getName()}**' has been created for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $synonym = $this->getModel()->load(AnimeSynonym::RELATION_VIDEOS); + + $synonym->anime->searchable(); + $synonym->anime->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/Synonym/SynonymDeleted.php b/app/Events/Wiki/Anime/Synonym/SynonymDeleted.php new file mode 100644 index 000000000..7c3b598a9 --- /dev/null +++ b/app/Events/Wiki/Anime/Synonym/SynonymDeleted.php @@ -0,0 +1,101 @@ + + */ +class SynonymDeleted extends WikiDeletedEvent implements UpdateRelatedIndicesEvent +{ + /** + * The anime that the synonym belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeSynonym $synonym + */ + public function __construct(AnimeSynonym $synonym) + { + parent::__construct($synonym); + $this->anime = $synonym->anime; + } + + /** + * Get the model that has fired this event. + * + * @return AnimeSynonym + */ + public function getModel(): AnimeSynonym + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Synonym '**{$this->getModel()->getName()}**' has been deleted for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Synonym '{$this->getModel()->getName()}' has been deleted for Anime '{$this->anime->getName()}'. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = SynonymResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $anime = $this->anime->load(Anime::RELATION_VIDEOS); + + $anime->searchable(); + $anime->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/Synonym/SynonymRestored.php b/app/Events/Wiki/Anime/Synonym/SynonymRestored.php new file mode 100644 index 000000000..ca5f2aa18 --- /dev/null +++ b/app/Events/Wiki/Anime/Synonym/SynonymRestored.php @@ -0,0 +1,78 @@ + + */ +class SynonymRestored extends WikiRestoredEvent implements UpdateRelatedIndicesEvent +{ + /** + * The anime that the synonym belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeSynonym $synonym + */ + public function __construct(AnimeSynonym $synonym) + { + parent::__construct($synonym); + $this->anime = $synonym->anime; + } + + /** + * Get the model that has fired this event. + * + * @return AnimeSynonym + */ + public function getModel(): AnimeSynonym + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Synonym '**{$this->getModel()->getName()}**' has been restored for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $synonym = $this->getModel()->load(AnimeSynonym::RELATION_VIDEOS); + + $synonym->anime->searchable(); + $synonym->anime->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/Synonym/SynonymUpdated.php b/app/Events/Wiki/Anime/Synonym/SynonymUpdated.php new file mode 100644 index 000000000..036e14acb --- /dev/null +++ b/app/Events/Wiki/Anime/Synonym/SynonymUpdated.php @@ -0,0 +1,79 @@ + + */ +class SynonymUpdated extends WikiUpdatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * The anime that the synonym belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeSynonym $synonym + */ + public function __construct(AnimeSynonym $synonym) + { + parent::__construct($synonym); + $this->anime = $synonym->anime; + $this->initializeEmbedFields($synonym); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeSynonym + */ + public function getModel(): AnimeSynonym + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Synonym '**{$this->getModel()->getName()}**' has been updated for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $synonym = $this->getModel()->load(AnimeSynonym::RELATION_VIDEOS); + + $synonym->anime->searchable(); + $synonym->anime->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/Theme/Entry/EntryCreated.php b/app/Events/Wiki/Anime/Theme/Entry/EntryCreated.php new file mode 100644 index 000000000..079bbc7a8 --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/Entry/EntryCreated.php @@ -0,0 +1,60 @@ + + */ +class EntryCreated extends WikiCreatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeThemeEntry $entry + */ + public function __construct(AnimeThemeEntry $entry) + { + parent::__construct($entry); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeThemeEntry + */ + public function getModel(): AnimeThemeEntry + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Entry '**{$this->getModel()->getName()}**' has been created."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $entry = $this->getModel(); + + $entry->videos->each(fn (Video $video) => $video->searchable()); + } +} diff --git a/app/Events/Wiki/Anime/Theme/Entry/EntryDeleted.php b/app/Events/Wiki/Anime/Theme/Entry/EntryDeleted.php new file mode 100644 index 000000000..04ab9193b --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/Entry/EntryDeleted.php @@ -0,0 +1,84 @@ + + */ +class EntryDeleted extends WikiDeletedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeThemeEntry $entry + */ + public function __construct(AnimeThemeEntry $entry) + { + parent::__construct($entry); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeThemeEntry + */ + public function getModel(): AnimeThemeEntry + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Entry '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Entry '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = EntryResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $entry = $this->getModel(); + + $videos = $entry->videos; + $videos->each(fn (Video $video) => $video->searchable()); + } +} diff --git a/app/Events/Wiki/Anime/Theme/Entry/EntryDeleting.php b/app/Events/Wiki/Anime/Theme/Entry/EntryDeleting.php new file mode 100644 index 000000000..daeacc66c --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/Entry/EntryDeleting.php @@ -0,0 +1,55 @@ + + */ +class EntryDeleting extends BaseEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeThemeEntry $entry + */ + public function __construct(AnimeThemeEntry $entry) + { + parent::__construct($entry); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeThemeEntry + */ + public function getModel(): AnimeThemeEntry + { + return $this->model; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $entry = $this->getModel(); + + if ($entry->isForceDeleting()) { + // refresh video documents by detaching entry + $videos = $entry->videos; + $entry->videos()->detach(); + $videos->each(fn (Video $video) => $video->searchable()); + } + } +} diff --git a/app/Events/Wiki/Anime/Theme/Entry/EntryRestored.php b/app/Events/Wiki/Anime/Theme/Entry/EntryRestored.php new file mode 100644 index 000000000..ff9c819dd --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/Entry/EntryRestored.php @@ -0,0 +1,60 @@ + + */ +class EntryRestored extends WikiRestoredEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeThemeEntry $entry + */ + public function __construct(AnimeThemeEntry $entry) + { + parent::__construct($entry); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeThemeEntry + */ + public function getModel(): AnimeThemeEntry + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Entry '**{$this->getModel()->getName()}**' has been restored."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $entry = $this->getModel(); + + $entry->videos->each(fn (Video $video) => $video->searchable()); + } +} diff --git a/app/Events/Wiki/Anime/Theme/Entry/EntryUpdated.php b/app/Events/Wiki/Anime/Theme/Entry/EntryUpdated.php new file mode 100644 index 000000000..4c38093f0 --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/Entry/EntryUpdated.php @@ -0,0 +1,61 @@ + + */ +class EntryUpdated extends WikiUpdatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeThemeEntry $entry + */ + public function __construct(AnimeThemeEntry $entry) + { + parent::__construct($entry); + $this->initializeEmbedFields($entry); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeThemeEntry + */ + public function getModel(): AnimeThemeEntry + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Entry '**{$this->getModel()->getName()}**' has been updated."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $entry = $this->getModel(); + + $entry->videos->each(fn (Video $video) => $video->searchable()); + } +} diff --git a/app/Events/Wiki/Anime/Theme/ThemeCreated.php b/app/Events/Wiki/Anime/Theme/ThemeCreated.php new file mode 100644 index 000000000..edc99eacf --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/ThemeCreated.php @@ -0,0 +1,73 @@ + + */ +class ThemeCreated extends WikiCreatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * The anime that the theme belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeTheme $theme + */ + public function __construct(AnimeTheme $theme) + { + parent::__construct($theme); + $this->anime = $theme->anime; + } + + /** + * Get the model that has fired this event. + * + * @return AnimeTheme + */ + public function getModel(): AnimeTheme + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Theme '**{$this->getModel()->getName()}**' has been created for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $theme = $this->getModel()->load(AnimeTheme::RELATION_VIDEOS); + + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + } +} diff --git a/app/Events/Wiki/Anime/Theme/ThemeCreating.php b/app/Events/Wiki/Anime/Theme/ThemeCreating.php new file mode 100644 index 000000000..a8b385c0d --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/ThemeCreating.php @@ -0,0 +1,36 @@ + + */ +class ThemeCreating extends BaseEvent +{ + /** + * Create a new event instance. + * + * @param AnimeTheme $theme + */ + public function __construct(AnimeTheme $theme) + { + parent::__construct($theme); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeTheme + */ + public function getModel(): AnimeTheme + { + return $this->model; + } +} diff --git a/app/Events/Wiki/Anime/Theme/ThemeDeleted.php b/app/Events/Wiki/Anime/Theme/ThemeDeleted.php new file mode 100644 index 000000000..4eeb413f5 --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/ThemeDeleted.php @@ -0,0 +1,78 @@ + + */ +class ThemeDeleted extends WikiDeletedEvent +{ + /** + * The anime that the theme belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeTheme $theme + */ + public function __construct(AnimeTheme $theme) + { + parent::__construct($theme); + $this->anime = $theme->anime; + } + + /** + * Get the model that has fired this event. + * + * @return AnimeTheme + */ + public function getModel(): AnimeTheme + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Theme '**{$this->getModel()->getName()}**' has been deleted for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Theme '{$this->getModel()->getName()}' has been deleted for Anime '{$this->anime->getName()}'. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = ThemeResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/Anime/Theme/ThemeDeleting.php b/app/Events/Wiki/Anime/Theme/ThemeDeleting.php new file mode 100644 index 000000000..03bbfadd5 --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/ThemeDeleting.php @@ -0,0 +1,59 @@ + + */ +class ThemeDeleting extends BaseEvent implements CascadesDeletesEvent +{ + /** + * Create a new event instance. + * + * @param AnimeTheme $theme + */ + public function __construct(AnimeTheme $theme) + { + parent::__construct($theme); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeTheme + */ + public function getModel(): AnimeTheme + { + return $this->model; + } + + /** + * Perform cascading deletes. + * + * @return void + */ + public function cascadeDeletes(): void + { + $theme = $this->getModel()->load(AnimeTheme::RELATION_VIDEOS); + + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + AnimeThemeEntry::withoutEvents(function () use ($entry) { + $entry->unsearchable(); + $entry->delete(); + + $videos = $entry->videos; + $videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/Theme/ThemeRestored.php b/app/Events/Wiki/Anime/Theme/ThemeRestored.php new file mode 100644 index 000000000..1e4ec640e --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/ThemeRestored.php @@ -0,0 +1,77 @@ + + */ +class ThemeRestored extends WikiRestoredEvent implements CascadesRestoresEvent +{ + /** + * The anime that the theme belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeTheme $theme + */ + public function __construct(AnimeTheme $theme) + { + parent::__construct($theme); + $this->anime = $theme->anime; + } + + /** + * Get the model that has fired this event. + * + * @return AnimeTheme + */ + public function getModel(): AnimeTheme + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Theme '**{$this->getModel()->getName()}**' has been restored for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Perform cascading restores. + * + * @return void + */ + public function cascadeRestores(): void + { + $theme = $this->getModel(); + + $theme->animethemeentries()->withoutGlobalScope(SoftDeletingScope::class)->get()->each(function (AnimeThemeEntry $entry) { + AnimeThemeEntry::withoutEvents(function () use ($entry) { + $entry->restore(); + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Anime/Theme/ThemeUpdated.php b/app/Events/Wiki/Anime/Theme/ThemeUpdated.php new file mode 100644 index 000000000..0316af79f --- /dev/null +++ b/app/Events/Wiki/Anime/Theme/ThemeUpdated.php @@ -0,0 +1,74 @@ + + */ +class ThemeUpdated extends WikiUpdatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * The anime that the theme belongs to. + * + * @var Anime + */ + protected Anime $anime; + + /** + * Create a new event instance. + * + * @param AnimeTheme $theme + */ + public function __construct(AnimeTheme $theme) + { + parent::__construct($theme); + $this->anime = $theme->anime; + $this->initializeEmbedFields($theme); + } + + /** + * Get the model that has fired this event. + * + * @return AnimeTheme + */ + public function getModel(): AnimeTheme + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Theme '**{$this->getModel()->getName()}**' has been updated for Anime '**{$this->anime->getName()}**'."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $theme = $this->getModel()->load(AnimeTheme::RELATION_VIDEOS); + + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + } +} diff --git a/app/Events/Wiki/Artist/ArtistCreated.php b/app/Events/Wiki/Artist/ArtistCreated.php new file mode 100644 index 000000000..1d0029fd4 --- /dev/null +++ b/app/Events/Wiki/Artist/ArtistCreated.php @@ -0,0 +1,46 @@ + + */ +class ArtistCreated extends WikiCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Artist $artist + */ + public function __construct(Artist $artist) + { + parent::__construct($artist); + } + + /** + * Get the model that has fired this event. + * + * @return Artist + */ + public function getModel(): Artist + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Artist '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Wiki/Artist/ArtistDeleted.php b/app/Events/Wiki/Artist/ArtistDeleted.php new file mode 100644 index 000000000..f099a1c62 --- /dev/null +++ b/app/Events/Wiki/Artist/ArtistDeleted.php @@ -0,0 +1,69 @@ + + */ +class ArtistDeleted extends WikiDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Artist $artist + */ + public function __construct(Artist $artist) + { + parent::__construct($artist); + } + + /** + * Get the model that has fired this event. + * + * @return Artist + */ + public function getModel(): Artist + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Artist '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Artist '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = ArtistResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/Artist/ArtistRestored.php b/app/Events/Wiki/Artist/ArtistRestored.php new file mode 100644 index 000000000..a5e7f3fc8 --- /dev/null +++ b/app/Events/Wiki/Artist/ArtistRestored.php @@ -0,0 +1,46 @@ + + */ +class ArtistRestored extends WikiRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Artist $artist + */ + public function __construct(Artist $artist) + { + parent::__construct($artist); + } + + /** + * Get the model that has fired this event. + * + * @return Artist + */ + public function getModel(): Artist + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Artist '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Wiki/Artist/ArtistUpdated.php b/app/Events/Wiki/Artist/ArtistUpdated.php new file mode 100644 index 000000000..ae5e21dfd --- /dev/null +++ b/app/Events/Wiki/Artist/ArtistUpdated.php @@ -0,0 +1,47 @@ + + */ +class ArtistUpdated extends WikiUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Artist $artist + */ + public function __construct(Artist $artist) + { + parent::__construct($artist); + $this->initializeEmbedFields($artist); + } + + /** + * Get the model that has fired this event. + * + * @return Artist + */ + public function getModel(): Artist + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Artist '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Wiki/ExternalResource/ExternalResourceCreated.php b/app/Events/Wiki/ExternalResource/ExternalResourceCreated.php new file mode 100644 index 000000000..0d88b0720 --- /dev/null +++ b/app/Events/Wiki/ExternalResource/ExternalResourceCreated.php @@ -0,0 +1,46 @@ + + */ +class ExternalResourceCreated extends WikiCreatedEvent +{ + /** + * Create a new event instance. + * + * @param ExternalResource $resource + */ + public function __construct(ExternalResource $resource) + { + parent::__construct($resource); + } + + /** + * Get the model that has fired this event. + * + * @return ExternalResource + */ + public function getModel(): ExternalResource + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Resource '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Wiki/ExternalResource/ExternalResourceDeleted.php b/app/Events/Wiki/ExternalResource/ExternalResourceDeleted.php new file mode 100644 index 000000000..7b584dfa1 --- /dev/null +++ b/app/Events/Wiki/ExternalResource/ExternalResourceDeleted.php @@ -0,0 +1,69 @@ + + */ +class ExternalResourceDeleted extends WikiDeletedEvent +{ + /** + * Create a new event instance. + * + * @param ExternalResource $resource + */ + public function __construct(ExternalResource $resource) + { + parent::__construct($resource); + } + + /** + * Get the model that has fired this event. + * + * @return ExternalResource + */ + public function getModel(): ExternalResource + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Resource '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Resource '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = ExternalResourceResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/ExternalResource/ExternalResourceRestored.php b/app/Events/Wiki/ExternalResource/ExternalResourceRestored.php new file mode 100644 index 000000000..75f79bc01 --- /dev/null +++ b/app/Events/Wiki/ExternalResource/ExternalResourceRestored.php @@ -0,0 +1,46 @@ + + */ +class ExternalResourceRestored extends WikiRestoredEvent +{ + /** + * Create a new event instance. + * + * @param ExternalResource $resource + */ + public function __construct(ExternalResource $resource) + { + parent::__construct($resource); + } + + /** + * Get the model that has fired this event. + * + * @return ExternalResource + */ + public function getModel(): ExternalResource + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Resource '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Wiki/ExternalResource/ExternalResourceUpdated.php b/app/Events/Wiki/ExternalResource/ExternalResourceUpdated.php new file mode 100644 index 000000000..e86893825 --- /dev/null +++ b/app/Events/Wiki/ExternalResource/ExternalResourceUpdated.php @@ -0,0 +1,47 @@ + + */ +class ExternalResourceUpdated extends WikiUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param ExternalResource $resource + */ + public function __construct(ExternalResource $resource) + { + parent::__construct($resource); + $this->initializeEmbedFields($resource); + } + + /** + * Get the model that has fired this event. + * + * @return ExternalResource + */ + public function getModel(): ExternalResource + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Resource '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Wiki/Image/ImageCreated.php b/app/Events/Wiki/Image/ImageCreated.php new file mode 100644 index 000000000..6b25de644 --- /dev/null +++ b/app/Events/Wiki/Image/ImageCreated.php @@ -0,0 +1,46 @@ + + */ +class ImageCreated extends WikiCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Image $image + */ + public function __construct(Image $image) + { + parent::__construct($image); + } + + /** + * Get the model that has fired this event. + * + * @return Image + */ + public function getModel(): Image + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Image '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Wiki/Image/ImageDeleted.php b/app/Events/Wiki/Image/ImageDeleted.php new file mode 100644 index 000000000..bb4e5db54 --- /dev/null +++ b/app/Events/Wiki/Image/ImageDeleted.php @@ -0,0 +1,69 @@ + + */ +class ImageDeleted extends WikiDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Image $image + */ + public function __construct(Image $image) + { + parent::__construct($image); + } + + /** + * Get the model that has fired this event. + * + * @return Image + */ + public function getModel(): Image + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Image '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Image '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = ImageResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/Image/ImageDeleting.php b/app/Events/Wiki/Image/ImageDeleting.php new file mode 100644 index 000000000..9c76f936c --- /dev/null +++ b/app/Events/Wiki/Image/ImageDeleting.php @@ -0,0 +1,36 @@ + + */ +class ImageDeleting extends BaseEvent +{ + /** + * Create a new event instance. + * + * @param Image $image + */ + public function __construct(Image $image) + { + parent::__construct($image); + } + + /** + * Get the model that has fired this event. + * + * @return Image + */ + public function getModel(): Image + { + return $this->model; + } +} diff --git a/app/Events/Wiki/Image/ImageRestored.php b/app/Events/Wiki/Image/ImageRestored.php new file mode 100644 index 000000000..5acc1118a --- /dev/null +++ b/app/Events/Wiki/Image/ImageRestored.php @@ -0,0 +1,46 @@ + + */ +class ImageRestored extends WikiRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Image $image + */ + public function __construct(Image $image) + { + parent::__construct($image); + } + + /** + * Get the model that has fired this event. + * + * @return Image + */ + public function getModel(): Image + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Image '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Wiki/Image/ImageUpdated.php b/app/Events/Wiki/Image/ImageUpdated.php new file mode 100644 index 000000000..2ab57821e --- /dev/null +++ b/app/Events/Wiki/Image/ImageUpdated.php @@ -0,0 +1,47 @@ + + */ +class ImageUpdated extends WikiUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Image $image + */ + public function __construct(Image $image) + { + parent::__construct($image); + $this->initializeEmbedFields($image); + } + + /** + * Get the model that has fired this event. + * + * @return Image + */ + public function getModel(): Image + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Image '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Wiki/Series/SeriesCreated.php b/app/Events/Wiki/Series/SeriesCreated.php new file mode 100644 index 000000000..da1645f2b --- /dev/null +++ b/app/Events/Wiki/Series/SeriesCreated.php @@ -0,0 +1,46 @@ + + */ +class SeriesCreated extends WikiCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Series $series + */ + public function __construct(Series $series) + { + parent::__construct($series); + } + + /** + * Get the model that has fired this event. + * + * @return Series + */ + public function getModel(): Series + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Series '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Wiki/Series/SeriesDeleted.php b/app/Events/Wiki/Series/SeriesDeleted.php new file mode 100644 index 000000000..a5c4c7200 --- /dev/null +++ b/app/Events/Wiki/Series/SeriesDeleted.php @@ -0,0 +1,69 @@ + + */ +class SeriesDeleted extends WikiDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Series $series + */ + public function __construct(Series $series) + { + parent::__construct($series); + } + + /** + * Get the model that has fired this event. + * + * @return Series + */ + public function getModel(): Series + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Series '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Series '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = SeriesResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/Series/SeriesRestored.php b/app/Events/Wiki/Series/SeriesRestored.php new file mode 100644 index 000000000..57f579ed3 --- /dev/null +++ b/app/Events/Wiki/Series/SeriesRestored.php @@ -0,0 +1,46 @@ + + */ +class SeriesRestored extends WikiRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Series $series + */ + public function __construct(Series $series) + { + parent::__construct($series); + } + + /** + * Get the model that has fired this event. + * + * @return Series + */ + public function getModel(): Series + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Series '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Wiki/Series/SeriesUpdated.php b/app/Events/Wiki/Series/SeriesUpdated.php new file mode 100644 index 000000000..835239bc6 --- /dev/null +++ b/app/Events/Wiki/Series/SeriesUpdated.php @@ -0,0 +1,47 @@ + + */ +class SeriesUpdated extends WikiUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Series $series + */ + public function __construct(Series $series) + { + parent::__construct($series); + $this->initializeEmbedFields($series); + } + + /** + * Get the model that has fired this event. + * + * @return Series + */ + public function getModel(): Series + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Series '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Wiki/Song/SongCreated.php b/app/Events/Wiki/Song/SongCreated.php new file mode 100644 index 000000000..b44ce7326 --- /dev/null +++ b/app/Events/Wiki/Song/SongCreated.php @@ -0,0 +1,71 @@ + + */ +class SongCreated extends WikiCreatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param Song $song + */ + public function __construct(Song $song) + { + parent::__construct($song); + } + + /** + * Get the model that has fired this event. + * + * @return Song + */ + public function getModel(): Song + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Song '**{$this->getModel()->getName()}**' has been created."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $song = $this->getModel()->load([Song::RELATION_ARTISTS, Song::RELATION_VIDEOS]); + + $song->artists->each(fn (Artist $artist) => $artist->searchable()); + + $song->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Song/SongDeleted.php b/app/Events/Wiki/Song/SongDeleted.php new file mode 100644 index 000000000..a204f9e98 --- /dev/null +++ b/app/Events/Wiki/Song/SongDeleted.php @@ -0,0 +1,95 @@ + + */ +class SongDeleted extends WikiDeletedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param Song $song + */ + public function __construct(Song $song) + { + parent::__construct($song); + } + + /** + * Get the model that has fired this event. + * + * @return Song + */ + public function getModel(): Song + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Song '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Song '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = SongResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $song = $this->getModel()->load([Song::RELATION_ARTISTS, Song::RELATION_VIDEOS]); + + $artists = $song->artists; + $artists->each(fn (Artist $artist) => $artist->searchable()); + + $song->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Song/SongDeleting.php b/app/Events/Wiki/Song/SongDeleting.php new file mode 100644 index 000000000..5d8a770a7 --- /dev/null +++ b/app/Events/Wiki/Song/SongDeleting.php @@ -0,0 +1,71 @@ + + */ +class SongDeleting extends BaseEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param Song $song + */ + public function __construct(Song $song) + { + parent::__construct($song); + } + + /** + * Get the model that has fired this event. + * + * @return Song + */ + public function getModel(): Song + { + return $this->model; + } + + /** + * Perform cascading deletes. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $song = $this->getModel()->load([Song::RELATION_ARTISTS, Song::RELATION_VIDEOS]); + + if ($song->isForceDeleting()) { + // refresh artist documents by detaching song + $artists = $song->artists; + $song->artists()->detach(); + $artists->each(fn (Artist $artist) => $artist->searchable()); + + // refresh theme documents by dissociating song + $song->animethemes->each(function (AnimeTheme $theme) { + AnimeTheme::withoutEvents(function () use ($theme) { + $theme->song()->dissociate(); + $theme->save(); + }); + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } + } +} diff --git a/app/Events/Wiki/Song/SongRestored.php b/app/Events/Wiki/Song/SongRestored.php new file mode 100644 index 000000000..a289a97db --- /dev/null +++ b/app/Events/Wiki/Song/SongRestored.php @@ -0,0 +1,73 @@ + + */ +class SongRestored extends WikiRestoredEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param Song $song + */ + public function __construct(Song $song) + { + parent::__construct($song); + } + + /** + * Get the model that has fired this event. + * + * @return Song + */ + public function getModel(): Song + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Song '**{$this->getModel()->getName()}**' has been restored."; + } + + /** + * Perform cascading deletes. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $song = $this->getModel()->load([Song::RELATION_ARTISTS, Song::RELATION_VIDEOS]); + + // refresh artist documents by detaching song + $artists = $song->artists; + $artists->each(fn (Artist $artist) => $artist->searchable()); + + $song->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Song/SongUpdated.php b/app/Events/Wiki/Song/SongUpdated.php new file mode 100644 index 000000000..123f6f35b --- /dev/null +++ b/app/Events/Wiki/Song/SongUpdated.php @@ -0,0 +1,72 @@ + + */ +class SongUpdated extends WikiUpdatedEvent implements UpdateRelatedIndicesEvent +{ + /** + * Create a new event instance. + * + * @param Song $song + */ + public function __construct(Song $song) + { + parent::__construct($song); + $this->initializeEmbedFields($song); + } + + /** + * Get the model that has fired this event. + * + * @return Song + */ + public function getModel(): Song + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Song '**{$this->getModel()->getName()}**' has been updated."; + } + + /** + * Perform updates on related indices. + * + * @return void + */ + public function updateRelatedIndices(): void + { + $song = $this->getModel()->load([Song::RELATION_ARTISTS, Song::RELATION_VIDEOS]); + + $song->artists->each(fn (Artist $artist) => $artist->searchable()); + + $song->animethemes->each(function (AnimeTheme $theme) { + $theme->searchable(); + $theme->animethemeentries->each(function (AnimeThemeEntry $entry) { + $entry->searchable(); + $entry->videos->each(fn (Video $video) => $video->searchable()); + }); + }); + } +} diff --git a/app/Events/Wiki/Studio/StudioCreated.php b/app/Events/Wiki/Studio/StudioCreated.php new file mode 100644 index 000000000..a17258175 --- /dev/null +++ b/app/Events/Wiki/Studio/StudioCreated.php @@ -0,0 +1,46 @@ + + */ +class StudioCreated extends WikiCreatedEvent +{ + /** + * Create a new event instance. + * + * @param Studio $studio + */ + public function __construct(Studio $studio) + { + parent::__construct($studio); + } + + /** + * Get the model that has fired this event. + * + * @return Studio + */ + public function getModel(): Studio + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Studio '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Wiki/Studio/StudioDeleted.php b/app/Events/Wiki/Studio/StudioDeleted.php new file mode 100644 index 000000000..684d71a70 --- /dev/null +++ b/app/Events/Wiki/Studio/StudioDeleted.php @@ -0,0 +1,69 @@ + + */ +class StudioDeleted extends WikiDeletedEvent +{ + /** + * Create a new event instance. + * + * @param Studio $studio + */ + public function __construct(Studio $studio) + { + parent::__construct($studio); + } + + /** + * Get the model that has fired this event. + * + * @return Studio + */ + public function getModel(): Studio + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Studio '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Studio '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = StudioResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/Studio/StudioRestored.php b/app/Events/Wiki/Studio/StudioRestored.php new file mode 100644 index 000000000..7f2f69cb1 --- /dev/null +++ b/app/Events/Wiki/Studio/StudioRestored.php @@ -0,0 +1,46 @@ + + */ +class StudioRestored extends WikiRestoredEvent +{ + /** + * Create a new event instance. + * + * @param Studio $studio + */ + public function __construct(Studio $studio) + { + parent::__construct($studio); + } + + /** + * Get the model that has fired this event. + * + * @return Studio + */ + public function getModel(): Studio + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Studio '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Wiki/Studio/StudioUpdated.php b/app/Events/Wiki/Studio/StudioUpdated.php new file mode 100644 index 000000000..240421720 --- /dev/null +++ b/app/Events/Wiki/Studio/StudioUpdated.php @@ -0,0 +1,47 @@ + + */ +class StudioUpdated extends WikiUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Studio $studio + */ + public function __construct(Studio $studio) + { + parent::__construct($studio); + $this->initializeEmbedFields($studio); + } + + /** + * Get the model that has fired this event. + * + * @return Studio + */ + public function getModel(): Studio + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Studio '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Events/Wiki/Video/VideoCreated.php b/app/Events/Wiki/Video/VideoCreated.php new file mode 100644 index 000000000..4e31762f5 --- /dev/null +++ b/app/Events/Wiki/Video/VideoCreated.php @@ -0,0 +1,46 @@ + + */ +class VideoCreated extends WikiCreatedEvent +{ + /** + * 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; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Video '**{$this->getModel()->getName()}**' has been created."; + } +} diff --git a/app/Events/Wiki/Video/VideoCreating.php b/app/Events/Wiki/Video/VideoCreating.php new file mode 100644 index 000000000..4ee40fd36 --- /dev/null +++ b/app/Events/Wiki/Video/VideoCreating.php @@ -0,0 +1,36 @@ + + */ +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; + } +} diff --git a/app/Events/Wiki/Video/VideoDeleted.php b/app/Events/Wiki/Video/VideoDeleted.php new file mode 100644 index 000000000..e61c13b15 --- /dev/null +++ b/app/Events/Wiki/Video/VideoDeleted.php @@ -0,0 +1,69 @@ + + */ +class VideoDeleted extends WikiDeletedEvent +{ + /** + * 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; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Video '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Get the message for the nova notification. + * + * @return string + */ + protected function getNotificationMessage(): string + { + return "Video '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review."; + } + + /** + * Get the URL for the nova notification. + * + * @return string + */ + protected function getNotificationUrl(): string + { + $uriKey = VideoResource::uriKey(); + + return "/resources/$uriKey/{$this->getModel()->getKey()}"; + } +} diff --git a/app/Events/Wiki/Video/VideoRestored.php b/app/Events/Wiki/Video/VideoRestored.php new file mode 100644 index 000000000..df887cd51 --- /dev/null +++ b/app/Events/Wiki/Video/VideoRestored.php @@ -0,0 +1,46 @@ + + */ +class VideoRestored extends WikiRestoredEvent +{ + /** + * 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; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Video '**{$this->getModel()->getName()}**' has been restored."; + } +} diff --git a/app/Events/Wiki/Video/VideoUpdated.php b/app/Events/Wiki/Video/VideoUpdated.php new file mode 100644 index 000000000..a06ee4710 --- /dev/null +++ b/app/Events/Wiki/Video/VideoUpdated.php @@ -0,0 +1,47 @@ + + */ +class VideoUpdated extends WikiUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param Video $video + */ + public function __construct(Video $video) + { + parent::__construct($video); + $this->initializeEmbedFields($video); + } + + /** + * Get the model that has fired this event. + * + * @return Video + */ + public function getModel(): Video + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Video '**{$this->getModel()->getName()}**' has been updated."; + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 59c585dc1..8b7f36e83 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -1,55 +1,24 @@ */ protected $dontFlash = [ + 'current_password', 'password', 'password_confirmation', ]; - - /** - * Report or log an exception. - * - * @param \Throwable $exception - * @return void - * - * @throws \Exception - */ - public function report(Throwable $exception) - { - parent::report($exception); - } - - /** - * Render an exception into an HTTP response. - * - * @param \Illuminate\Http\Request $request - * @param \Throwable $exception - * @return \Symfony\Component\HttpFoundation\Response - * - * @throws \Throwable - */ - public function render($request, Throwable $exception) - { - return parent::render($request, $exception); - } } diff --git a/app/Grills/Grill.php b/app/Grills/Grill.php deleted file mode 100644 index c75bc8365..000000000 --- a/app/Grills/Grill.php +++ /dev/null @@ -1,18 +0,0 @@ -path = $path; - } - - public function getPath() - { - return $this->path; - } -} diff --git a/app/Grills/GrillFactory.php b/app/Grills/GrillFactory.php deleted file mode 100644 index cea443cc2..000000000 --- a/app/Grills/GrillFactory.php +++ /dev/null @@ -1,25 +0,0 @@ -files(); - - // Retrieve a random grill with Faker - $faker = Factory::create(); - $grill = $faker->randomElement($grills); - $grill_path = $grill_disk->url($grill); - - // Return Grill - return new Grill($grill_path); - } -} diff --git a/app/Http/Api/Criteria/Field/Criteria.php b/app/Http/Api/Criteria/Field/Criteria.php new file mode 100644 index 000000000..e555dea99 --- /dev/null +++ b/app/Http/Api/Criteria/Field/Criteria.php @@ -0,0 +1,54 @@ + $fields + */ + public function __construct(protected readonly string $type, protected readonly Collection $fields) + { + } + + /** + * Get the type that this sparse fieldsets mapping belongs to. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Get the fields to include for the type. + * + * @return Collection + */ + public function getFields(): Collection + { + return $this->fields; + } + + /** + * Is the field allowed? + * + * @param string $field + * @return bool + */ + public function isAllowedField(string $field): bool + { + return $this->fields->contains($field); + } +} diff --git a/app/Http/Api/Criteria/Filter/Criteria.php b/app/Http/Api/Criteria/Filter/Criteria.php new file mode 100644 index 000000000..b0bbe7635 --- /dev/null +++ b/app/Http/Api/Criteria/Filter/Criteria.php @@ -0,0 +1,139 @@ +scope; + } + + /** + * Get the field that the predicate is applying an expression on. + * + * @return string + */ + public function getField(): string + { + return $this->predicate->getColumn(); + } + + /** + * Get the comparison operator. + * + * @return ComparisonOperator|null + */ + public function getComparisonOperator(): ?ComparisonOperator + { + return $this->predicate->getOperator(); + } + + /** + * Get the logical operator. + * + * @return BinaryLogicalOperator + */ + public function getLogicalOperator(): BinaryLogicalOperator + { + return $this->operator; + } + + /** + * Get the filter values. + * + * @return array + */ + public function getFilterValues(): array + { + $value = $this->predicate->getExpression()->getValue(); + + if ($value instanceof Collection) { + return $value->all(); + } + + return Arr::wrap($value); + } + + /** + * Determine if this filter should be applied. + * + * @param Filter $filter + * @param Scope $scope + * @return bool + */ + public function shouldFilter(Filter $filter, Scope $scope): bool + { + // Don't apply filter if key does not match + if ($this->getField() !== $filter->getKey()) { + return false; + } + + // Don't apply filter if scope does not match + if (! $this->getScope()->isWithinScope($scope)) { + return false; + } + + $filterValues = $filter->getFilterValues($this->getFilterValues()); + + // Apply filter if we have a subset of valid values specified + return ! empty($filterValues) && ! $filter->isAllFilterValues($filterValues); + } + + /** + * Apply criteria to builder. + * + * @param Builder $builder + * @param Filter $filter + * @param ReadQuery $query + * @return Builder + */ + abstract public function filter(Builder $builder, Filter $filter, ReadQuery $query): Builder; + + /** + * Create a new criteria instance from query string. + * + * @param Scope $scope + * @param string $filterParam + * @param mixed $filterValues + * @return static + */ + abstract public static function make(Scope $scope, string $filterParam, mixed $filterValues): static; +} diff --git a/app/Http/Api/Criteria/Filter/Expression.php b/app/Http/Api/Criteria/Filter/Expression.php new file mode 100644 index 000000000..981f106e1 --- /dev/null +++ b/app/Http/Api/Criteria/Filter/Expression.php @@ -0,0 +1,30 @@ +value; + } +} diff --git a/app/Http/Api/Criteria/Filter/HasCriteria.php b/app/Http/Api/Criteria/Filter/HasCriteria.php new file mode 100644 index 000000000..56bc61a40 --- /dev/null +++ b/app/Http/Api/Criteria/Filter/HasCriteria.php @@ -0,0 +1,154 @@ +count; + } + + /** + * Create a new criteria instance from query string. + * + * @param Scope $scope + * @param string $filterParam + * @param mixed $filterValues + * @return static + */ + public static function make(Scope $scope, string $filterParam, mixed $filterValues): static + { + $field = ''; + $comparisonOperator = ComparisonOperator::GTE(); + $count = 1; + $logicalOperator = BinaryLogicalOperator::AND(); + + $filterParts = Str::of($filterParam)->explode(Criteria::PARAM_SEPARATOR); + while ($filterParts->isNotEmpty()) { + $filterPart = $filterParts->pop(); + + // Set logical operator + if (empty($field) && BinaryLogicalOperator::hasKey(Str::upper($filterPart))) { + try { + $logicalOperator = BinaryLogicalOperator::fromKey(Str::upper($filterPart)); + } catch (InvalidEnumKeyException $e) { + Log::error($e->getMessage()); + } + continue; + } + + // Set count + if ( + empty($field) + && filter_var($filterPart, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE) !== null + ) { + $count = filter_var($filterPart, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE); + continue; + } + + // Set comparison operator + if (empty($field) && ComparisonOperator::hasKey(Str::upper($filterPart))) { + try { + $comparisonOperator = ComparisonOperator::fromKey(Str::upper($filterPart)); + } catch (InvalidEnumKeyException $e) { + Log::error($e->getMessage()); + } + continue; + } + + // Set found has param + if (empty($field) && $filterPart === HasCriteria::PARAM_VALUE) { + $field = $filterPart; + } + } + + $expression = new Expression(Str::of($filterValues)->explode(Criteria::VALUE_SEPARATOR)); + + return new static( + new Predicate(HasCriteria::PARAM_VALUE, $comparisonOperator, $expression), + $logicalOperator, + $scope, + $count + ); + } + + /** + * Apply criteria to builder. + * + * @param Builder $builder + * @param Filter $filter + * @param ReadQuery $query + * @return Builder + */ + public function filter(Builder $builder, Filter $filter, ReadQuery $query): Builder + { + $schema = $query->schema(); + $filterValues = $filter->getFilterValues($this->getFilterValues()); + + foreach ($filterValues as $filterValue) { + $scope = ScopeParser::parse($filterValue); + $relationSchema = $schema->relation($filterValue); + + $builder = $builder->has( + $filterValue, + $this->getComparisonOperator()?->value, + $this->count, + $this->getLogicalOperator()->value, + function (Builder $relationBuilder) use ($scope, $query, $relationSchema) { + if ($relationSchema !== null) { + foreach ($query->getFilterCriteria() as $criteria) { + foreach ($relationSchema->filters() as $filter) { + if ($criteria->shouldFilter($filter, $scope)) { + $criteria->filter($relationBuilder, $filter, $query); + } + } + } + } + } + ); + } + + return $builder; + } +} diff --git a/app/Http/Api/Criteria/Filter/Predicate.php b/app/Http/Api/Criteria/Filter/Predicate.php new file mode 100644 index 000000000..6b1632607 --- /dev/null +++ b/app/Http/Api/Criteria/Filter/Predicate.php @@ -0,0 +1,57 @@ +column; + } + + /** + * Get the predicate operator. + * + * @return ComparisonOperator|null + */ + public function getOperator(): ?ComparisonOperator + { + return $this->operator; + } + + /** + * Get the predicate expression. + * + * @return Expression + */ + public function getExpression(): Expression + { + return $this->expression; + } +} diff --git a/app/Http/Api/Criteria/Filter/TrashedCriteria.php b/app/Http/Api/Criteria/Filter/TrashedCriteria.php new file mode 100644 index 000000000..a6ef9b172 --- /dev/null +++ b/app/Http/Api/Criteria/Filter/TrashedCriteria.php @@ -0,0 +1,79 @@ +explode(Criteria::VALUE_SEPARATOR)); + + return new static( + new Predicate(TrashedCriteria::PARAM_VALUE, null, $expression), + BinaryLogicalOperator::AND(), + $scope + ); + } + + /** + * Apply criteria to builder. + * + * @param Builder $builder + * @param Filter $filter + * @param ReadQuery $query + * @return Builder + */ + public function filter(Builder $builder, Filter $filter, ReadQuery $query): Builder + { + $filterValues = $filter->getFilterValues($this->getFilterValues()); + + foreach ($filterValues as $filterValue) { + $builder = match (Str::lower($filterValue)) { + TrashedStatus::WITH => $builder->withTrashed(), + TrashedStatus::WITHOUT => $builder->withoutTrashed(), + TrashedStatus::ONLY => $builder->onlyTrashed(), + default => $builder, + }; + } + + return $builder; + } +} diff --git a/app/Http/Api/Criteria/Filter/WhereCriteria.php b/app/Http/Api/Criteria/Filter/WhereCriteria.php new file mode 100644 index 000000000..b112d4059 --- /dev/null +++ b/app/Http/Api/Criteria/Filter/WhereCriteria.php @@ -0,0 +1,107 @@ +explode(Criteria::PARAM_SEPARATOR); + while ($filterParts->isNotEmpty()) { + $filterPart = $filterParts->pop(); + + // Set logical operator + if (empty($field) && BinaryLogicalOperator::hasKey(Str::upper($filterPart))) { + try { + $logicalOperator = BinaryLogicalOperator::fromKey(Str::upper($filterPart)); + } catch (InvalidEnumKeyException $e) { + Log::error($e->getMessage()); + } + continue; + } + + // Set comparison operator + if (empty($field) && ComparisonOperator::hasKey(Str::upper($filterPart))) { + try { + $comparisonOperator = ComparisonOperator::fromKey(Str::upper($filterPart)); + } catch (InvalidEnumKeyException $e) { + Log::error($e->getMessage()); + } + continue; + } + + // Set field + if (empty($field)) { + $field = Str::lower($filterPart); + } + } + + $expression = new Expression($filterValues); + + return new static( + new Predicate($field, $comparisonOperator, $expression), + $logicalOperator, + $scope + ); + } + + /** + * Apply criteria to builder. + * + * @param Builder $builder + * @param Filter $filter + * @param ReadQuery $query + * @return Builder + */ + public function filter(Builder $builder, Filter $filter, ReadQuery $query): Builder + { + return $builder->where( + $builder->qualifyColumn($filter->getColumn()), + $this->getComparisonOperator()?->value, + $filter->getFilterValues($this->getFilterValues()), + $this->getLogicalOperator()->value + ); + } +} diff --git a/app/Http/Api/Criteria/Filter/WhereInCriteria.php b/app/Http/Api/Criteria/Filter/WhereInCriteria.php new file mode 100644 index 000000000..37bd2e92d --- /dev/null +++ b/app/Http/Api/Criteria/Filter/WhereInCriteria.php @@ -0,0 +1,116 @@ +not; + } + + /** + * Create a new criteria instance from query string. + * + * @param Scope $scope + * @param string $filterParam + * @param mixed $filterValues + * @return static + */ + public static function make(Scope $scope, string $filterParam, mixed $filterValues): static + { + $field = ''; + $operator = BinaryLogicalOperator::AND(); + $not = false; + + $filterParts = Str::of($filterParam)->explode(Criteria::PARAM_SEPARATOR); + while ($filterParts->isNotEmpty()) { + $filterPart = $filterParts->pop(); + + // Set Not + if (empty($field) && UnaryLogicalOperator::hasKey(Str::upper($filterPart))) { + $not = true; + continue; + } + + // Set operator + if (empty($field) && BinaryLogicalOperator::hasKey(Str::upper($filterPart))) { + try { + $operator = BinaryLogicalOperator::fromKey(Str::upper($filterPart)); + } catch (InvalidEnumKeyException $e) { + Log::error($e->getMessage()); + } + continue; + } + + // Set field + if (empty($field)) { + $field = Str::lower($filterPart); + } + } + + $expression = new Expression(Str::of($filterValues)->explode(Criteria::VALUE_SEPARATOR)); + + return new static( + new Predicate($field, null, $expression), + $operator, + $not, + $scope + ); + } + + /** + * Apply criteria to builder. + * + * @param Builder $builder + * @param Filter $filter + * @param ReadQuery $query + * @return Builder + */ + public function filter(Builder $builder, Filter $filter, ReadQuery $query): Builder + { + return $builder->whereIn( + $builder->qualifyColumn($filter->getColumn()), + $filter->getFilterValues($this->getFilterValues()), + $this->getLogicalOperator()->value, + $this->not() + ); + } +} diff --git a/app/Http/Api/Criteria/Include/Criteria.php b/app/Http/Api/Criteria/Include/Criteria.php new file mode 100644 index 000000000..e7927ea7f --- /dev/null +++ b/app/Http/Api/Criteria/Include/Criteria.php @@ -0,0 +1,32 @@ + $paths + */ + public function __construct(protected readonly Collection $paths) + { + } + + /** + * Get include paths. + * + * @return Collection + */ + public function getPaths(): Collection + { + return $this->paths; + } +} diff --git a/app/Http/Api/Criteria/Include/ResourceCriteria.php b/app/Http/Api/Criteria/Include/ResourceCriteria.php new file mode 100644 index 000000000..ab7a788be --- /dev/null +++ b/app/Http/Api/Criteria/Include/ResourceCriteria.php @@ -0,0 +1,34 @@ + $paths + */ + public function __construct(protected readonly string $type, Collection $paths) + { + parent::__construct($paths); + } + + /** + * Get the resource type. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } +} diff --git a/app/Http/Api/Criteria/Paging/Criteria.php b/app/Http/Api/Criteria/Paging/Criteria.php new file mode 100644 index 000000000..c1624e6b5 --- /dev/null +++ b/app/Http/Api/Criteria/Paging/Criteria.php @@ -0,0 +1,59 @@ +resultSize <= 0 || $this->resultSize > self::MAX_RESULTS) { + return self::DEFAULT_SIZE; + } + + return $this->resultSize; + } + + /** + * Get the intended pagination strategy. + * + * @return PaginationStrategy + */ + abstract public function getStrategy(): PaginationStrategy; + + /** + * Paginate the query. + * + * @param Builder $builder + * @return Collection|Paginator + */ + abstract public function paginate(Builder $builder): Collection|Paginator; +} diff --git a/app/Http/Api/Criteria/Paging/LimitCriteria.php b/app/Http/Api/Criteria/Paging/LimitCriteria.php new file mode 100644 index 000000000..14d4b38c0 --- /dev/null +++ b/app/Http/Api/Criteria/Paging/LimitCriteria.php @@ -0,0 +1,39 @@ +take($this->getResultSize())->get(); + } +} diff --git a/app/Http/Api/Criteria/Paging/OffsetCriteria.php b/app/Http/Api/Criteria/Paging/OffsetCriteria.php new file mode 100644 index 000000000..e5521a412 --- /dev/null +++ b/app/Http/Api/Criteria/Paging/OffsetCriteria.php @@ -0,0 +1,64 @@ +append('.') + ->append(self::NUMBER_PARAM) + ->__toString(); + + $pageNameLink = Str::of(PagingParser::param()) + ->append('[') + ->append(self::NUMBER_PARAM) + ->append(']') + ->__toString(); + + $paginator = $builder->simplePaginate($this->getResultSize(), ['*'], $pageNameQuery); + + if ($paginator instanceof AbstractPaginator) { + $paginator = $paginator->setPageName($pageNameLink); + + $paginator->appends(Request::except($pageNameQuery)); + } + + return $paginator; + } +} diff --git a/app/Http/Api/Criteria/Search/Criteria.php b/app/Http/Api/Criteria/Search/Criteria.php new file mode 100644 index 000000000..8790e4a2e --- /dev/null +++ b/app/Http/Api/Criteria/Search/Criteria.php @@ -0,0 +1,30 @@ +term; + } +} diff --git a/app/Http/Api/Criteria/Sort/Criteria.php b/app/Http/Api/Criteria/Sort/Criteria.php new file mode 100644 index 000000000..aa9e35418 --- /dev/null +++ b/app/Http/Api/Criteria/Sort/Criteria.php @@ -0,0 +1,76 @@ +scope; + } + + /** + * Get the criteria field. + * + * @return string + */ + public function getField(): string + { + return $this->field; + } + + /** + * Determine if this sort should be applied. + * + * @param Sort $sort + * @param Scope $scope + * @return bool + */ + public function shouldSort(Sort $sort, Scope $scope): bool + { + // Don't apply sort if key does not match + if ($this->getField() !== $sort->getKey()) { + return false; + } + + // Don't apply sort if scope does not match + if (! $this->getScope()->isWithinScope($scope)) { + return false; + } + + return true; + } + + /** + * Apply criteria to builder. + * + * @param Builder $builder + * @param Sort $sort + * @return Builder + */ + abstract public function sort(Builder $builder, Sort $sort): Builder; +} diff --git a/app/Http/Api/Criteria/Sort/FieldCriteria.php b/app/Http/Api/Criteria/Sort/FieldCriteria.php new file mode 100644 index 000000000..6c76af6dc --- /dev/null +++ b/app/Http/Api/Criteria/Sort/FieldCriteria.php @@ -0,0 +1,50 @@ +direction; + } + + /** + * Apply criteria to builder. + * + * @param Builder $builder + * @param Sort $sort + * @return Builder + */ + public function sort(Builder $builder, Sort $sort): Builder + { + return $builder->orderBy($builder->qualifyColumn($sort->getColumn()), $this->direction->value); + } +} diff --git a/app/Http/Api/Criteria/Sort/RandomCriteria.php b/app/Http/Api/Criteria/Sort/RandomCriteria.php new file mode 100644 index 000000000..3b38f08e8 --- /dev/null +++ b/app/Http/Api/Criteria/Sort/RandomCriteria.php @@ -0,0 +1,39 @@ +inRandomOrder(); + } +} diff --git a/app/Http/Api/Criteria/Sort/RelationCriteria.php b/app/Http/Api/Criteria/Sort/RelationCriteria.php new file mode 100644 index 000000000..d234c3766 --- /dev/null +++ b/app/Http/Api/Criteria/Sort/RelationCriteria.php @@ -0,0 +1,36 @@ +field, '.'); + $fieldName = Str::afterLast($this->field, '.'); + + $relation = $builder->getRelation($includePath); + + $orderBySubQuery = $relation->getRelationExistenceQuery($relation->getQuery(), $builder, [$fieldName]); + + return $builder->orderBy($orderBySubQuery->toBase(), $this->direction->value); + } +} diff --git a/app/Http/Api/Field/Admin/Announcement/AnnouncementContentField.php b/app/Http/Api/Field/Admin/Announcement/AnnouncementContentField.php new file mode 100644 index 000000000..a4b2e0a89 --- /dev/null +++ b/app/Http/Api/Field/Admin/Announcement/AnnouncementContentField.php @@ -0,0 +1,56 @@ +append(AllowedDateFormat::YMD)->__toString(), + ]; + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + Str::of('date_format:')->append(AllowedDateFormat::YMD)->__toString(), + ]; + } +} diff --git a/app/Http/Api/Field/Billing/Balance/BalanceFrequencyField.php b/app/Http/Api/Field/Billing/Balance/BalanceFrequencyField.php new file mode 100644 index 000000000..af7f45637 --- /dev/null +++ b/app/Http/Api/Field/Billing/Balance/BalanceFrequencyField.php @@ -0,0 +1,56 @@ +append(AllowedDateFormat::YMD)->__toString(), + ]; + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + Str::of('date_format:')->append(AllowedDateFormat::YMD)->__toString(), + ]; + } +} diff --git a/app/Http/Api/Field/Billing/Transaction/TransactionDescriptionField.php b/app/Http/Api/Field/Billing/Transaction/TransactionDescriptionField.php new file mode 100644 index 000000000..582808366 --- /dev/null +++ b/app/Http/Api/Field/Billing/Transaction/TransactionDescriptionField.php @@ -0,0 +1,56 @@ +getKey(), $this->getColumn()); + } + + /** + * Determine if the field should be included in the select clause of our query. + * + * @param Criteria|null $criteria + * @return bool + */ + public function shouldSelect(?Criteria $criteria): bool + { + return $criteria === null || $criteria->isAllowedField($this->getKey()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getColumn()); + } +} diff --git a/app/Http/Api/Field/Config/Flags/FlagsAllowDiscordNotificationsField.php b/app/Http/Api/Field/Config/Flags/FlagsAllowDiscordNotificationsField.php new file mode 100644 index 000000000..f7a58f6b6 --- /dev/null +++ b/app/Http/Api/Field/Config/Flags/FlagsAllowDiscordNotificationsField.php @@ -0,0 +1,22 @@ +getKey(), $this->getColumn()); + } + + /** + * Determine if the field should be included in the select clause of our query. + * + * @param Criteria|null $criteria + * @return bool + */ + public function shouldSelect(?Criteria $criteria): bool + { + return $criteria === null || $criteria->isAllowedField($this->getKey()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getColumn()); + } +} diff --git a/app/Http/Api/Field/Document/Page/PageBodyField.php b/app/Http/Api/Field/Document/Page/PageBodyField.php new file mode 100644 index 000000000..8c6084c5d --- /dev/null +++ b/app/Http/Api/Field/Document/Page/PageBodyField.php @@ -0,0 +1,70 @@ +isAllowedField($this->getKey()); + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + 'string', + 'max:16777215', + ]; + } +} diff --git a/app/Http/Api/Field/Document/Page/PageNameField.php b/app/Http/Api/Field/Document/Page/PageNameField.php new file mode 100644 index 000000000..aa76088ff --- /dev/null +++ b/app/Http/Api/Field/Document/Page/PageNameField.php @@ -0,0 +1,56 @@ +ignore($request->route('page'), Page::ATTRIBUTE_ID), + ]; + } +} diff --git a/app/Http/Api/Field/EnumField.php b/app/Http/Api/Field/EnumField.php new file mode 100644 index 000000000..45e3a319c --- /dev/null +++ b/app/Http/Api/Field/EnumField.php @@ -0,0 +1,76 @@ + $enumClass + * @param string|null $column + */ + public function __construct( + string $key, + protected readonly string $enumClass, + ?string $column = null + ) { + parent::__construct($key, $column); + } + + /** + * Get the enum class. + * + * @return class-string + */ + public function getEnumClass(): string + { + return $this->enumClass; + } + + /** + * Get the filter that can be applied to the field. + * + * @return Filter + */ + public function getFilter(): Filter + { + return new EnumFilter($this->getKey(), $this->enumClass, $this->getColumn()); + } + + /** + * Determine if the field should be included in the select clause of our query. + * + * @param Criteria|null $criteria + * @return bool + */ + public function shouldSelect(?Criteria $criteria): bool + { + return $criteria === null || $criteria->isAllowedField($this->getKey()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getColumn()); + } +} diff --git a/app/Http/Api/Field/Field.php b/app/Http/Api/Field/Field.php new file mode 100644 index 000000000..9a6ed99f0 --- /dev/null +++ b/app/Http/Api/Field/Field.php @@ -0,0 +1,43 @@ +key; + } + + /** + * Get the field column. + * + * @return string|null + */ + public function getColumn(): ?string + { + return $this->column ?? $this->key; + } +} diff --git a/app/Http/Api/Field/FloatField.php b/app/Http/Api/Field/FloatField.php new file mode 100644 index 000000000..cf200a3d5 --- /dev/null +++ b/app/Http/Api/Field/FloatField.php @@ -0,0 +1,50 @@ +getKey(), $this->getColumn()); + } + + /** + * Determine if the field should be included in the select clause of our query. + * + * @param Criteria|null $criteria + * @return bool + */ + public function shouldSelect(?Criteria $criteria): bool + { + return $criteria === null || $criteria->isAllowedField($this->getKey()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getColumn()); + } +} diff --git a/app/Http/Api/Field/IntField.php b/app/Http/Api/Field/IntField.php new file mode 100644 index 000000000..d77cbf7ad --- /dev/null +++ b/app/Http/Api/Field/IntField.php @@ -0,0 +1,50 @@ +getKey(), $this->getColumn()); + } + + /** + * Determine if the field should be included in the select clause of our query. + * + * @param Criteria|null $criteria + * @return bool + */ + public function shouldSelect(?Criteria $criteria): bool + { + return $criteria === null || $criteria->isAllowedField($this->getKey()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getColumn()); + } +} diff --git a/app/Http/Api/Field/StringField.php b/app/Http/Api/Field/StringField.php new file mode 100644 index 000000000..4acb56b64 --- /dev/null +++ b/app/Http/Api/Field/StringField.php @@ -0,0 +1,50 @@ +getKey(), $this->getColumn()); + } + + /** + * Determine if the field should be included in the select clause of our query. + * + * @param Criteria|null $criteria + * @return bool + */ + public function shouldSelect(?Criteria $criteria): bool + { + return $criteria === null || $criteria->isAllowedField($this->getKey()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getColumn()); + } +} diff --git a/app/Http/Api/Field/Wiki/Anime/AnimeAsField.php b/app/Http/Api/Field/Wiki/Anime/AnimeAsField.php new file mode 100644 index 000000000..178100d21 --- /dev/null +++ b/app/Http/Api/Field/Wiki/Anime/AnimeAsField.php @@ -0,0 +1,22 @@ +ignore($request->route('anime'), Anime::ATTRIBUTE_ID), + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Anime/AnimeSynopsisField.php b/app/Http/Api/Field/Wiki/Anime/AnimeSynopsisField.php new file mode 100644 index 000000000..f5d99ea17 --- /dev/null +++ b/app/Http/Api/Field/Wiki/Anime/AnimeSynopsisField.php @@ -0,0 +1,55 @@ +ignore($request->route('artist'), Artist::ATTRIBUTE_ID), + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceAsField.php b/app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceAsField.php new file mode 100644 index 000000000..4b5ce41f0 --- /dev/null +++ b/app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceAsField.php @@ -0,0 +1,22 @@ +resolveSite($request)), + ]; + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'bail', + 'sometimes', + 'required', + 'max:192', + 'url', + new ResourceLinkMatchesSiteRule($this->resolveSite($request)), + ]; + } + + /** + * Resolve site field from request. + * + * @param Request $request + * @return int|null + */ + protected function resolveSite(Request $request): ?int + { + if ($request->has(ExternalResource::ATTRIBUTE_SITE)) { + $site = $request->input(ExternalResource::ATTRIBUTE_SITE); + + return is_int($site) ? $site : null; + } + + $resource = $request->route('resource'); + if ($resource instanceof ExternalResource) { + return $resource->site->value; + } + + return null; + } +} diff --git a/app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceSiteField.php b/app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceSiteField.php new file mode 100644 index 000000000..a764021d7 --- /dev/null +++ b/app/Http/Api/Field/Wiki/ExternalResource/ExternalResourceSiteField.php @@ -0,0 +1,83 @@ +resolveLink($request)), + ]; + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'bail', + 'sometimes', + 'required', + new EnumValue(ResourceSite::class), + new ResourceSiteMatchesLinkRule($this->resolveLink($request)), + ]; + } + + /** + * Resolve link field from request. + * + * @param Request $request + * @return string + */ + protected function resolveLink(Request $request): string + { + if ($request->has(ExternalResource::ATTRIBUTE_LINK)) { + $link = $request->input(ExternalResource::ATTRIBUTE_LINK); + + return is_string($link) ? $link : ''; + } + + $resource = $request->route('resource'); + if ($resource instanceof ExternalResource) { + return $resource->link; + } + + return ''; + } +} diff --git a/app/Http/Api/Field/Wiki/Image/ImageFacetField.php b/app/Http/Api/Field/Wiki/Image/ImageFacetField.php new file mode 100644 index 000000000..66f200463 --- /dev/null +++ b/app/Http/Api/Field/Wiki/Image/ImageFacetField.php @@ -0,0 +1,57 @@ +isAllowedField(ImageResource::ATTRIBUTE_LINK); + } +} diff --git a/app/Http/Api/Field/Wiki/Search/SearchAnimeField.php b/app/Http/Api/Field/Wiki/Search/SearchAnimeField.php new file mode 100644 index 000000000..f5dae7aae --- /dev/null +++ b/app/Http/Api/Field/Wiki/Search/SearchAnimeField.php @@ -0,0 +1,22 @@ +ignore($request->route('series'), Series::ATTRIBUTE_ID), + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Song/SongAsField.php b/app/Http/Api/Field/Wiki/Song/SongAsField.php new file mode 100644 index 000000000..4ba5db751 --- /dev/null +++ b/app/Http/Api/Field/Wiki/Song/SongAsField.php @@ -0,0 +1,22 @@ +ignore($request->route('studio'), Studio::ATTRIBUTE_ID), + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Video/VideoBasenameField.php b/app/Http/Api/Field/Wiki/Video/VideoBasenameField.php new file mode 100644 index 000000000..e633d17c7 --- /dev/null +++ b/app/Http/Api/Field/Wiki/Video/VideoBasenameField.php @@ -0,0 +1,53 @@ +isAllowedField(VideoResource::ATTRIBUTE_LINK); + } +} diff --git a/app/Http/Api/Field/Wiki/Video/VideoFilenameField.php b/app/Http/Api/Field/Wiki/Video/VideoFilenameField.php new file mode 100644 index 000000000..01b6f2ffa --- /dev/null +++ b/app/Http/Api/Field/Wiki/Video/VideoFilenameField.php @@ -0,0 +1,39 @@ +isAllowedField(Video::ATTRIBUTE_TAGS); + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + 'boolean', + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Video/VideoMimeTypeField.php b/app/Http/Api/Field/Wiki/Video/VideoMimeTypeField.php new file mode 100644 index 000000000..4f9504f04 --- /dev/null +++ b/app/Http/Api/Field/Wiki/Video/VideoMimeTypeField.php @@ -0,0 +1,39 @@ +isAllowedField(Video::ATTRIBUTE_TAGS); + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + 'boolean', + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Video/VideoOverlapField.php b/app/Http/Api/Field/Wiki/Video/VideoOverlapField.php new file mode 100644 index 000000000..3bf46a6a6 --- /dev/null +++ b/app/Http/Api/Field/Wiki/Video/VideoOverlapField.php @@ -0,0 +1,57 @@ +isAllowedField(Video::ATTRIBUTE_TAGS); + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + 'integer', + 'min:360', + 'max:1080', + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Video/VideoSizeField.php b/app/Http/Api/Field/Wiki/Video/VideoSizeField.php new file mode 100644 index 000000000..e7e79382c --- /dev/null +++ b/app/Http/Api/Field/Wiki/Video/VideoSizeField.php @@ -0,0 +1,39 @@ +isAllowedField(Video::ATTRIBUTE_TAGS); + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + new EnumValue(VideoSource::class), + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Video/VideoSubbedField.php b/app/Http/Api/Field/Wiki/Video/VideoSubbedField.php new file mode 100644 index 000000000..c9c380b1e --- /dev/null +++ b/app/Http/Api/Field/Wiki/Video/VideoSubbedField.php @@ -0,0 +1,68 @@ +isAllowedField(Video::ATTRIBUTE_TAGS); + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + 'boolean', + ]; + } +} diff --git a/app/Http/Api/Field/Wiki/Video/VideoTagsField.php b/app/Http/Api/Field/Wiki/Video/VideoTagsField.php new file mode 100644 index 000000000..49526033c --- /dev/null +++ b/app/Http/Api/Field/Wiki/Video/VideoTagsField.php @@ -0,0 +1,22 @@ + filter_var($filterValue, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE), + $filterValues + ); + } + + /** + * Get only filter values that are valid boolean options. + * Accepted for true: "1", "true", "on" and "yes". + * Accepted for false: "0", "false", "off" and "no. + * + * @param array $filterValues + * @return array + */ + protected function getValidFilterValues(array $filterValues): array + { + return array_values( + array_filter( + $filterValues, + fn (string $filterValue) => filter_var($filterValue, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null + ) + ); + } + + /** + * Determine if both true and false have been specified. + * + * @param array $filterValues + * @return bool + */ + public function isAllFilterValues(array $filterValues): bool + { + return in_array(true, $filterValues) && in_array(false, $filterValues); + } + + /** + * Get the validation rules for the filter. + * + * @return array + */ + public function getRules(): array + { + return [ + new IsValidBoolean(), + ]; + } + + /** + * Get the allowed comparison operators for the filter. + * + * @return ComparisonOperator[] + */ + public function getAllowedComparisonOperators(): array + { + return [ + ComparisonOperator::EQ(), + ComparisonOperator::NE(), + ]; + } +} diff --git a/app/Http/Api/Filter/DateFilter.php b/app/Http/Api/Filter/DateFilter.php new file mode 100644 index 000000000..f83ac4032 --- /dev/null +++ b/app/Http/Api/Filter/DateFilter.php @@ -0,0 +1,106 @@ +format($allowedDateFormat) === $filterValue) { + return $date->format(AllowedDateFormat::YMDHISU); + } + } + + return null; + }, + $filterValues + ); + } + + /** + * Get only filter values that are valid dates. + * + * @param array $filterValues + * @return array + */ + protected function getValidFilterValues(array $filterValues): array + { + return array_values( + array_filter( + $filterValues, + function (string $filterValue) { + foreach (AllowedDateFormat::getValues() as $allowedDateFormat) { + $date = DateTime::createFromFormat('!'.$allowedDateFormat, $filterValue); + if ($date && $date->format($allowedDateFormat) === $filterValue) { + return true; + } + } + + return false; + } + ) + ); + } + + /** + * Determine if all valid filter values have been specified. + * By default, this is false as we assume an unrestricted amount of valid values. + * + * @param array $filterValues + * @return bool + */ + public function isAllFilterValues(array $filterValues): bool + { + return false; + } + + /** + * Get the validation rules for the filter. + * + * @return array + */ + public function getRules(): array + { + $dateFormats = implode(',', AllowedDateFormat::getValues()); + + return [ + "date_format:$dateFormats", + ]; + } + + /** + * Get the allowed comparison operators for the filter. + * + * @return ComparisonOperator[] + */ + public function getAllowedComparisonOperators(): array + { + return [ + ComparisonOperator::EQ(), + ComparisonOperator::NE(), + ComparisonOperator::LT(), + ComparisonOperator::GT(), + ComparisonOperator::LTE(), + ComparisonOperator::GTE(), + ]; + } +} diff --git a/app/Http/Api/Filter/EnumFilter.php b/app/Http/Api/Filter/EnumFilter.php new file mode 100644 index 000000000..9b2639e7f --- /dev/null +++ b/app/Http/Api/Filter/EnumFilter.php @@ -0,0 +1,93 @@ + $enumClass + */ + public function __construct(string $key, protected readonly string $enumClass, ?string $column = null) + { + parent::__construct($key, $column); + } + + /** + * Convert filter values to enum values from key. + * + * @param array $filterValues + * @return array + */ + protected function convertFilterValues(array $filterValues): array + { + return array_map( + fn (string $filterValue) => $this->enumClass::fromDescription($filterValue)?->value, + $filterValues + ); + } + + /** + * Get only filter values that are valid enum options. + * + * @param array $filterValues + * @return array + */ + protected function getValidFilterValues(array $filterValues): array + { + return array_values( + array_filter( + $filterValues, + fn (string $filterValue) => $this->enumClass::fromDescription($filterValue) !== null + ) + ); + } + + /** + * Determine if all enum options have been specified. + * + * @param array $filterValues + * @return bool + */ + public function isAllFilterValues(array $filterValues): bool + { + return count($filterValues) === count($this->enumClass::getInstances()); + } + + /** + * Get the validation rules for the filter. + * + * @return array + */ + public function getRules(): array + { + return [ + new EnumDescriptionRule($this->enumClass), + ]; + } + + /** + * Get the allowed comparison operators for the filter. + * + * @return ComparisonOperator[] + */ + public function getAllowedComparisonOperators(): array + { + return [ + ComparisonOperator::EQ(), + ComparisonOperator::NE(), + ]; + } +} diff --git a/app/Http/Api/Filter/Filter.php b/app/Http/Api/Filter/Filter.php new file mode 100644 index 000000000..d24dffbec --- /dev/null +++ b/app/Http/Api/Filter/Filter.php @@ -0,0 +1,139 @@ +key; + } + + /** + * Get filter column. + * + * @return string + */ + public function getColumn(): string + { + return $this->column ?? $this->key; + } + + /** + * Get sanitized filter values. + * + * @param array $attemptedFilterValues + * @return array + */ + public function getFilterValues(array $attemptedFilterValues): array + { + return $this->getUniqueFilterValues( + $this->convertFilterValues( + $this->getValidFilterValues( + $attemptedFilterValues + ) + ) + ); + } + + /** + * Get unique filter values. + * + * @param array $filterValues + * @return array + */ + protected function getUniqueFilterValues(array $filterValues): array + { + return array_values(array_unique($filterValues)); + } + + /** + * Convert filter values if needed. By default, no conversion is needed. + * + * @param array $filterValues + * @return array + */ + abstract protected function convertFilterValues(array $filterValues): array; + + /** + * Get only filter values that are valid. By default, all values are valid. + * + * @param array $filterValues + * @return array + */ + abstract protected function getValidFilterValues(array $filterValues): array; + + /** + * Determine if all valid filter values have been specified. + * By default, this is false as we assume an unrestricted amount of valid values. + * + * @param array $filterValues + * @return bool + */ + abstract public function isAllFilterValues(array $filterValues): bool; + + /** + * Get the validation rules for the filter. + * + * @return array + */ + abstract public function getRules(): array; + + /** + * Get the allowed comparison operators for the filter. + * + * @return ComparisonOperator[] + */ + abstract public function getAllowedComparisonOperators(): array; + + /** + * Format filter string with conditions. + * + * @param LogicalOperator|null $logicalOperator + * @param ComparisonOperator|null $comparisonOperator + * @return string + */ + public function format( + ?LogicalOperator $logicalOperator = null, + ?ComparisonOperator $comparisonOperator = null + ): string { + $formattedFilter = Str::of($this->getKey()); + + if ($comparisonOperator !== null) { + $formattedFilter = $formattedFilter->append(Criteria::PARAM_SEPARATOR) + ->append($comparisonOperator->key); + } + + if ($logicalOperator !== null) { + $formattedFilter = $formattedFilter->append(Criteria::PARAM_SEPARATOR) + ->append($logicalOperator->key); + } + + return $formattedFilter->lower()->__toString(); + } +} diff --git a/app/Http/Api/Filter/FloatFilter.php b/app/Http/Api/Filter/FloatFilter.php new file mode 100644 index 000000000..3ac8922a0 --- /dev/null +++ b/app/Http/Api/Filter/FloatFilter.php @@ -0,0 +1,84 @@ + filter_var($filterValue, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE), + $filterValues + ); + } + + /** + * Get only filter values that are floats. + * + * @param array $filterValues + * @return array + */ + protected function getValidFilterValues(array $filterValues): array + { + return array_values( + array_filter( + $filterValues, + fn (string $filterValue) => filter_var($filterValue, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE) !== null + ) + ); + } + + /** + * Determine if all valid filter values have been specified. + * By default, this is false as we assume an unrestricted amount of valid values. + * + * @param array $filterValues + * @return bool + */ + public function isAllFilterValues(array $filterValues): bool + { + return false; + } + + /** + * Get the validation rules for the filter. + * + * @return array + */ + public function getRules(): array + { + return [ + 'numeric', + ]; + } + + /** + * Get the allowed comparison operators for the filter. + * + * @return ComparisonOperator[] + */ + public function getAllowedComparisonOperators(): array + { + return [ + ComparisonOperator::EQ(), + ComparisonOperator::NE(), + ComparisonOperator::LT(), + ComparisonOperator::GT(), + ComparisonOperator::LTE(), + ComparisonOperator::GTE(), + ]; + } +} diff --git a/app/Http/Api/Filter/HasFilter.php b/app/Http/Api/Filter/HasFilter.php new file mode 100644 index 000000000..bf739b247 --- /dev/null +++ b/app/Http/Api/Filter/HasFilter.php @@ -0,0 +1,94 @@ +allowedIncludePaths, fn (AllowedInclude $allowedInclude) => $allowedInclude->path()); + + return array_intersect($filterValues, $paths); + } + + /** + * Determine if all valid filter values have been specified. + * By default, this is false as we assume an unrestricted amount of valid values. + * + * @param array $filterValues + * @return bool + */ + public function isAllFilterValues(array $filterValues): bool + { + return false; + } + + /** + * Get the validation rules for the filter. + * + * @return array + */ + public function getRules(): array + { + $paths = Arr::map($this->allowedIncludePaths, fn (AllowedInclude $allowedInclude) => $allowedInclude->path()); + + return [ + Rule::in($paths), + ]; + } + + /** + * Get the allowed comparison operators for the filter. + * + * @return ComparisonOperator[] + */ + public function getAllowedComparisonOperators(): array + { + return [ + ComparisonOperator::EQ(), + ComparisonOperator::NE(), + ComparisonOperator::LT(), + ComparisonOperator::GT(), + ComparisonOperator::LTE(), + ComparisonOperator::GTE(), + ]; + } +} diff --git a/app/Http/Api/Filter/IntFilter.php b/app/Http/Api/Filter/IntFilter.php new file mode 100644 index 000000000..c9b971025 --- /dev/null +++ b/app/Http/Api/Filter/IntFilter.php @@ -0,0 +1,84 @@ + filter_var($filterValue, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE), + $filterValues + ); + } + + /** + * Get only filter values that are integers. + * + * @param array $filterValues + * @return array + */ + protected function getValidFilterValues(array $filterValues): array + { + return array_values( + array_filter( + $filterValues, + fn (string $filterValue) => filter_var($filterValue, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE) !== null + ) + ); + } + + /** + * Determine if all valid filter values have been specified. + * By default, this is false as we assume an unrestricted amount of valid values. + * + * @param array $filterValues + * @return bool + */ + public function isAllFilterValues(array $filterValues): bool + { + return false; + } + + /** + * Get the validation rules for the filter. + * + * @return array + */ + public function getRules(): array + { + return [ + 'integer', + ]; + } + + /** + * Get the allowed comparison operators for the filter. + * + * @return ComparisonOperator[] + */ + public function getAllowedComparisonOperators(): array + { + return [ + ComparisonOperator::EQ(), + ComparisonOperator::NE(), + ComparisonOperator::LT(), + ComparisonOperator::GT(), + ComparisonOperator::LTE(), + ComparisonOperator::GTE(), + ]; + } +} diff --git a/app/Http/Api/Filter/StringFilter.php b/app/Http/Api/Filter/StringFilter.php new file mode 100644 index 000000000..59d5e74d4 --- /dev/null +++ b/app/Http/Api/Filter/StringFilter.php @@ -0,0 +1,74 @@ +getKey(); + } +} diff --git a/app/Http/Api/Include/AllowedInclude.php b/app/Http/Api/Include/AllowedInclude.php new file mode 100644 index 000000000..d294cbe32 --- /dev/null +++ b/app/Http/Api/Include/AllowedInclude.php @@ -0,0 +1,43 @@ +schema; + } + + /** + * Get the path. + * + * @return string + */ + public function path(): string + { + return $this->path; + } +} diff --git a/app/Http/Api/Parser/FieldParser.php b/app/Http/Api/Parser/FieldParser.php new file mode 100644 index 000000000..222b3da01 --- /dev/null +++ b/app/Http/Api/Parser/FieldParser.php @@ -0,0 +1,63 @@ + $fieldList) { + if ($fieldList !== null && ! Arr::accessible($fieldList)) { + $criteria[] = static::parseCriteria($type, $fieldList); + } + } + } + } + + return $criteria; + } + + /** + * Parse criteria instance from query string. + * + * @param string $type + * @param string $fieldList + * @return Criteria + */ + protected static function parseCriteria(string $type, string $fieldList): Criteria + { + $fields = Str::of($fieldList)->lower()->explode(','); + + return new Criteria(Str::lower($type), $fields); + } +} diff --git a/app/Http/Api/Parser/FilterParser.php b/app/Http/Api/Parser/FilterParser.php new file mode 100644 index 000000000..93e0fcf71 --- /dev/null +++ b/app/Http/Api/Parser/FilterParser.php @@ -0,0 +1,91 @@ + $filterList) { + if ($filterList !== null && ! Arr::accessible($filterList)) { + $criteria[] = static::parseCriteria(new GlobalScope(), $scopeOrType, $filterList); + } + if (Arr::accessible($filterList) && Arr::isAssoc($filterList)) { + $scope = ScopeParser::parse($scopeOrType); + foreach ($filterList as $filter => $filterValues) { + if ($filterValues !== null && ! Arr::accessible($filterValues)) { + $criteria[] = static::parseCriteria($scope, $filter, $filterValues); + } + } + } + } + } + } + + return $criteria; + } + + /** + * Parse criteria instance from query string. + * + * @param Scope $scope + * @param string $filterParam + * @param mixed $filterValues + * @return Criteria + */ + protected static function parseCriteria(Scope $scope, string $filterParam, mixed $filterValues): Criteria + { + $param = Str::lower($filterParam); + + if (Str::of($param)->explode(Criteria::PARAM_SEPARATOR)->contains(TrashedCriteria::PARAM_VALUE)) { + return TrashedCriteria::make($scope, $param, $filterValues); + } + + if (Str::of($param)->explode(Criteria::PARAM_SEPARATOR)->contains(HasCriteria::PARAM_VALUE)) { + return HasCriteria::make($scope, $filterParam, $filterValues); + } + + if (Str::contains($filterValues, Criteria::VALUE_SEPARATOR)) { + return WhereInCriteria::make($scope, $param, $filterValues); + } + + return WhereCriteria::make($scope, $param, $filterValues); + } +} diff --git a/app/Http/Api/Parser/IncludeParser.php b/app/Http/Api/Parser/IncludeParser.php new file mode 100644 index 000000000..e50a16334 --- /dev/null +++ b/app/Http/Api/Parser/IncludeParser.php @@ -0,0 +1,80 @@ + $includeList) { + if ($includeList !== null && ! Arr::accessible($includeList)) { + $criteria[] = static::parseResourceCriteria($type, $includeList); + } + } + } + } + + return $criteria; + } + + /** + * Parse criteria instance from query string. + * + * @param string $includeParam + * @return Criteria + */ + protected static function parseCriteria(string $includeParam): Criteria + { + $paths = Str::of($includeParam)->lower()->explode(','); + + return new Criteria($paths); + } + + /** + * Parse resource criteria instance from query string. + * + * @param string $type + * @param string $includeParam + * @return ResourceCriteria + */ + protected static function parseResourceCriteria(string $type, string $includeParam): ResourceCriteria + { + $paths = Str::of($includeParam)->lower()->explode(','); + + return new ResourceCriteria(Str::lower($type), $paths); + } +} diff --git a/app/Http/Api/Parser/PagingParser.php b/app/Http/Api/Parser/PagingParser.php new file mode 100644 index 000000000..ccd05d58f --- /dev/null +++ b/app/Http/Api/Parser/PagingParser.php @@ -0,0 +1,81 @@ +explode(','); + foreach ($sortValues as $sortValue) { + $criteria[] = static::parseCriteria(new GlobalScope(), $sortValue); + } + } + if (Arr::accessible($sortParam) && Arr::isAssoc($sortParam)) { + foreach ($sortParam as $type => $sortList) { + if ($sortList !== null && ! Arr::accessible($sortList)) { + $scope = ScopeParser::parse($type); + $sortValues = Str::of($sortList)->explode(','); + foreach ($sortValues as $sortValue) { + $criteria[] = static::parseCriteria($scope, $sortValue); + } + } + } + } + } + + return $criteria; + } + + /** + * Parse criteria instance from query string. + * + * @param Scope $scope + * @param string $sortValue + * @return Criteria + */ + protected static function parseCriteria(Scope $scope, string $sortValue): Criteria + { + $field = Str::lower($sortValue); + + if ($field === RandomCriteria::PARAM_VALUE) { + return new RandomCriteria($scope); + } + + $direction = Direction::ASCENDING(); + + if (Str::startsWith($field, '-')) { + $direction = Direction::DESCENDING(); + $field = Str::replaceFirst('-', '', $field); + } + + if (Str::contains($field, '.')) { + return new RelationCriteria($scope, $field, $direction); + } + + return new FieldCriteria($scope, $field, $direction); + } +} diff --git a/app/Http/Api/Query/Admin/AnnouncementReadQuery.php b/app/Http/Api/Query/Admin/AnnouncementReadQuery.php new file mode 100644 index 000000000..2eaaa92aa --- /dev/null +++ b/app/Http/Api/Query/Admin/AnnouncementReadQuery.php @@ -0,0 +1,63 @@ +constrainEagerLoads(); + + return $this->resource($model->load($constrainedEagerLoads)); + } + + /** + * Prepare models for resource collection. + * + * @return BaseCollection + */ + public function index(): BaseCollection + { + $schema = $this->schema(); + + // initialize builder, returning early if not resolved + $builder = $this->builder(); + + // eager load relations with constraints + $builder->with($this->constrainEagerLoads()); + + // select fields + $fieldCriteria = $this->getFieldCriteria($schema->type()); + $selectedFields = collect($schema->fields()) + ->filter(fn (Field $field) => $field instanceof SelectableField && $field->shouldSelect($fieldCriteria)) + ->map(fn (Field $field) => $field->getColumn()); + $builder->select($builder->qualifyColumns($selectedFields->all())); + + // apply filters + $scope = ScopeParser::parse($schema->type()); + foreach ($this->getFilterCriteria() as $criteria) { + foreach ($schema->filters() as $filter) { + if ($criteria->shouldFilter($filter, $scope)) { + $criteria->filter($builder, $filter, $this); + } + } + } + + // special case: only apply HasFilter to top-level models + if (! empty($schema->allowedIncludes())) { + $hasFilter = new HasFilter($schema->allowedIncludes()); + foreach ($this->getFilterCriteria() as $criteria) { + if ($criteria->shouldFilter($hasFilter, $scope)) { + $criteria->filter($builder, $hasFilter, $this); + } + } + } + + // apply sorts + foreach ($this->getSortCriteria() as $sortCriterion) { + foreach ($schema->sorts() as $sort) { + if ($sortCriterion->shouldSort($sort, $scope)) { + $sortCriterion->sort($builder, $sort); + } + } + } + + // paginate + $paginationCriteria = $this->getPagingCriteria(PaginationStrategy::OFFSET()); + $collection = $paginationCriteria !== null + ? $paginationCriteria->paginate($builder) + : $builder->get(); + + // return paginated resource collection + return $this->collection($collection); + } + + /** + * Constrain eager loads by binding callbacks that filter on the relations. + * + * @return array + */ + public function constrainEagerLoads(): array + { + $constrainedEagerLoads = []; + + $schema = $this->schema(); + + $includeCriteria = $this->getIncludeCriteria($schema->type()); + if ($includeCriteria === null) { + return $constrainedEagerLoads; + } + + foreach ($includeCriteria->getPaths() as $allowedIncludePath) { + $relationSchema = $schema->relation($allowedIncludePath); + if ($relationSchema === null) { + throw new RuntimeException("Unknown relation '$allowedIncludePath' for type '{$schema->type()}'."); + } + + $scope = ScopeParser::parse($allowedIncludePath); + $constrainedEagerLoads[$allowedIncludePath] = function (Relation $relation) use ($scope, $relationSchema) { + $relationBuilder = $relation->getQuery(); + + // select fields + $fieldCriteria = $this->getFieldCriteria($relationSchema->type()); + $selectedFields = collect($relationSchema->fields()) + ->filter(fn (Field $field) => $field instanceof SelectableField && $field->shouldSelect($fieldCriteria)) + ->map(fn (Field $field) => $field->getColumn()); + $relationBuilder->select($relationBuilder->qualifyColumns($selectedFields->all())); + + // apply filters + foreach ($this->getFilterCriteria() as $criteria) { + foreach ($relationSchema->filters() as $filter) { + if ($criteria->shouldFilter($filter, $scope)) { + $criteria->filter($relationBuilder, $filter, $this); + } + } + } + + // apply sorts + foreach ($this->getSortCriteria() as $sortCriterion) { + foreach ($relationSchema->sorts() as $sort) { + if ($sortCriterion->shouldSort($sort, $scope)) { + $sortCriterion->sort($relationBuilder, $sort); + } + } + } + }; + } + + return $constrainedEagerLoads; + } + + /** + * Prepare models for sear. + * + * @param PaginationStrategy $paginationStrategy + * @return BaseCollection + * + * @throws RuntimeException + */ + public function search(PaginationStrategy $paginationStrategy): BaseCollection + { + $search = static::getSearch(); + + if ($search !== null && $search->shouldSearch($this)) { + return $search->search($this, $paginationStrategy); + } + + // Let developer know why search can't be performed + $driver = Config::get('scout.driver'); + $term = $this->getSearchCriteria()?->getTerm(); + throw new RuntimeException("Can't search for term '$term' with driver '$driver'. Please configure supported driver."); + } + + /** + * Get the search instance. + * + * @return Search|null + */ + protected static function getSearch(): ?Search + { + return match (Config::get('scout.driver')) { + 'elastic' => App::make(Elasticsearch::class), + default => null, + }; + } + + /** + * Get the resource schema. + * + * @return EloquentSchema + */ + abstract public function schema(): EloquentSchema; + + /** + * Get the query builder of the resource. + * + * @return Builder + */ + abstract public function builder(): Builder; + + /** + * Get the json resource. + * + * @param mixed $resource + * @return BaseResource + */ + abstract public function resource(mixed $resource): BaseResource; + + /** + * Get the resource collection. + * + * @param mixed $resource + * @return BaseCollection + */ + abstract public function collection(mixed $resource): BaseCollection; +} diff --git a/app/Http/Api/Query/Base/EloquentWriteQuery.php b/app/Http/Api/Query/Base/EloquentWriteQuery.php new file mode 100644 index 000000000..08498af4e --- /dev/null +++ b/app/Http/Api/Query/Base/EloquentWriteQuery.php @@ -0,0 +1,129 @@ +builder()->create($this->parameters); + + // Scout will load relations to refresh related search indices. + $model->unsetRelations(); + + // Columns with default values may be unset if not provided in the query string. + $model->refresh(); + + return $this->resource($model); + } + + /** + * Update Model. + * + * @param Model $model + * @return BaseResource + */ + public function update(Model $model): BaseResource + { + $model->update($this->parameters); + + // Scout will load relations to refresh related search indices. + $model->unsetRelations(); + + return $this->resource($model); + } + + /** + * Destroy Model. + * + * @param Model $model + * @return BaseResource + */ + public function destroy(Model $model): BaseResource + { + $model->delete(); + + // Scout will load relations to refresh related search indices. + $model->unsetRelations(); + + return $this->resource($model); + } + + /** + * Restore model. + * + * @param BaseModel $model + * @return BaseResource + */ + public function restore(BaseModel $model): BaseResource + { + $model->restore(); + + // Scout will load relations to refresh related search indices. + $model->unsetRelations(); + + return $this->resource($model); + } + + /** + * Force Delete Model. + * + * @param BaseModel $model + * @return JsonResponse + */ + public function forceDelete(BaseModel $model): JsonResponse + { + $message = Str::of(class_basename($model)) + ->append(' \'') + ->append($model->getName()) + ->append('\' was deleted.') + ->__toString(); + + $model->forceDelete(); + + return new JsonResponse([ + 'message' => $message, + ]); + } + + /** + * Get the resource schema. + * + * @return EloquentSchema + */ + abstract public function schema(): EloquentSchema; + + /** + * Get the query builder of the resource. + * + * @return Builder + */ + abstract public function builder(): Builder; + + /** + * Get the json resource. + * + * @param mixed $resource + * @return BaseResource + */ + abstract public function resource(mixed $resource): BaseResource; +} diff --git a/app/Http/Api/Query/Billing/Balance/BalanceReadQuery.php b/app/Http/Api/Query/Billing/Balance/BalanceReadQuery.php new file mode 100644 index 000000000..b205c6589 --- /dev/null +++ b/app/Http/Api/Query/Billing/Balance/BalanceReadQuery.php @@ -0,0 +1,63 @@ +fieldCriteria = FieldParser::parse($parameters); + $this->includeCriteria = IncludeParser::parse($parameters); + $this->sortCriteria = SortParser::parse($parameters); + $this->filterCriteria = FilterParser::parse($parameters); + $this->searchCriteria = SearchParser::parse($parameters); + $this->pagingCriteria = PagingParser::parse($parameters); + } + + /** + * Get the field criteria. + * + * @param string $type + * @return FieldCriteria|null + */ + public function getFieldCriteria(string $type): ?FieldCriteria + { + return Arr::first($this->fieldCriteria, fn (FieldCriteria $criteria) => $criteria->getType() === $type); + } + + /** + * Get include criteria. + * + * @param string $type + * @return IncludeCriteria|null + */ + public function getIncludeCriteria(string $type): ?IncludeCriteria + { + return $this->getResourceIncludeCriteria($type) ?? + Arr::first( + $this->includeCriteria, + fn (IncludeCriteria $criteria) => get_class($criteria) === IncludeCriteria::class + ); + } + + /** + * Get the resource include criteria. + * + * @param string $type + * @return IncludeCriteria|null + */ + public function getResourceIncludeCriteria(string $type): ?IncludeCriteria + { + return Arr::first( + $this->includeCriteria, + fn (IncludeCriteria $criteria) => $criteria instanceof ResourceCriteria && $criteria->getType() === $type + ); + } + + /** + * Get sort criteria. + * + * @return SortCriteria[] + */ + public function getSortCriteria(): array + { + return $this->sortCriteria; + } + + /** + * Get filter criteria for the field. + * + * @return FilterCriteria[] + */ + public function getFilterCriteria(): array + { + return $this->filterCriteria; + } + + /** + * Does the query have search criteria? + * + * @return bool + */ + public function hasSearchCriteria(): bool + { + return ! empty($this->searchCriteria); + } + + /** + * Get search criteria. + * Note: At the time of writing, the API shall only support a single search term. + * + * @return SearchCriteria|null + */ + public function getSearchCriteria(): ?SearchCriteria + { + return Arr::first($this->searchCriteria); + } + + /** + * Get paging criteria that matches pagination strategy. + * + * @param PaginationStrategy $strategy + * @return PagingCriteria|null + */ + public function getPagingCriteria(PaginationStrategy $strategy): ?PagingCriteria + { + return Arr::first( + $this->pagingCriteria, + fn (PagingCriteria $criteria) => $strategy->is($criteria->getStrategy()) + ); + } +} diff --git a/app/Http/Api/Query/Wiki/Anime/AnimeReadQuery.php b/app/Http/Api/Query/Wiki/Anime/AnimeReadQuery.php new file mode 100644 index 000000000..9be644c66 --- /dev/null +++ b/app/Http/Api/Query/Wiki/Anime/AnimeReadQuery.php @@ -0,0 +1,63 @@ +parameters, ImageFileField::ATTRIBUTE_FILE); + + $file = Arr::get($this->parameters, ImageFileField::ATTRIBUTE_FILE); + if ($file instanceof UploadedFile) { + Arr::set($imageParameters, Image::ATTRIBUTE_PATH, $file->store('', 'images')); + } + + $model = $this->builder()->create($imageParameters); + + // Scout will load relations to refresh related search indices. + $model->unsetRelations(); + + // Columns with default values may be unset if not provided in the query string. + $model->refresh(); + + return $this->resource($model); + } + + /** + * Get the resource schema. + * + * @return EloquentSchema + */ + public function schema(): EloquentSchema + { + return new ImageSchema(); + } + + /** + * Get the query builder of the resource. + * + * @return Builder + */ + public function builder(): Builder + { + return Image::query(); + } + + /** + * Get the json resource. + * + * @param mixed $resource + * @return BaseResource + */ + public function resource(mixed $resource): BaseResource + { + return new ImageResource($resource, new ImageReadQuery()); + } +} diff --git a/app/Http/Api/Query/Wiki/SearchReadQuery.php b/app/Http/Api/Query/Wiki/SearchReadQuery.php new file mode 100644 index 000000000..a72567c28 --- /dev/null +++ b/app/Http/Api/Query/Wiki/SearchReadQuery.php @@ -0,0 +1,68 @@ +queries[] = new AnimeReadQuery($parameters); + $this->queries[] = new ThemeReadQuery($parameters); + $this->queries[] = new ArtistReadQuery($parameters); + $this->queries[] = new SeriesReadQuery($parameters); + $this->queries[] = new SongReadQuery($parameters); + $this->queries[] = new StudioReadQuery($parameters); + $this->queries[] = new VideoReadQuery($parameters); + } + + /** + * Get the resource schema. + * + * @return Schema + */ + public function schema(): Schema + { + return new SearchSchema(); + } + + /** + * Get the query by class. + * + * @param string $queryClass + * @return EloquentReadQuery|null + */ + public function getQuery(string $queryClass): ?EloquentReadQuery + { + return Arr::first($this->queries, fn (EloquentReadQuery $query) => is_a($query, $queryClass)); + } +} diff --git a/app/Http/Api/Query/Wiki/Series/SeriesReadQuery.php b/app/Http/Api/Query/Wiki/Series/SeriesReadQuery.php new file mode 100644 index 000000000..7a8ff6602 --- /dev/null +++ b/app/Http/Api/Query/Wiki/Series/SeriesReadQuery.php @@ -0,0 +1,63 @@ +fields()) + ->filter(fn (Field $field) => $field instanceof FilterableField) + ->map(fn (FilterableField $field) => $field->getFilter()) + ->push(new TrashedFilter()) + ->all(); + } + + /** + * Get the sorts of the resource. + * + * @return Sort[] + */ + public function sorts(): array + { + return collect($this->fields()) + ->filter(fn (Field $field) => $field instanceof SortableField) + ->map(fn (SortableField $field) => $field->getSort()) + ->push(new RandomSort()) + ->all(); + } + + /** + * Get the schema of the relation by path. + * + * @param string $path + * @return Schema|null + */ + public function relation(string $path): ?Schema + { + $relationInclude = Arr::first($this->allowedIncludes(), fn (AllowedInclude $include) => $include->path() === $path); + + return $relationInclude?->schema(); + } +} diff --git a/app/Http/Api/Schema/Wiki/Anime/SynonymSchema.php b/app/Http/Api/Schema/Wiki/Anime/SynonymSchema.php new file mode 100644 index 000000000..f6ecb9563 --- /dev/null +++ b/app/Http/Api/Schema/Wiki/Anime/SynonymSchema.php @@ -0,0 +1,70 @@ +relation; + } + + /** + * Get the type of the relation. + * + * @return string + */ + public function getType(): string + { + return Str::of($this->getRelation())->explode('.')->last(); + } + + /** + * Determine if the provided scope is within this scope. + * + * @param Scope $scope + * @return bool + */ + public function isWithinScope(Scope $scope): bool + { + return $scope instanceof RelationScope && $this->relation === $scope->getRelation(); + } +} diff --git a/app/Http/Api/Scope/Scope.php b/app/Http/Api/Scope/Scope.php new file mode 100644 index 000000000..6eb2a3922 --- /dev/null +++ b/app/Http/Api/Scope/Scope.php @@ -0,0 +1,19 @@ +explode('.') + ->map(fn (string $scopePart) => Str::singular($scopePart)) + ->join('.') + ); + } + + return new TypeScope(Str::singular($scope)); + } +} diff --git a/app/Http/Api/Scope/TypeScope.php b/app/Http/Api/Scope/TypeScope.php new file mode 100644 index 000000000..9dafe32e1 --- /dev/null +++ b/app/Http/Api/Scope/TypeScope.php @@ -0,0 +1,45 @@ +type; + } + + /** + * Determine if the provided scope is within this scope. + * + * @param Scope $scope + * @return bool + */ + public function isWithinScope(Scope $scope): bool + { + if ($scope instanceof TypeScope) { + return $this->type === $scope->getType(); + } + + return $scope instanceof RelationScope && $this->type === $scope->getType(); + } +} diff --git a/app/Http/Api/Sort/RandomSort.php b/app/Http/Api/Sort/RandomSort.php new file mode 100644 index 000000000..738af160b --- /dev/null +++ b/app/Http/Api/Sort/RandomSort.php @@ -0,0 +1,35 @@ +getKey(); + } +} diff --git a/app/Http/Api/Sort/Sort.php b/app/Http/Api/Sort/Sort.php new file mode 100644 index 000000000..ba2bb7211 --- /dev/null +++ b/app/Http/Api/Sort/Sort.php @@ -0,0 +1,57 @@ +key; + } + + /** + * Get sort column. + * + * @return string + */ + public function getColumn(): string + { + return $this->column ?? $this->key; + } + + /** + * Format the sort based on direction. + * + * @param Direction $direction + * @return string + */ + public function format(Direction $direction): string + { + return match ($direction->value) { + Direction::DESCENDING => "-{$this->getKey()}", + default => $this->getKey(), + }; + } +} diff --git a/app/Http/Controllers/Api/Admin/AnnouncementController.php b/app/Http/Controllers/Api/Admin/AnnouncementController.php new file mode 100644 index 000000000..a98879984 --- /dev/null +++ b/app/Http/Controllers/Api/Admin/AnnouncementController.php @@ -0,0 +1,124 @@ +getQuery()->index(); + + return $announcements->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param AnnouncementStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'announcement', name: 'announcement.store', middleware: 'auth:sanctum')] + public function store(AnnouncementStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param AnnouncementShowRequest $request + * @param Announcement $announcement + * @return JsonResponse + */ + #[Route(fullUri: 'announcement/{announcement}', name: 'announcement.show')] + public function show(AnnouncementShowRequest $request, Announcement $announcement): JsonResponse + { + $resource = $request->getQuery()->show($announcement); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param AnnouncementUpdateRequest $request + * @param Announcement $announcement + * @return JsonResponse + */ + #[Route(fullUri: 'announcement/{announcement}', name: 'announcement.update', middleware: 'auth:sanctum')] + public function update(AnnouncementUpdateRequest $request, Announcement $announcement): JsonResponse + { + $resource = $request->getQuery()->update($announcement); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param AnnouncementDestroyRequest $request + * @param Announcement $announcement + * @return JsonResponse + */ + #[Route(fullUri: 'announcement/{announcement}', name: 'announcement.destroy', middleware: 'auth:sanctum')] + public function destroy(AnnouncementDestroyRequest $request, Announcement $announcement): JsonResponse + { + $resource = $request->getQuery()->destroy($announcement); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param AnnouncementRestoreRequest $request + * @param Announcement $announcement + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/announcement/{announcement}', name: 'announcement.restore', middleware: 'auth:sanctum')] + public function restore(AnnouncementRestoreRequest $request, Announcement $announcement): JsonResponse + { + $resource = $request->getQuery()->restore($announcement); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param AnnouncementForceDeleteRequest $request + * @param Announcement $announcement + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/announcement/{announcement}', name: 'announcement.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(AnnouncementForceDeleteRequest $request, Announcement $announcement): JsonResponse + { + return $request->getQuery()->forceDelete($announcement); + } +} diff --git a/app/Http/Controllers/Api/Billing/BalanceController.php b/app/Http/Controllers/Api/Billing/BalanceController.php new file mode 100644 index 000000000..07f809705 --- /dev/null +++ b/app/Http/Controllers/Api/Billing/BalanceController.php @@ -0,0 +1,124 @@ +getQuery()->index(); + + return $balances->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param BalanceStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'balance', name: 'balance.store', middleware: 'auth:sanctum')] + public function store(BalanceStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param BalanceShowRequest $request + * @param Balance $balance + * @return JsonResponse + */ + #[Route(fullUri: 'balance/{balance}', name: 'balance.show')] + public function show(BalanceShowRequest $request, Balance $balance): JsonResponse + { + $resource = $request->getQuery()->show($balance); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param BalanceUpdateRequest $request + * @param Balance $balance + * @return JsonResponse + */ + #[Route(fullUri: 'balance/{balance}', name: 'balance.update', middleware: 'auth:sanctum')] + public function update(BalanceUpdateRequest $request, Balance $balance): JsonResponse + { + $resource = $request->getQuery()->update($balance); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param BalanceDestroyRequest $request + * @param Balance $balance + * @return JsonResponse + */ + #[Route(fullUri: 'balance/{balance}', name: 'balance.destroy', middleware: 'auth:sanctum')] + public function destroy(BalanceDestroyRequest $request, Balance $balance): JsonResponse + { + $resource = $request->getQuery()->destroy($balance); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param BalanceRestoreRequest $request + * @param Balance $balance + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/balance/{balance}', name: 'balance.restore', middleware: 'auth:sanctum')] + public function restore(BalanceRestoreRequest $request, Balance $balance): JsonResponse + { + $resource = $request->getQuery()->restore($balance); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param BalanceForceDeleteRequest $request + * @param Balance $balance + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/balance/{balance}', name: 'balance.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(BalanceForceDeleteRequest $request, Balance $balance): JsonResponse + { + return $request->getQuery()->forceDelete($balance); + } +} diff --git a/app/Http/Controllers/Api/Billing/TransactionController.php b/app/Http/Controllers/Api/Billing/TransactionController.php new file mode 100644 index 000000000..de3232461 --- /dev/null +++ b/app/Http/Controllers/Api/Billing/TransactionController.php @@ -0,0 +1,124 @@ +getQuery()->index(); + + return $transactions->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param TransactionStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'transaction', name: 'transaction.store', middleware: 'auth:sanctum')] + public function store(TransactionStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param TransactionShowRequest $request + * @param Transaction $transaction + * @return JsonResponse + */ + #[Route(fullUri: 'transaction/{transaction}', name: 'transaction.show')] + public function show(TransactionShowRequest $request, Transaction $transaction): JsonResponse + { + $resource = $request->getQuery()->show($transaction); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param TransactionUpdateRequest $request + * @param Transaction $transaction + * @return JsonResponse + */ + #[Route(fullUri: 'transaction/{transaction}', name: 'transaction.update', middleware: 'auth:sanctum')] + public function update(TransactionUpdateRequest $request, Transaction $transaction): JsonResponse + { + $resource = $request->getQuery()->update($transaction); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param TransactionDestroyRequest $request + * @param Transaction $transaction + * @return JsonResponse + */ + #[Route(fullUri: 'transaction/{transaction}', name: 'transaction.destroy', middleware: 'auth:sanctum')] + public function destroy(TransactionDestroyRequest $request, Transaction $transaction): JsonResponse + { + $resource = $request->getQuery()->destroy($transaction); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param TransactionRestoreRequest $request + * @param Transaction $transaction + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/transaction/{transaction}', name: 'transaction.restore', middleware: 'auth:sanctum')] + public function restore(TransactionRestoreRequest $request, Transaction $transaction): JsonResponse + { + $resource = $request->getQuery()->restore($transaction); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param TransactionForceDeleteRequest $request + * @param Transaction $transaction + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/transaction/{transaction}', name: 'transaction.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(TransactionForceDeleteRequest $request, Transaction $transaction): JsonResponse + { + return $request->getQuery()->forceDelete($transaction); + } +} diff --git a/app/Http/Controllers/Api/Config/FlagsController.php b/app/Http/Controllers/Api/Config/FlagsController.php new file mode 100644 index 000000000..4faa77c40 --- /dev/null +++ b/app/Http/Controllers/Api/Config/FlagsController.php @@ -0,0 +1,31 @@ +getQuery()); + + return $resource->toResponse($request); + } +} diff --git a/app/Http/Controllers/Api/Config/WikiController.php b/app/Http/Controllers/Api/Config/WikiController.php new file mode 100644 index 000000000..a0c2891be --- /dev/null +++ b/app/Http/Controllers/Api/Config/WikiController.php @@ -0,0 +1,31 @@ +getQuery()); + + return $resource->toResponse($request); + } +} diff --git a/app/Http/Controllers/Api/Document/PageController.php b/app/Http/Controllers/Api/Document/PageController.php new file mode 100644 index 000000000..f964320d3 --- /dev/null +++ b/app/Http/Controllers/Api/Document/PageController.php @@ -0,0 +1,126 @@ +getQuery()->index(); + + return $pages->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param PageStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'page', name: 'page.store', middleware: 'auth:sanctum')] + public function store(PageStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param PageShowRequest $request + * @param Page $page + * @return JsonResponse + */ + #[Route(fullUri: 'page/{page}', name: 'page.show')] + public function show(PageShowRequest $request, Page $page): JsonResponse + { + $resource = $request->getQuery()->show($page); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param PageUpdateRequest $request + * @param Page $page + * @return JsonResponse + */ + #[Route(fullUri: 'page/{page}', name: 'page.update', middleware: 'auth:sanctum')] + public function update(PageUpdateRequest $request, Page $page): JsonResponse + { + $resource = $request->getQuery()->update($page); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param PageDestroyRequest $request + * @param Page $page + * @return JsonResponse + */ + #[Route(fullUri: 'page/{page}', name: 'page.destroy', middleware: 'auth:sanctum')] + public function destroy(PageDestroyRequest $request, Page $page): JsonResponse + { + $resource = $request->getQuery()->destroy($page); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param PageRestoreRequest $request + * @param Page $page + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/page/{page}', name: 'page.restore', middleware: 'auth:sanctum')] + public function restore(PageRestoreRequest $request, Page $page): JsonResponse + { + $resource = $request->getQuery()->restore($page); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param PageForceDeleteRequest $request + * @param Page $page + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/page/{page}', name: 'page.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(PageForceDeleteRequest $request, Page $page): JsonResponse + { + return $request->getQuery()->forceDelete($page); + } +} diff --git a/app/Http/Controllers/Api/Wiki/Anime/SynonymController.php b/app/Http/Controllers/Api/Wiki/Anime/SynonymController.php new file mode 100644 index 000000000..98404de02 --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/Anime/SynonymController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param SynonymStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'animesynonym', name: 'animesynonym.store', middleware: 'auth:sanctum')] + public function store(SynonymStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param SynonymShowRequest $request + * @param AnimeSynonym $synonym + * @return JsonResponse + */ + #[Route(fullUri: 'animesynonym/{animesynonym}', name: 'animesynonym.show')] + public function show(SynonymShowRequest $request, AnimeSynonym $synonym): JsonResponse + { + $resource = $request->getQuery()->show($synonym); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param SynonymUpdateRequest $request + * @param AnimeSynonym $synonym + * @return JsonResponse + */ + #[Route(fullUri: 'animesynonym/{animesynonym}', name: 'animesynonym.update', middleware: 'auth:sanctum')] + public function update(SynonymUpdateRequest $request, AnimeSynonym $synonym): JsonResponse + { + $resource = $request->getQuery()->update($synonym); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param SynonymDestroyRequest $request + * @param AnimeSynonym $synonym + * @return JsonResponse + */ + #[Route(fullUri: 'animesynonym/{animesynonym}', name: 'animesynonym.destroy', middleware: 'auth:sanctum')] + public function destroy(SynonymDestroyRequest $request, AnimeSynonym $synonym): JsonResponse + { + $resource = $request->getQuery()->destroy($synonym); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param SynonymRestoreRequest $request + * @param AnimeSynonym $synonym + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/animesynonym/{animesynonym}', name: 'animesynonym.restore', middleware: 'auth:sanctum')] + public function restore(SynonymRestoreRequest $request, AnimeSynonym $synonym): JsonResponse + { + $resource = $request->getQuery()->restore($synonym); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param SynonymForceDeleteRequest $request + * @param AnimeSynonym $synonym + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/animesynonym/{animesynonym}', name: 'animesynonym.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(SynonymForceDeleteRequest $request, AnimeSynonym $synonym): JsonResponse + { + return $request->getQuery()->forceDelete($synonym); + } +} diff --git a/app/Http/Controllers/Api/Wiki/Anime/Theme/EntryController.php b/app/Http/Controllers/Api/Wiki/Anime/Theme/EntryController.php new file mode 100644 index 000000000..3dd2829b8 --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/Anime/Theme/EntryController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param EntryStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'animethemeentry', name: 'animethemeentry.store', middleware: 'auth:sanctum')] + public function store(EntryStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param EntryShowRequest $request + * @param AnimeThemeEntry $entry + * @return JsonResponse + */ + #[Route(fullUri: 'animethemeentry/{animethemeentry}', name: 'animethemeentry.show')] + public function show(EntryShowRequest $request, AnimeThemeEntry $entry): JsonResponse + { + $resource = $request->getQuery()->show($entry); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param EntryUpdateRequest $request + * @param AnimeThemeEntry $entry + * @return JsonResponse + */ + #[Route(fullUri: 'animethemeentry/{animethemeentry}', name: 'animethemeentry.update', middleware: 'auth:sanctum')] + public function update(EntryUpdateRequest $request, AnimeThemeEntry $entry): JsonResponse + { + $resource = $request->getQuery()->update($entry); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param EntryDestroyRequest $request + * @param AnimeThemeEntry $entry + * @return JsonResponse + */ + #[Route(fullUri: 'animethemeentry/{animethemeentry}', name: 'animethemeentry.destroy', middleware: 'auth:sanctum')] + public function destroy(EntryDestroyRequest $request, AnimeThemeEntry $entry): JsonResponse + { + $resource = $request->getQuery()->destroy($entry); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param EntryRestoreRequest $request + * @param AnimeThemeEntry $entry + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/animethemeentry/{animethemeentry}', name: 'animethemeentry.restore', middleware: 'auth:sanctum')] + public function restore(EntryRestoreRequest $request, AnimeThemeEntry $entry): JsonResponse + { + $resource = $request->getQuery()->restore($entry); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param EntryForceDeleteRequest $request + * @param AnimeThemeEntry $entry + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/animethemeentry/{animethemeentry}', name: 'animethemeentry.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(EntryForceDeleteRequest $request, AnimeThemeEntry $entry): JsonResponse + { + return $request->getQuery()->forceDelete($entry); + } +} diff --git a/app/Http/Controllers/Api/Wiki/Anime/ThemeController.php b/app/Http/Controllers/Api/Wiki/Anime/ThemeController.php new file mode 100644 index 000000000..fc0974bfa --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/Anime/ThemeController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param ThemeStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'animetheme', name: 'animetheme.store', middleware: 'auth:sanctum')] + public function store(ThemeStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param ThemeShowRequest $request + * @param AnimeTheme $theme + * @return JsonResponse + */ + #[Route(fullUri: 'animetheme/{animetheme}', name: 'animetheme.show')] + public function show(ThemeShowRequest $request, AnimeTheme $theme): JsonResponse + { + $resource = $request->getQuery()->show($theme); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param ThemeUpdateRequest $request + * @param AnimeTheme $theme + * @return JsonResponse + */ + #[Route(fullUri: 'animetheme/{animetheme}', name: 'animetheme.update', middleware: 'auth:sanctum')] + public function update(ThemeUpdateRequest $request, AnimeTheme $theme): JsonResponse + { + $resource = $request->getQuery()->update($theme); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param ThemeDestroyRequest $request + * @param AnimeTheme $theme + * @return JsonResponse + */ + #[Route(fullUri: 'animetheme/{animetheme}', name: 'animetheme.destroy', middleware: 'auth:sanctum')] + public function destroy(ThemeDestroyRequest $request, AnimeTheme $theme): JsonResponse + { + $resource = $request->getQuery()->destroy($theme); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param ThemeRestoreRequest $request + * @param AnimeTheme $theme + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/animetheme/{animetheme}', name: 'animetheme.restore', middleware: 'auth:sanctum')] + public function restore(ThemeRestoreRequest $request, AnimeTheme $theme): JsonResponse + { + $resource = $request->getQuery()->restore($theme); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param ThemeForceDeleteRequest $request + * @param AnimeTheme $theme + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/animetheme/{animetheme}', name: 'animetheme.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(ThemeForceDeleteRequest $request, AnimeTheme $theme): JsonResponse + { + return $request->getQuery()->forceDelete($theme); + } +} diff --git a/app/Http/Controllers/Api/Wiki/Anime/YearController.php b/app/Http/Controllers/Api/Wiki/Anime/YearController.php new file mode 100644 index 000000000..5d9ba37c3 --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/Anime/YearController.php @@ -0,0 +1,76 @@ +distinct(Anime::ATTRIBUTE_YEAR) + ->orderBy(Anime::ATTRIBUTE_YEAR) + ->pluck(Anime::ATTRIBUTE_YEAR) + ); + } + + /** + * Display a listing of anime of year by season. + * + * @param YearShowRequest $request + * @param string $year + * @return JsonResponse + */ + #[Route(fullUri: 'animeyear/{year}', name: 'animeyear.show')] + public function show(YearShowRequest $request, string $year): JsonResponse + { + $includeCriteria = $request->getQuery()->getIncludeCriteria(AnimeCollection::$wrap); + + $allowedIncludePaths = collect($includeCriteria?->getPaths()); + + $anime = new AnimeCollection( + Anime::query() + ->where(Anime::ATTRIBUTE_YEAR, $year) + ->with($allowedIncludePaths->all()) + ->orderBy(Anime::ATTRIBUTE_NAME) + ->get(), + $request->getQuery() + ); + + $anime = collect($anime->toArray($request)); + + $anime = $anime->groupBy(fn (AnimeResource $anime) => Str::lower(AnimeSeason::getDescription($anime->season))); + + $anime = $anime->sortBy( + fn (Collection $seasonAnime, string $seasonKey) => AnimeSeason::getValue(Str::upper($seasonKey)) + ); + + return new JsonResponse($anime); + } +} diff --git a/app/Http/Controllers/Api/Wiki/AnimeController.php b/app/Http/Controllers/Api/Wiki/AnimeController.php new file mode 100644 index 000000000..0b06957b1 --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/AnimeController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param AnimeStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'anime', name: 'anime.store', middleware: 'auth:sanctum')] + public function store(AnimeStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param AnimeShowRequest $request + * @param Anime $anime + * @return JsonResponse + */ + #[Route(fullUri: 'anime/{anime}', name: 'anime.show')] + public function show(AnimeShowRequest $request, Anime $anime): JsonResponse + { + $resource = $request->getQuery()->show($anime); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param AnimeUpdateRequest $request + * @param Anime $anime + * @return JsonResponse + */ + #[Route(fullUri: 'anime/{anime}', name: 'anime.update', middleware: 'auth:sanctum')] + public function update(AnimeUpdateRequest $request, Anime $anime): JsonResponse + { + $resource = $request->getQuery()->update($anime); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param AnimeDestroyRequest $request + * @param Anime $anime + * @return JsonResponse + */ + #[Route(fullUri: 'anime/{anime}', name: 'anime.destroy', middleware: 'auth:sanctum')] + public function destroy(AnimeDestroyRequest $request, Anime $anime): JsonResponse + { + $resource = $request->getQuery()->destroy($anime); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param AnimeRestoreRequest $request + * @param Anime $anime + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/anime/{anime}', name: 'anime.restore', middleware: 'auth:sanctum')] + public function restore(AnimeRestoreRequest $request, Anime $anime): JsonResponse + { + $resource = $request->getQuery()->restore($anime); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param AnimeForceDeleteRequest $request + * @param Anime $anime + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/anime/{anime}', name: 'anime.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(AnimeForceDeleteRequest $request, Anime $anime): JsonResponse + { + return $request->getQuery()->forceDelete($anime); + } +} diff --git a/app/Http/Controllers/Api/Wiki/ArtistController.php b/app/Http/Controllers/Api/Wiki/ArtistController.php new file mode 100644 index 000000000..057965b1d --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/ArtistController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param ArtistStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'artist', name: 'artist.store', middleware: 'auth:sanctum')] + public function store(ArtistStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param ArtistShowRequest $request + * @param Artist $artist + * @return JsonResponse + */ + #[Route(fullUri: 'artist/{artist}', name: 'artist.show')] + public function show(ArtistShowRequest $request, Artist $artist): JsonResponse + { + $resource = $request->getQuery()->show($artist); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param ArtistUpdateRequest $request + * @param Artist $artist + * @return JsonResponse + */ + #[Route(fullUri: 'artist/{artist}', name: 'artist.update', middleware: 'auth:sanctum')] + public function update(ArtistUpdateRequest $request, Artist $artist): JsonResponse + { + $resource = $request->getQuery()->update($artist); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param ArtistDestroyRequest $request + * @param Artist $artist + * @return JsonResponse + */ + #[Route(fullUri: 'artist/{artist}', name: 'artist.destroy', middleware: 'auth:sanctum')] + public function destroy(ArtistDestroyRequest $request, Artist $artist): JsonResponse + { + $resource = $request->getQuery()->destroy($artist); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param ArtistRestoreRequest $request + * @param Artist $artist + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/artist/{artist}', name: 'artist.restore', middleware: 'auth:sanctum')] + public function restore(ArtistRestoreRequest $request, Artist $artist): JsonResponse + { + $resource = $request->getQuery()->restore($artist); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param ArtistForceDeleteRequest $request + * @param Artist $artist + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/artist/{artist}', name: 'artist.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(ArtistForceDeleteRequest $request, Artist $artist): JsonResponse + { + return $request->getQuery()->forceDelete($artist); + } +} diff --git a/app/Http/Controllers/Api/Wiki/ExternalResourceController.php b/app/Http/Controllers/Api/Wiki/ExternalResourceController.php new file mode 100644 index 000000000..76aed11a8 --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/ExternalResourceController.php @@ -0,0 +1,124 @@ +getQuery()->index(); + + return $resources->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param ExternalResourceStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'resource', name: 'resource.store', middleware: 'auth:sanctum')] + public function store(ExternalResourceStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param ExternalResourceShowRequest $request + * @param ExternalResource $resource + * @return JsonResponse + */ + #[Route(fullUri: 'resource/{resource}', name: 'resource.show')] + public function show(ExternalResourceShowRequest $request, ExternalResource $resource): JsonResponse + { + $apiResource = $request->getQuery()->show($resource); + + return $apiResource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param ExternalResourceUpdateRequest $request + * @param ExternalResource $resource + * @return JsonResponse + */ + #[Route(fullUri: 'resource/{resource}', name: 'resource.update', middleware: 'auth:sanctum')] + public function update(ExternalResourceUpdateRequest $request, ExternalResource $resource): JsonResponse + { + $apiResource = $request->getQuery()->update($resource); + + return $apiResource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param ExternalResourceDestroyRequest $request + * @param ExternalResource $resource + * @return JsonResponse + */ + #[Route(fullUri: 'resource/{resource}', name: 'resource.destroy', middleware: 'auth:sanctum')] + public function destroy(ExternalResourceDestroyRequest $request, ExternalResource $resource): JsonResponse + { + $apiResource = $request->getQuery()->destroy($resource); + + return $apiResource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param ExternalResourceRestoreRequest $request + * @param ExternalResource $resource + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/resource/{resource}', name: 'resource.restore', middleware: 'auth:sanctum')] + public function restore(ExternalResourceRestoreRequest $request, ExternalResource $resource): JsonResponse + { + $apiResource = $request->getQuery()->restore($resource); + + return $apiResource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param ExternalResourceForceDeleteRequest $request + * @param ExternalResource $resource + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/resource/{resource}', name: 'resource.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(ExternalResourceForceDeleteRequest $request, ExternalResource $resource): JsonResponse + { + return $request->getQuery()->forceDelete($resource); + } +} diff --git a/app/Http/Controllers/Api/Wiki/ImageController.php b/app/Http/Controllers/Api/Wiki/ImageController.php new file mode 100644 index 000000000..748f3be50 --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/ImageController.php @@ -0,0 +1,124 @@ +getQuery()->index(); + + return $images->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param ImageStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'image', name: 'image.store', middleware: 'auth:sanctum')] + public function store(ImageStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param ImageShowRequest $request + * @param Image $image + * @return JsonResponse + */ + #[Route(fullUri: 'image/{image}', name: 'image.show')] + public function show(ImageShowRequest $request, Image $image): JsonResponse + { + $resource = $request->getQuery()->show($image); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param ImageUpdateRequest $request + * @param Image $image + * @return JsonResponse + */ + #[Route(fullUri: 'image/{image}', name: 'image.update', middleware: 'auth:sanctum')] + public function update(ImageUpdateRequest $request, Image $image): JsonResponse + { + $resource = $request->getQuery()->update($image); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param ImageDestroyRequest $request + * @param Image $image + * @return JsonResponse + */ + #[Route(fullUri: 'image/{image}', name: 'image.destroy', middleware: 'auth:sanctum')] + public function destroy(ImageDestroyRequest $request, Image $image): JsonResponse + { + $resource = $request->getQuery()->destroy($image); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param ImageRestoreRequest $request + * @param Image $image + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/image/{image}', name: 'image.restore', middleware: 'auth:sanctum')] + public function restore(ImageRestoreRequest $request, Image $image): JsonResponse + { + $resource = $request->getQuery()->restore($image); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param ImageForceDeleteRequest $request + * @param Image $image + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/image/{image}', name: 'image.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(ImageForceDeleteRequest $request, Image $image): JsonResponse + { + return $request->getQuery()->forceDelete($image); + } +} diff --git a/app/Http/Controllers/Api/Wiki/SearchController.php b/app/Http/Controllers/Api/Wiki/SearchController.php new file mode 100644 index 000000000..a9f665ffd --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/SearchController.php @@ -0,0 +1,31 @@ +getQuery()); + + return $resource->toResponse($request); + } +} diff --git a/app/Http/Controllers/Api/Wiki/SeriesController.php b/app/Http/Controllers/Api/Wiki/SeriesController.php new file mode 100644 index 000000000..db94fbb2c --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/SeriesController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param SeriesStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'series', name: 'series.store', middleware: 'auth:sanctum')] + public function store(SeriesStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param SeriesShowRequest $request + * @param Series $series + * @return JsonResponse + */ + #[Route(fullUri: 'series/{series}', name: 'series.show')] + public function show(SeriesShowRequest $request, Series $series): JsonResponse + { + $resource = $request->getQuery()->show($series); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param SeriesUpdateRequest $request + * @param Series $series + * @return JsonResponse + */ + #[Route(fullUri: 'series/{series}', name: 'series.update', middleware: 'auth:sanctum')] + public function update(SeriesUpdateRequest $request, Series $series): JsonResponse + { + $resource = $request->getQuery()->update($series); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param SeriesDestroyRequest $request + * @param Series $series + * @return JsonResponse + */ + #[Route(fullUri: 'series/{series}', name: 'series.destroy', middleware: 'auth:sanctum')] + public function destroy(SeriesDestroyRequest $request, Series $series): JsonResponse + { + $resource = $request->getQuery()->destroy($series); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param SeriesRestoreRequest $request + * @param Series $series + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/series/{series}', name: 'series.restore', middleware: 'auth:sanctum')] + public function restore(SeriesRestoreRequest $request, Series $series): JsonResponse + { + $resource = $request->getQuery()->restore($series); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param SeriesForceDeleteRequest $request + * @param Series $series + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/series/{series}', name: 'series.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(SeriesForceDeleteRequest $request, Series $series): JsonResponse + { + return $request->getQuery()->forceDelete($series); + } +} diff --git a/app/Http/Controllers/Api/Wiki/SongController.php b/app/Http/Controllers/Api/Wiki/SongController.php new file mode 100644 index 000000000..7125b040e --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/SongController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param SongStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'song', name: 'song.store', middleware: 'auth:sanctum')] + public function store(SongStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param SongShowRequest $request + * @param Song $song + * @return JsonResponse + */ + #[Route(fullUri: 'song/{song}', name: 'song.show')] + public function show(SongShowRequest $request, Song $song): JsonResponse + { + $resource = $request->getQuery()->show($song); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param SongUpdateRequest $request + * @param Song $song + * @return JsonResponse + */ + #[Route(fullUri: 'song/{song}', name: 'song.update', middleware: 'auth:sanctum')] + public function update(SongUpdateRequest $request, Song $song): JsonResponse + { + $resource = $request->getQuery()->update($song); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param SongDestroyRequest $request + * @param Song $song + * @return JsonResponse + */ + #[Route(fullUri: 'song/{song}', name: 'song.destroy', middleware: 'auth:sanctum')] + public function destroy(SongDestroyRequest $request, Song $song): JsonResponse + { + $resource = $request->getQuery()->destroy($song); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param SongRestoreRequest $request + * @param Song $song + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/song/{song}', name: 'song.restore', middleware: 'auth:sanctum')] + public function restore(SongRestoreRequest $request, Song $song): JsonResponse + { + $resource = $request->getQuery()->restore($song); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param SongForceDeleteRequest $request + * @param Song $song + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/song/{song}', name: 'song.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(SongForceDeleteRequest $request, Song $song): JsonResponse + { + return $request->getQuery()->forceDelete($song); + } +} diff --git a/app/Http/Controllers/Api/Wiki/StudioController.php b/app/Http/Controllers/Api/Wiki/StudioController.php new file mode 100644 index 000000000..4656f595f --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/StudioController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param StudioStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'studio', name: 'studio.store', middleware: 'auth:sanctum')] + public function store(StudioStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param StudioShowRequest $request + * @param Studio $studio + * @return JsonResponse + */ + #[Route(fullUri: 'studio/{studio}', name: 'studio.show')] + public function show(StudioShowRequest $request, Studio $studio): JsonResponse + { + $resource = $request->getQuery()->show($studio); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param StudioUpdateRequest $request + * @param Studio $studio + * @return JsonResponse + */ + #[Route(fullUri: 'studio/{studio}', name: 'studio.update', middleware: 'auth:sanctum')] + public function update(StudioUpdateRequest $request, Studio $studio): JsonResponse + { + $resource = $request->getQuery()->update($studio); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param StudioDestroyRequest $request + * @param Studio $studio + * @return JsonResponse + */ + #[Route(fullUri: 'studio/{studio}', name: 'studio.destroy', middleware: 'auth:sanctum')] + public function destroy(StudioDestroyRequest $request, Studio $studio): JsonResponse + { + $resource = $request->getQuery()->destroy($studio); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param StudioRestoreRequest $request + * @param Studio $studio + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/studio/{studio}', name: 'studio.restore', middleware: 'auth:sanctum')] + public function restore(StudioRestoreRequest $request, Studio $studio): JsonResponse + { + $resource = $request->getQuery()->restore($studio); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param StudioForceDeleteRequest $request + * @param Studio $studio + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/studio/{studio}', name: 'studio.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(StudioForceDeleteRequest $request, Studio $studio): JsonResponse + { + return $request->getQuery()->forceDelete($studio); + } +} diff --git a/app/Http/Controllers/Api/Wiki/VideoController.php b/app/Http/Controllers/Api/Wiki/VideoController.php new file mode 100644 index 000000000..37eec7c77 --- /dev/null +++ b/app/Http/Controllers/Api/Wiki/VideoController.php @@ -0,0 +1,129 @@ +getQuery(); + + if ($query->hasSearchCriteria()) { + return $query->search(PaginationStrategy::OFFSET())->toResponse($request); + } + + return $query->index()->toResponse($request); + } + + /** + * Store a newly created resource. + * + * @param VideoStoreRequest $request + * @return JsonResponse + */ + #[Route(fullUri: 'video', name: 'video.store', middleware: 'auth:sanctum')] + public function store(VideoStoreRequest $request): JsonResponse + { + $resource = $request->getQuery()->store(); + + return $resource->toResponse($request); + } + + /** + * Display the specified resource. + * + * @param VideoShowRequest $request + * @param Video $video + * @return JsonResponse + */ + #[Route(fullUri: 'video/{video}', name: 'video.show')] + public function show(VideoShowRequest $request, Video $video): JsonResponse + { + $resource = $request->getQuery()->show($video); + + return $resource->toResponse($request); + } + + /** + * Update the specified resource. + * + * @param VideoUpdateRequest $request + * @param Video $video + * @return JsonResponse + */ + #[Route(fullUri: 'video/{video}', name: 'video.update', middleware: 'auth:sanctum')] + public function update(VideoUpdateRequest $request, Video $video): JsonResponse + { + $resource = $request->getQuery()->update($video); + + return $resource->toResponse($request); + } + + /** + * Remove the specified resource. + * + * @param VideoDestroyRequest $request + * @param Video $video + * @return JsonResponse + */ + #[Route(fullUri: 'video/{video}', name: 'video.destroy', middleware: 'auth:sanctum')] + public function destroy(VideoDestroyRequest $request, Video $video): JsonResponse + { + $resource = $request->getQuery()->destroy($video); + + return $resource->toResponse($request); + } + + /** + * Restore the specified resource. + * + * @param VideoRestoreRequest $request + * @param Video $video + * @return JsonResponse + */ + #[Route(method: 'patch', fullUri: 'restore/video/{video}', name: 'video.restore', middleware: 'auth:sanctum')] + public function restore(VideoRestoreRequest $request, Video $video): JsonResponse + { + $resource = $request->getQuery()->restore($video); + + return $resource->toResponse($request); + } + + /** + * Hard-delete the specified resource. + * + * @param VideoForceDeleteRequest $request + * @param Video $video + * @return JsonResponse + */ + #[Route(method: 'delete', fullUri: 'forceDelete/video/{video}', name: 'video.forceDelete', middleware: 'auth:sanctum')] + public function forceDelete(VideoForceDeleteRequest $request, Video $video): JsonResponse + { + return $request->getQuery()->forceDelete($video); + } +} diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php new file mode 100644 index 000000000..7c353e1d4 --- /dev/null +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -0,0 +1,144 @@ +middleware(['guest', 'signed', 'has_open_invitation', 'without_trashed:invitation'], ['only' => ['showRegistrationForm', 'register']]); + } + + /** + * Where to redirect users after registration. + * + * @return string + */ + #[DoNotDiscover] + public function redirectPath(): string + { + return route('dashboard'); + } + + /** + * Show the application registration form. + * + * @param Invitation $invitation + * @return View|Factory + */ + #[Route(method: 'get', fullUri: 'register/{invitation}', name: 'showRegistrationForm')] + public function showRegistrationForm(Invitation $invitation): View|Factory + { + return view('auth.register', [ + 'invitation' => $invitation, + ]); + } + + /** + * Handle a registration request for the application. + * + * @param Request $request + * @param Invitation $invitation + * @return JsonResponse|RedirectResponse|Redirector + * + * @throws ValidationException + */ + #[Route(method: 'post', fullUri: 'register/{invitation}', name: 'register')] + public function register(Request $request, Invitation $invitation): JsonResponse|RedirectResponse|Redirector + { + $data = array_merge($request->all(), [ + 'name' => $invitation->name, + 'email' => $invitation->email, + ]); + + $this->validator($data)->validate(); + + event(new Registered($user = $this->create($data))); + + $invitation->status = InvitationStatus::CLOSED(); + $invitation->save(); + + $this->guard()->login($user); + + if ($response = $this->registered($request, $user)) { + return $response; + } + + return $request->wantsJson() + ? new JsonResponse([], 201) + : redirect($this->redirectPath()); + } + + /** + * Get a validator for an incoming registration request. + * + * @param array $data + * @return \Illuminate\Contracts\Validation\Validator + */ + #[DoNotDiscover] + protected function validator(array $data): \Illuminate\Contracts\Validation\Validator + { + return Validator::make($data, [ + 'name' => ['required', 'string', 'max:192'], + 'email' => ['required', 'string', 'email', 'max:192', Rule::unique(User::TABLE)], + 'password' => Password::required(), + 'terms' => ['required'], + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * @return User + */ + #[DoNotDiscover] + protected function create(array $data): User + { + return User::factory()->createOne([ + User::ATTRIBUTE_EMAIL => Arr::get($data, 'email'), + User::ATTRIBUTE_EMAIL_VERIFIED_AT => Date::now(), + User::ATTRIBUTE_NAME => Arr::get($data, 'name'), + User::ATTRIBUTE_PASSWORD => Hash::make(Arr::get($data, 'password')), + User::ATTRIBUTE_REMEMBER_TOKEN => null, + ]); + } +} diff --git a/app/Http/Controllers/Billing/TransparencyController.php b/app/Http/Controllers/Billing/TransparencyController.php new file mode 100644 index 000000000..41867e1b2 --- /dev/null +++ b/app/Http/Controllers/Billing/TransparencyController.php @@ -0,0 +1,34 @@ + $request->getBalances(), + 'transactions' => $request->getTransactions(), + 'filterOptions' => $request->getValidDates(), + 'selectedDate' => $request->getSelectedDate(), + ]); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index a0a2a8a34..e951291fa 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -1,5 +1,7 @@ first(); - - return response()->view('sitemap.index', [ - 'video' => $video, - ])->header('Content-Type', 'text/xml'); - } - - public function videos() - { - $video = Video::orderBy('updated_at', 'desc')->first(); - - return response()->view('sitemap.videos', [ - 'video' => $video, - ])->header('Content-Type', 'text/xml'); - } -} diff --git a/app/Http/Controllers/VideosController.php b/app/Http/Controllers/VideosController.php deleted file mode 100644 index 3d2883f1f..000000000 --- a/app/Http/Controllers/VideosController.php +++ /dev/null @@ -1,45 +0,0 @@ -orWhere('filename', $alias)->first(); - - // Rather than let an error be thrown, we will instead redirect to the home screen. - // We are doing this to try and prevent the overzealous CDN from caching the errors. - // We don't initiate a session for requesting video, so we can't flash a message. - if ($video == null) { - return redirect()->route('welcome'); - } - - $response = new StreamedResponse; - - $disposition = $response->headers->makeDisposition('inline', $video->basename); - - $response->headers->replace([ - 'Accept-Ranges' => 'bytes', - 'Content-Type' => 'video/webm', - 'Content-Length' => $video->size, - 'Content-Disposition' => $disposition, - ]); - - $fs = Storage::disk('spaces'); - - $response->setCallback(function () use ($fs, $video) { - $stream = $fs->readStream($video->path); - fpassthru($stream); - fclose($stream); - }); - - return $response; - } -} diff --git a/app/Http/Controllers/WelcomeController.php b/app/Http/Controllers/WelcomeController.php deleted file mode 100644 index 1c8cdc75a..000000000 --- a/app/Http/Controllers/WelcomeController.php +++ /dev/null @@ -1,25 +0,0 @@ - $announcements, - 'grill' => $grill->getPath(), - 'videoCount' => $videoCount, - ]); - } -} diff --git a/app/Http/Controllers/Wiki/VideoController.php b/app/Http/Controllers/Wiki/VideoController.php new file mode 100644 index 000000000..af3542c4f --- /dev/null +++ b/app/Http/Controllers/Wiki/VideoController.php @@ -0,0 +1,63 @@ +middleware(['is_video_streaming_allowed', 'without_trashed:video', 'record_view:video'], ['only' => 'show']); + } + + /** + * Stream video through nginx internal redirect. + * + * @param Video $video + * @return Response + */ + #[Route(fullUri: '/{video}', name: 'video.show')] + public function show(Video $video): Response + { + $fs = Storage::disk('videos'); + + // Generate temporary link for the object + $temporaryURL = $fs->temporaryUrl($video->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 = "/video_redirect/$url_scheme/$url_host$url_path_query"; + + $response = new Response(); + + $disposition = $response->headers->makeDisposition('inline', $video->basename); + + return $response->withHeaders([ + 'Accept-Ranges' => 'bytes', + 'Content-Type' => $video->mimetype, + 'Content-Length' => $video->size, + 'Content-Disposition' => $disposition, + 'X-Accel-Redirect' => $link, + ]); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index c0bc89e6c..2255cb39d 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -1,9 +1,44 @@ */ protected $middleware = [ - \App\Http\Middleware\TrustProxies::class, - //\Fruitcake\Cors\HandleCors::class, - \App\Http\Middleware\CheckForMaintenanceMode::class, - \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, - //\App\Http\Middleware\TrimStrings::class, - //\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, - \App\Http\Middleware\LoggerMiddleware::class, + TrustHosts::class, + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, + ValidatePostSize::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, + SecureHeadersMiddleware::class, ]; /** * The application's route middleware groups. * - * @var array + * @var array> */ protected $middlewareGroups = [ 'web' => [ - //\App\Http\Middleware\EncryptCookies::class, - //\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - //\Illuminate\Session\Middleware\StartSession::class, - // \Illuminate\Session\Middleware\AuthenticateSession::class, - //\Illuminate\View\Middleware\ShareErrorsFromSession::class, - //\App\Http\Middleware\VerifyCsrfToken::class, - \Illuminate\Routing\Middleware\SubstituteBindings::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + AuthenticateSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + LogRequest::class, ], 'api' => [ - 'throttle:60,1', - \Illuminate\Routing\Middleware\SubstituteBindings::class, + SetAcceptJsonHeader::class, + 'throttle:api', + SubstituteBindings::class, + LogRequest::class, ], ]; @@ -50,18 +89,22 @@ class Kernel extends HttpKernel * * These middleware may be assigned to groups or used individually. * - * @var array + * @var array */ protected $routeMiddleware = [ - 'auth' => \App\Http\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, - 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, - 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, - 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.session' => AuthenticateSession::class, + 'cache.headers' => SetCacheHeaders::class, + 'can' => Authorize::class, + 'guest' => RedirectIfAuthenticated::class, + 'password.confirm' => RequirePassword::class, + 'signed' => ValidateSignature::class, + 'throttle' => ThrottleRequestsWithService::class, + 'verified' => EnsureEmailIsVerified::class, + 'has_open_invitation' => HasOpenInvitation::class, + 'is_video_streaming_allowed' => IsVideoStreamingAllowed::class, + 'without_trashed' => WithoutTrashed::class, + 'record_view' => RecordView::class, ]; } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 704089a7f..cd58092bc 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -1,21 +1,31 @@ expectsJson()) { return route('login'); } + + return null; } } diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php deleted file mode 100644 index 35b9824ba..000000000 --- a/app/Http/Middleware/CheckForMaintenanceMode.php +++ /dev/null @@ -1,17 +0,0 @@ -route('invitation'); + + if (! $invitation instanceof Invitation) { + throw new RuntimeException('has_open_invitation should only be configured for invitations'); + } + + if (! $invitation->isOpen()) { + abort(403, 'Closed Invitation'); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/IsVideoStreamingAllowed.php b/app/Http/Middleware/IsVideoStreamingAllowed.php new file mode 100644 index 000000000..f4b33efca --- /dev/null +++ b/app/Http/Middleware/IsVideoStreamingAllowed.php @@ -0,0 +1,36 @@ +toString(); + + Log::withContext([ + 'request-id' => $requestId, + ]); + + Log::info('Request Info', [ + 'method' => $request->method(), + 'full-url' => $request->fullUrl(), + 'ip' => $request->ip(), + 'headers' => $request->headers->all(), + ]); + + return $next($request)->header('Request-Id', $requestId); + } +} diff --git a/app/Http/Middleware/LoggerMiddleware.php b/app/Http/Middleware/LoggerMiddleware.php deleted file mode 100644 index 100ffc12c..000000000 --- a/app/Http/Middleware/LoggerMiddleware.php +++ /dev/null @@ -1,28 +0,0 @@ - $request->method(), - 'full-url' => $request->fullUrl(), - 'ip' => $request->ip(), - 'headers' => $request->headers->all(), - ]); - - return $next($request); - } -} diff --git a/app/Http/Middleware/RecordView.php b/app/Http/Middleware/RecordView.php new file mode 100644 index 000000000..517aa5a3b --- /dev/null +++ b/app/Http/Middleware/RecordView.php @@ -0,0 +1,40 @@ +route($modelKey); + + if (! $model instanceof Viewable) { + throw new RuntimeException('record_view should only be configured for viewable models'); + } + + if (config(FlagConstants::ALLOW_VIEW_RECORDING_FLAG_QUALIFIED, false)) { + views($model)->cooldown(now()->addMinutes(5))->record(); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 2395ddccf..7ebe270b2 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -1,25 +1,37 @@ check()) { - return redirect(RouteServiceProvider::HOME); + $guards = empty($guards) ? [null] : $guards; + + foreach ($guards as $guard) { + if (Auth::guard($guard)->check()) { + return redirect(RouteServiceProvider::HOME); + } } return $next($request); diff --git a/app/Http/Middleware/SetAcceptJsonHeader.php b/app/Http/Middleware/SetAcceptJsonHeader.php new file mode 100644 index 000000000..c9f880d2e --- /dev/null +++ b/app/Http/Middleware/SetAcceptJsonHeader.php @@ -0,0 +1,28 @@ +headers->set('Accept', 'application/json'); + + return $next($request); + } +} diff --git a/app/Http/Middleware/ThrottleRequestsWithService.php b/app/Http/Middleware/ThrottleRequestsWithService.php new file mode 100644 index 000000000..18bcd0913 --- /dev/null +++ b/app/Http/Middleware/ThrottleRequestsWithService.php @@ -0,0 +1,52 @@ +appUsesRedis() + ? App::make(ThrottleRequestsWithRedis::class) + : App::make(ThrottleRequests::class); + + // Use named limiter if configured, else use default handling + // Note: framework requires that we pass exactly 3 arguments to use named limiter + if (RateLimiter::limiter($maxAttempts) !== null) { + return $middleware->handle($request, $next, $maxAttempts); + } + + return $middleware->handle($request, $next, $maxAttempts, $decayMinutes, $prefix); + } +} diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php index 5a50e7b5c..180759ae0 100644 --- a/app/Http/Middleware/TrimStrings.php +++ b/app/Http/Middleware/TrimStrings.php @@ -1,17 +1,23 @@ */ protected $except = [ + 'current_password', 'password', 'password_confirmation', ]; diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php new file mode 100644 index 000000000..3bd6646df --- /dev/null +++ b/app/Http/Middleware/TrustHosts.php @@ -0,0 +1,25 @@ +allSubdomainsOfApplicationUrl(), + ]; + } +} diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index ee5b5958e..784d67d51 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -1,23 +1,33 @@ |string|null */ - protected $proxies; + protected $proxies = '*'; /** * The headers that should be used to detect proxies. * * @var int */ - protected $headers = Request::HEADER_X_FORWARDED_ALL; + protected $headers = + Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB; } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php deleted file mode 100644 index 0c13b8548..000000000 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ /dev/null @@ -1,17 +0,0 @@ -route($modelKey); + + if (! $model instanceof BaseModel) { + throw new RuntimeException('without_trashed should only be configured for models that can be soft deleted'); + } + + if ($model->trashed()) { + abort(403, 'Deleted Model'); + } + + return $next($request); + } +} diff --git a/app/Http/Requests/Api/Admin/AnnouncementDestroyRequest.php b/app/Http/Requests/Api/Admin/AnnouncementDestroyRequest.php new file mode 100644 index 000000000..60aa4b506 --- /dev/null +++ b/app/Http/Requests/Api/Admin/AnnouncementDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Admin/AnnouncementForceDeleteRequest.php b/app/Http/Requests/Api/Admin/AnnouncementForceDeleteRequest.php new file mode 100644 index 000000000..f04d18165 --- /dev/null +++ b/app/Http/Requests/Api/Admin/AnnouncementForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Admin/AnnouncementIndexRequest.php b/app/Http/Requests/Api/Admin/AnnouncementIndexRequest.php new file mode 100644 index 000000000..3a7d518f1 --- /dev/null +++ b/app/Http/Requests/Api/Admin/AnnouncementIndexRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Admin/AnnouncementRestoreRequest.php b/app/Http/Requests/Api/Admin/AnnouncementRestoreRequest.php new file mode 100644 index 000000000..68f0b1728 --- /dev/null +++ b/app/Http/Requests/Api/Admin/AnnouncementRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Admin/AnnouncementShowRequest.php b/app/Http/Requests/Api/Admin/AnnouncementShowRequest.php new file mode 100644 index 000000000..a555ab680 --- /dev/null +++ b/app/Http/Requests/Api/Admin/AnnouncementShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Admin/AnnouncementStoreRequest.php b/app/Http/Requests/Api/Admin/AnnouncementStoreRequest.php new file mode 100644 index 000000000..fb2b6a281 --- /dev/null +++ b/app/Http/Requests/Api/Admin/AnnouncementStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Admin/AnnouncementUpdateRequest.php b/app/Http/Requests/Api/Admin/AnnouncementUpdateRequest.php new file mode 100644 index 000000000..fe2331658 --- /dev/null +++ b/app/Http/Requests/Api/Admin/AnnouncementUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Base/EloquentDestroyRequest.php b/app/Http/Requests/Api/Base/EloquentDestroyRequest.php new file mode 100644 index 000000000..e346426ca --- /dev/null +++ b/app/Http/Requests/Api/Base/EloquentDestroyRequest.php @@ -0,0 +1,39 @@ +schema()->model(); + } +} diff --git a/app/Http/Requests/Api/Base/EloquentForceDeleteRequest.php b/app/Http/Requests/Api/Base/EloquentForceDeleteRequest.php new file mode 100644 index 000000000..2b2d6de8e --- /dev/null +++ b/app/Http/Requests/Api/Base/EloquentForceDeleteRequest.php @@ -0,0 +1,39 @@ +schema()->model(); + } +} diff --git a/app/Http/Requests/Api/Base/EloquentIndexRequest.php b/app/Http/Requests/Api/Base/EloquentIndexRequest.php new file mode 100644 index 000000000..9c86107f4 --- /dev/null +++ b/app/Http/Requests/Api/Base/EloquentIndexRequest.php @@ -0,0 +1,29 @@ +schema()->model(); + } +} diff --git a/app/Http/Requests/Api/Base/EloquentShowRequest.php b/app/Http/Requests/Api/Base/EloquentShowRequest.php new file mode 100644 index 000000000..e8ef2cf70 --- /dev/null +++ b/app/Http/Requests/Api/Base/EloquentShowRequest.php @@ -0,0 +1,29 @@ +schema()->model(); + } +} diff --git a/app/Http/Requests/Api/Base/EloquentUpdateRequest.php b/app/Http/Requests/Api/Base/EloquentUpdateRequest.php new file mode 100644 index 000000000..76500f23d --- /dev/null +++ b/app/Http/Requests/Api/Base/EloquentUpdateRequest.php @@ -0,0 +1,39 @@ +schema()->model(); + } +} diff --git a/app/Http/Requests/Api/BaseRequest.php b/app/Http/Requests/Api/BaseRequest.php new file mode 100644 index 000000000..8faca5ca2 --- /dev/null +++ b/app/Http/Requests/Api/BaseRequest.php @@ -0,0 +1,43 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Balance/BalanceForceDeleteRequest.php b/app/Http/Requests/Api/Billing/Balance/BalanceForceDeleteRequest.php new file mode 100644 index 000000000..858951131 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Balance/BalanceForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Balance/BalanceIndexRequest.php b/app/Http/Requests/Api/Billing/Balance/BalanceIndexRequest.php new file mode 100644 index 000000000..232f63de8 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Balance/BalanceIndexRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Balance/BalanceRestoreRequest.php b/app/Http/Requests/Api/Billing/Balance/BalanceRestoreRequest.php new file mode 100644 index 000000000..cb994c960 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Balance/BalanceRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Balance/BalanceShowRequest.php b/app/Http/Requests/Api/Billing/Balance/BalanceShowRequest.php new file mode 100644 index 000000000..73b7a6ef5 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Balance/BalanceShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Balance/BalanceStoreRequest.php b/app/Http/Requests/Api/Billing/Balance/BalanceStoreRequest.php new file mode 100644 index 000000000..741b875ff --- /dev/null +++ b/app/Http/Requests/Api/Billing/Balance/BalanceStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Balance/BalanceUpdateRequest.php b/app/Http/Requests/Api/Billing/Balance/BalanceUpdateRequest.php new file mode 100644 index 000000000..b1631949a --- /dev/null +++ b/app/Http/Requests/Api/Billing/Balance/BalanceUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Transaction/TransactionDestroyRequest.php b/app/Http/Requests/Api/Billing/Transaction/TransactionDestroyRequest.php new file mode 100644 index 000000000..c7cdf3046 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Transaction/TransactionDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Transaction/TransactionForceDeleteRequest.php b/app/Http/Requests/Api/Billing/Transaction/TransactionForceDeleteRequest.php new file mode 100644 index 000000000..d0c819238 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Transaction/TransactionForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Transaction/TransactionIndexRequest.php b/app/Http/Requests/Api/Billing/Transaction/TransactionIndexRequest.php new file mode 100644 index 000000000..5cbd51036 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Transaction/TransactionIndexRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Transaction/TransactionRestoreRequest.php b/app/Http/Requests/Api/Billing/Transaction/TransactionRestoreRequest.php new file mode 100644 index 000000000..43bd2f719 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Transaction/TransactionRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Transaction/TransactionShowRequest.php b/app/Http/Requests/Api/Billing/Transaction/TransactionShowRequest.php new file mode 100644 index 000000000..656a1240d --- /dev/null +++ b/app/Http/Requests/Api/Billing/Transaction/TransactionShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Transaction/TransactionStoreRequest.php b/app/Http/Requests/Api/Billing/Transaction/TransactionStoreRequest.php new file mode 100644 index 000000000..8a856d565 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Transaction/TransactionStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Billing/Transaction/TransactionUpdateRequest.php b/app/Http/Requests/Api/Billing/Transaction/TransactionUpdateRequest.php new file mode 100644 index 000000000..cf7de49a8 --- /dev/null +++ b/app/Http/Requests/Api/Billing/Transaction/TransactionUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Config/FlagsRequest.php b/app/Http/Requests/Api/Config/FlagsRequest.php new file mode 100644 index 000000000..f2569f04e --- /dev/null +++ b/app/Http/Requests/Api/Config/FlagsRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Config/WikiRequest.php b/app/Http/Requests/Api/Config/WikiRequest.php new file mode 100644 index 000000000..4d3d5fd77 --- /dev/null +++ b/app/Http/Requests/Api/Config/WikiRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/DestroyRequest.php b/app/Http/Requests/Api/DestroyRequest.php new file mode 100644 index 000000000..9199a9bd3 --- /dev/null +++ b/app/Http/Requests/Api/DestroyRequest.php @@ -0,0 +1,31 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Document/Page/PageForceDeleteRequest.php b/app/Http/Requests/Api/Document/Page/PageForceDeleteRequest.php new file mode 100644 index 000000000..792a8aed4 --- /dev/null +++ b/app/Http/Requests/Api/Document/Page/PageForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Document/Page/PageIndexRequest.php b/app/Http/Requests/Api/Document/Page/PageIndexRequest.php new file mode 100644 index 000000000..9fa0701a6 --- /dev/null +++ b/app/Http/Requests/Api/Document/Page/PageIndexRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Document/Page/PageRestoreRequest.php b/app/Http/Requests/Api/Document/Page/PageRestoreRequest.php new file mode 100644 index 000000000..da395cd69 --- /dev/null +++ b/app/Http/Requests/Api/Document/Page/PageRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Document/Page/PageShowRequest.php b/app/Http/Requests/Api/Document/Page/PageShowRequest.php new file mode 100644 index 000000000..f2ce9835d --- /dev/null +++ b/app/Http/Requests/Api/Document/Page/PageShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Document/Page/PageStoreRequest.php b/app/Http/Requests/Api/Document/Page/PageStoreRequest.php new file mode 100644 index 000000000..2f182a112 --- /dev/null +++ b/app/Http/Requests/Api/Document/Page/PageStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Document/Page/PageUpdateRequest.php b/app/Http/Requests/Api/Document/Page/PageUpdateRequest.php new file mode 100644 index 000000000..e30f13616 --- /dev/null +++ b/app/Http/Requests/Api/Document/Page/PageUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/ForceDeleteRequest.php b/app/Http/Requests/Api/ForceDeleteRequest.php new file mode 100644 index 000000000..de994c1e8 --- /dev/null +++ b/app/Http/Requests/Api/ForceDeleteRequest.php @@ -0,0 +1,31 @@ +schema(); + $types = Arr::wrap($schema->type()); + + $schemaFormattedFilters = array_merge( + $this->getSchemaFormattedFilters($schema), + $this->getFilterFormats(new HasFilter($schema->allowedIncludes()), BinaryLogicalOperator::getInstances()) + ); + + $param = Str::of(FilterParser::param())->append('.')->append($schema->type())->__toString(); + $rules = $this->restrictAllowedTypes($param, $schemaFormattedFilters); + + $types = array_merge($types, $schemaFormattedFilters); + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + $types[] = $relationSchema->type(); + + $relationSchemaFormattedFilters = $this->getSchemaFormattedFilters($relationSchema); + $types = array_merge($types, $relationSchemaFormattedFilters); + + $param = Str::of(FilterParser::param())->append('.')->append($relationSchema->type())->__toString(); + $rules = $rules + $this->restrictAllowedTypes($param, $relationSchemaFormattedFilters); + } + + return $rules + $this->restrictAllowedTypes(FilterParser::param(), array_unique($types)); + } + + /** + * Get the paging validation rules. + * + * @return array + */ + protected function getPagingRules(): array + { + return $this->offset(); + } + + /** + * Get the search validation rules. + * + * @return array + */ + protected function getSearchRules(): array + { + if ($this instanceof SearchableRequest) { + return $this->optional(SearchParser::param()); + } + + return $this->prohibit(SearchParser::param()); + } + + /** + * Get the sort validation rules. + * + * @return array + */ + protected function getSortRules(): array + { + $schema = $this->schema(); + + $param = Str::of(SortParser::param())->append('.')->append($schema->type())->__toString(); + $rules = $this->restrictAllowedSortValues($param, $schema); + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + + $param = Str::of(SortParser::param())->append('.')->append($relationSchema->type())->__toString(); + $rules = $rules + $this->restrictAllowedSortValues($param, $relationSchema); + } + + return $rules; + } + + /** + * Configure validator with needed conditional validation. + * + * @param Validator $validator + * @return void + */ + protected function handleConditionalValidation(Validator $validator): void + { + parent::handleConditionalValidation($validator); + + $this->conditionallyRestrictAllowedSortTypes($validator); + } + + /** + * Filters shall be validated based on values. + * If the value contains a separator, this is a multi-value filter that builds a where in clause. + * Otherwise, this is a single-value filter that builds a where clause. + * Logical operators apply to specific clauses, so we must check formatted filter parameters against filter values. + * + * @param Validator $validator + * @return void + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function conditionallyRestrictAllowedFilterValues(Validator $validator): void + { + $schema = $this->schema(); + + foreach ($schema->filters() as $filter) { + $this->conditionallyRestrictFilter($validator, $schema, $filter); + } + $this->conditionallyRestrictFilter($validator, $schema, new HasFilter($schema->allowedIncludes())); + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + foreach ($relationSchema->filters() as $relationFilter) { + $this->conditionallyRestrictFilter($validator, $relationSchema, $relationFilter); + } + } + } + + /** + * Sort types can be scoped globally or by type for index endpoints. + * Ex: /api/anime?sort=year. + * Ex: /api/anime?sort[anime]=year. + * + * @param Validator $validator + * @return void + */ + protected function conditionallyRestrictAllowedSortTypes(Validator $validator): void + { + $schema = $this->schema(); + $types = Arr::wrap($schema->type()); + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + $types[] = $relationSchema->type(); + } + + $validator->sometimes( + SortParser::param(), + ['sometimes', 'required', new Delimited(Rule::in($this->formatAllowedSortValues($schema)))], + fn (Fluent $fluent) => is_string($fluent->get(SortParser::param())) + ); + + $validator->sometimes( + SortParser::param(), + ['nullable', Str::of('array:')->append(implode(',', $types))->__toString()], + fn (Fluent $fluent) => is_array($fluent->get(SortParser::param())) + ); + } +} diff --git a/app/Http/Requests/Api/ReadRequest.php b/app/Http/Requests/Api/ReadRequest.php new file mode 100644 index 000000000..456a5af88 --- /dev/null +++ b/app/Http/Requests/Api/ReadRequest.php @@ -0,0 +1,119 @@ +getFieldRules(), + $this->getFilterRules(), + $this->getIncludeRules(), + $this->getPagingRules(), + $this->getSearchRules(), + $this->getSortRules(), + ); + } + + /** + * Get the field validation rules. + * + * @return array + */ + protected function getFieldRules(): array + { + $schema = $this->schema(); + $types = Arr::wrap($schema->type()); + $rules = $this->restrictAllowedFieldValues($schema); + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + $types[] = $relationSchema->type(); + $rules = $rules + $this->restrictAllowedFieldValues($relationSchema); + } + + return $rules + $this->restrictAllowedTypes(FieldParser::param(), $types); + } + + /** + * Get the filter validation rules. + * + * @return array + */ + abstract protected function getFilterRules(): array; + + /** + * Get include validation rules. + * + * @return array + */ + protected function getIncludeRules(): array + { + $schema = $this->schema(); + + if (empty($schema->allowedIncludes())) { + return $this->prohibit(IncludeParser::param()); + } + + return $this->restrictAllowedIncludeValues(IncludeParser::param(), $schema); + } + + /** + * Get the paging validation rules. + * + * @return array + */ + abstract protected function getPagingRules(): array; + + /** + * Get the search validation rules. + * + * @return array + */ + abstract protected function getSearchRules(): array; + + /** + * Get the sort validation rules. + * + * @return array + */ + abstract protected function getSortRules(): array; +} diff --git a/app/Http/Requests/Api/RestoreRequest.php b/app/Http/Requests/Api/RestoreRequest.php new file mode 100644 index 000000000..00a9304cd --- /dev/null +++ b/app/Http/Requests/Api/RestoreRequest.php @@ -0,0 +1,31 @@ +schema(); + + $allowedIncludes = $schema->allowedIncludes(); + if (empty($allowedIncludes)) { + return $this->prohibit(FilterParser::param()); + } + + $rules = []; + $types = []; + + foreach ($allowedIncludes as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + $types[] = $relationSchema->type(); + + $relationSchemaFormattedFilters = $this->getSchemaFormattedFilters($relationSchema); + $types = array_merge($types, $relationSchemaFormattedFilters); + + $param = Str::of(FilterParser::param())->append('.')->append($relationSchema->type())->__toString(); + $rules = $rules + $this->restrictAllowedTypes($param, $relationSchemaFormattedFilters); + } + + return $rules + $this->restrictAllowedTypes(FilterParser::param(), array_unique($types)); + } + + /** + * Get the paging validation rules. + * + * @return array + */ + protected function getPagingRules(): array + { + return $this->prohibit(PagingParser::param()); + } + + /** + * Get the search validation rules. + * + * @return array + */ + protected function getSearchRules(): array + { + return $this->prohibit(SearchParser::param()); + } + + /** + * Get the sort validation rules. + * + * @return array + */ + protected function getSortRules(): array + { + $schema = $this->schema(); + + $allowedIncludes = $schema->allowedIncludes(); + if (empty($allowedIncludes)) { + return $this->prohibit(SortParser::param()); + } + + $rules = []; + $types = []; + + foreach ($allowedIncludes as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + $types[] = $relationSchema->type(); + + $param = Str::of(SortParser::param())->append('.')->append($relationSchema->type())->__toString(); + $rules = $rules + $this->restrictAllowedSortValues($param, $relationSchema); + } + + return $rules + $this->restrictAllowedTypes(SortParser::param(), $types); + } + + /** + * Filters shall be validated based on values. + * If the value contains a separator, this is a multi-value filter that builds a where in clause. + * Otherwise, this is a single-value filter that builds a where clause. + * Logical operators apply to specific clauses, so we must check formatted filter parameters against filter values. + * + * @param Validator $validator + * @return void + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function conditionallyRestrictAllowedFilterValues(Validator $validator): void + { + $schema = $this->schema(); + foreach ($schema->allowedIncludes() as $allowedInclude) { + $relationSchema = $allowedInclude->schema(); + foreach ($relationSchema->filters() as $relationFilter) { + $this->conditionallyRestrictFilter($validator, $relationSchema, $relationFilter); + } + } + } +} diff --git a/app/Http/Requests/Api/StoreRequest.php b/app/Http/Requests/Api/StoreRequest.php new file mode 100644 index 000000000..1819cfc2f --- /dev/null +++ b/app/Http/Requests/Api/StoreRequest.php @@ -0,0 +1,59 @@ +schema()->fields() as $field) { + $column = $field->getColumn(); + if ($field instanceof CreatableField) { + $rules = $rules + [$column => $field->getCreationRules($this)]; + } + } + + return $rules; + } + + /** + * Get fields for validation preparation. + * + * @return Field[] + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function getFieldsForPreparation(): array + { + return Arr::where( + $this->schema()->fields(), + fn (Field $field) => $field instanceof CreatableField + ); + } +} diff --git a/app/Http/Requests/Api/UpdateRequest.php b/app/Http/Requests/Api/UpdateRequest.php new file mode 100644 index 000000000..4d256228e --- /dev/null +++ b/app/Http/Requests/Api/UpdateRequest.php @@ -0,0 +1,59 @@ +schema()->fields() as $field) { + $column = $field->getColumn(); + if ($field instanceof UpdatableField) { + $rules = $rules + [$column => $field->getUpdateRules($this)]; + } + } + + return $rules; + } + + /** + * Get fields for validation preparation. + * + * @return Field[] + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function getFieldsForPreparation(): array + { + return Arr::where( + $this->schema()->fields(), + fn (Field $field) => $field instanceof UpdatableField + ); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/AnimeDestroyRequest.php b/app/Http/Requests/Api/Wiki/Anime/AnimeDestroyRequest.php new file mode 100644 index 000000000..f78869175 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/AnimeDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/AnimeForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Anime/AnimeForceDeleteRequest.php new file mode 100644 index 000000000..1bf140014 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/AnimeForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/AnimeIndexRequest.php b/app/Http/Requests/Api/Wiki/Anime/AnimeIndexRequest.php new file mode 100644 index 000000000..c6b19242d --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/AnimeIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/AnimeRestoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/AnimeRestoreRequest.php new file mode 100644 index 000000000..0f8cf8f0d --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/AnimeRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/AnimeShowRequest.php b/app/Http/Requests/Api/Wiki/Anime/AnimeShowRequest.php new file mode 100644 index 000000000..b21181d06 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/AnimeShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/AnimeStoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/AnimeStoreRequest.php new file mode 100644 index 000000000..861bddcb1 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/AnimeStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/AnimeUpdateRequest.php b/app/Http/Requests/Api/Wiki/Anime/AnimeUpdateRequest.php new file mode 100644 index 000000000..79e9c44ef --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/AnimeUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymDestroyRequest.php b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymDestroyRequest.php new file mode 100644 index 000000000..82daf35fc --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymForceDeleteRequest.php new file mode 100644 index 000000000..d004c4437 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymIndexRequest.php b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymIndexRequest.php new file mode 100644 index 000000000..a3af656d5 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymRestoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymRestoreRequest.php new file mode 100644 index 000000000..f21474f48 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymShowRequest.php b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymShowRequest.php new file mode 100644 index 000000000..39bcdd6fc --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymStoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymStoreRequest.php new file mode 100644 index 000000000..782ce2074 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymUpdateRequest.php b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymUpdateRequest.php new file mode 100644 index 000000000..bc7389feb --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Synonym/SynonymUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryDestroyRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryDestroyRequest.php new file mode 100644 index 000000000..42f0d15f9 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryForceDeleteRequest.php new file mode 100644 index 000000000..b50906317 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryIndexRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryIndexRequest.php new file mode 100644 index 000000000..5ee72e7f8 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryRestoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryRestoreRequest.php new file mode 100644 index 000000000..ec69a1340 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryShowRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryShowRequest.php new file mode 100644 index 000000000..7e30f4294 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryStoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryStoreRequest.php new file mode 100644 index 000000000..b005fcc40 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryUpdateRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryUpdateRequest.php new file mode 100644 index 000000000..efc82148f --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/Entry/EntryUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeDestroyRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeDestroyRequest.php new file mode 100644 index 000000000..da0550991 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeForceDeleteRequest.php new file mode 100644 index 000000000..b10831559 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeIndexRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeIndexRequest.php new file mode 100644 index 000000000..27705405f --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeRestoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeRestoreRequest.php new file mode 100644 index 000000000..d15a90a82 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeShowRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeShowRequest.php new file mode 100644 index 000000000..587353e97 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeStoreRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeStoreRequest.php new file mode 100644 index 000000000..13b168213 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeUpdateRequest.php b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeUpdateRequest.php new file mode 100644 index 000000000..2ccb127ac --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/Theme/ThemeUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/YearIndexRequest.php b/app/Http/Requests/Api/Wiki/Anime/YearIndexRequest.php new file mode 100644 index 000000000..dd037b8ad --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/YearIndexRequest.php @@ -0,0 +1,107 @@ +prohibit(FieldParser::param()); + } + + /** + * Get the filter validation rules. + * + * @return array + */ + protected function getFilterRules(): array + { + return $this->prohibit(FilterParser::param()); + } + + /** + * Get include validation rules. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function getIncludeRules(): array + { + return $this->prohibit(IncludeParser::param()); + } + + /** + * Get the paging validation rules. + * + * @return array + */ + protected function getPagingRules(): array + { + return $this->prohibit(PagingParser::param()); + } + + /** + * Get the search validation rules. + * + * @return array + */ + protected function getSearchRules(): array + { + return $this->prohibit(SearchParser::param()); + } + + /** + * Get the sort validation rules. + * + * @return array + */ + protected function getSortRules(): array + { + return $this->prohibit(SortParser::param()); + } + + /** + * Get the schema. + * + * @return EloquentSchema + */ + protected function schema(): EloquentSchema + { + return new AnimeSchema(); + } + + /** + * Get the validation API Query. + * + * @return ReadQuery + */ + public function getQuery(): ReadQuery + { + return new AnimeReadQuery($this->validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Anime/YearShowRequest.php b/app/Http/Requests/Api/Wiki/Anime/YearShowRequest.php new file mode 100644 index 000000000..c3ff6d085 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Anime/YearShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Artist/ArtistDestroyRequest.php b/app/Http/Requests/Api/Wiki/Artist/ArtistDestroyRequest.php new file mode 100644 index 000000000..418fc8cfe --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Artist/ArtistDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Artist/ArtistForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Artist/ArtistForceDeleteRequest.php new file mode 100644 index 000000000..2ac54f56e --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Artist/ArtistForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Artist/ArtistIndexRequest.php b/app/Http/Requests/Api/Wiki/Artist/ArtistIndexRequest.php new file mode 100644 index 000000000..07b547d91 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Artist/ArtistIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Artist/ArtistRestoreRequest.php b/app/Http/Requests/Api/Wiki/Artist/ArtistRestoreRequest.php new file mode 100644 index 000000000..b931f0b30 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Artist/ArtistRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Artist/ArtistShowRequest.php b/app/Http/Requests/Api/Wiki/Artist/ArtistShowRequest.php new file mode 100644 index 000000000..2d09fa7fc --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Artist/ArtistShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Artist/ArtistStoreRequest.php b/app/Http/Requests/Api/Wiki/Artist/ArtistStoreRequest.php new file mode 100644 index 000000000..d7c0e86cf --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Artist/ArtistStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Artist/ArtistUpdateRequest.php b/app/Http/Requests/Api/Wiki/Artist/ArtistUpdateRequest.php new file mode 100644 index 000000000..fda069a50 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Artist/ArtistUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceDestroyRequest.php b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceDestroyRequest.php new file mode 100644 index 000000000..2617292f2 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceForceDeleteRequest.php new file mode 100644 index 000000000..fdf84ceb8 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceIndexRequest.php b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceIndexRequest.php new file mode 100644 index 000000000..65992a930 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceIndexRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceRestoreRequest.php b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceRestoreRequest.php new file mode 100644 index 000000000..f927517e1 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceShowRequest.php b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceShowRequest.php new file mode 100644 index 000000000..9f15aafb1 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceStoreRequest.php b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceStoreRequest.php new file mode 100644 index 000000000..6a7629eda --- /dev/null +++ b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceUpdateRequest.php b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceUpdateRequest.php new file mode 100644 index 000000000..7b88aea5f --- /dev/null +++ b/app/Http/Requests/Api/Wiki/ExternalResource/ExternalResourceUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Image/ImageDestroyRequest.php b/app/Http/Requests/Api/Wiki/Image/ImageDestroyRequest.php new file mode 100644 index 000000000..e36fadfeb --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Image/ImageDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Image/ImageForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Image/ImageForceDeleteRequest.php new file mode 100644 index 000000000..12500329c --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Image/ImageForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Image/ImageIndexRequest.php b/app/Http/Requests/Api/Wiki/Image/ImageIndexRequest.php new file mode 100644 index 000000000..c7ffaf4c1 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Image/ImageIndexRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Image/ImageRestoreRequest.php b/app/Http/Requests/Api/Wiki/Image/ImageRestoreRequest.php new file mode 100644 index 000000000..cee383d22 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Image/ImageRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Image/ImageShowRequest.php b/app/Http/Requests/Api/Wiki/Image/ImageShowRequest.php new file mode 100644 index 000000000..0b8bbfce1 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Image/ImageShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Image/ImageStoreRequest.php b/app/Http/Requests/Api/Wiki/Image/ImageStoreRequest.php new file mode 100644 index 000000000..4118e6c6b --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Image/ImageStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Image/ImageUpdateRequest.php b/app/Http/Requests/Api/Wiki/Image/ImageUpdateRequest.php new file mode 100644 index 000000000..ad3a7557b --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Image/ImageUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/SearchRequest.php b/app/Http/Requests/Api/Wiki/SearchRequest.php new file mode 100644 index 000000000..7d904b4d3 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/SearchRequest.php @@ -0,0 +1,226 @@ +schema(); + $types = Arr::wrap($schema->type()); + $rules = $this->restrictAllowedFieldValues($schema); + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $resourceSchema = $allowedInclude->schema(); + $types[] = $resourceSchema->type(); + $rules = $rules + $this->restrictAllowedFieldValues($resourceSchema); + + foreach ($resourceSchema->allowedIncludes() as $resourceAllowedIncludePath) { + $resourceRelationSchema = $resourceAllowedIncludePath->schema(); + $types[] = $resourceRelationSchema->type(); + $rules = $rules + $this->restrictAllowedFieldValues($resourceRelationSchema); + } + } + + return $rules + $this->restrictAllowedTypes(FieldParser::param(), $types); + } + + /** + * Get the filter validation rules. + * + * @return array + */ + protected function getFilterRules(): array + { + $schema = $this->schema(); + $rules = []; + $types = []; + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $resourceSchema = $allowedInclude->schema(); + $types[] = $resourceSchema->type(); + + $resourceSchemaFormattedFilters = $this->getSchemaFormattedFilters($resourceSchema); + $types = array_merge($types, $resourceSchemaFormattedFilters); + + $param = Str::of(FilterParser::param())->append('.')->append($resourceSchema->type())->__toString(); + $rules = $rules + $this->restrictAllowedTypes($param, $resourceSchemaFormattedFilters); + + foreach ($resourceSchema->allowedIncludes() as $resourceAllowedIncludePath) { + $resourceRelationSchema = $resourceAllowedIncludePath->schema(); + $types[] = $resourceRelationSchema->type(); + + $relationSchemaFormattedFilters = $this->getSchemaFormattedFilters($resourceRelationSchema); + $types = array_merge($types, $relationSchemaFormattedFilters); + + $param = Str::of(FilterParser::param())->append('.')->append($resourceRelationSchema->type())->__toString(); + $rules = $rules + $this->restrictAllowedTypes($param, $relationSchemaFormattedFilters); + } + } + + return $rules + $this->restrictAllowedTypes(FilterParser::param(), array_unique($types)); + } + + /** + * Get include validation rules. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function getIncludeRules(): array + { + $schema = $this->schema(); + + $rules = []; + $types = []; + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $resourceSchema = $allowedInclude->schema(); + + $resourceIncludes = $resourceSchema->allowedIncludes(); + + if (! empty($resourceIncludes)) { + $types[] = $resourceSchema->type(); + + $param = Str::of(IncludeParser::param())->append('.')->append($resourceSchema->type())->__toString(); + + $rules = $rules + $this->restrictAllowedIncludeValues($param, $resourceSchema); + } + } + + return $rules + $this->restrictAllowedTypes(IncludeParser::param(), $types); + } + + /** + * Get the paging validation rules. + * + * @return array + */ + protected function getPagingRules(): array + { + return $this->limit(); + } + + /** + * Get the search validation rules. + * + * @return array + */ + protected function getSearchRules(): array + { + return $this->require(SearchParser::param(), ['string']); + } + + /** + * Get the sort validation rules. + * + * @return array + */ + protected function getSortRules(): array + { + $schema = $this->schema(); + + $rules = []; + $types = []; + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $resourceSchema = $allowedInclude->schema(); + + $types[] = $resourceSchema->type(); + + $param = Str::of(SortParser::param())->append('.')->append($resourceSchema->type())->__toString(); + + $rules = $rules + $this->restrictAllowedSortValues($param, $resourceSchema); + + foreach ($resourceSchema->allowedIncludes() as $resourceAllowedIncludePath) { + $resourceRelationSchema = $resourceAllowedIncludePath->schema(); + + $types[] = $resourceRelationSchema->type(); + + $param = Str::of(SortParser::param())->append('.')->append($resourceRelationSchema->type())->__toString(); + + $rules = $rules + $this->restrictAllowedSortValues($param, $resourceRelationSchema); + } + } + + return $rules + $this->restrictAllowedTypes(SortParser::param(), $types); + } + + /** + * Get the validation API Query. + * + * @return SearchReadQuery + */ + public function getQuery(): SearchReadQuery + { + return new SearchReadQuery($this->validated()); + } + + /** + * Get the schema. + * + * @return Schema + */ + protected function schema(): Schema + { + return new SearchSchema(); + } + + /** + * Filters shall be validated based on values. + * If the value contains a separator, this is a multi-value filter that builds a where in clause. + * Otherwise, this is a single-value filter that builds a where clause. + * Logical operators apply to specific clauses, so we must check formatted filter parameters against filter values. + * + * @param Validator $validator + * @return void + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function conditionallyRestrictAllowedFilterValues(Validator $validator): void + { + $schema = $this->schema(); + + foreach ($schema->allowedIncludes() as $allowedInclude) { + $resourceSchema = $allowedInclude->schema(); + + foreach ($resourceSchema->filters() as $resourceFilter) { + $this->conditionallyRestrictFilter($validator, $resourceSchema, $resourceFilter); + } + + foreach ($resourceSchema->allowedIncludes() as $resourceAllowedIncludePath) { + $resourceRelationSchema = $resourceAllowedIncludePath->schema(); + + foreach ($resourceRelationSchema->filters() as $resourceRelationFilter) { + $this->conditionallyRestrictFilter($validator, $resourceRelationSchema, $resourceRelationFilter); + } + } + } + } +} diff --git a/app/Http/Requests/Api/Wiki/Series/SeriesDestroyRequest.php b/app/Http/Requests/Api/Wiki/Series/SeriesDestroyRequest.php new file mode 100644 index 000000000..ee731496a --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Series/SeriesDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Series/SeriesForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Series/SeriesForceDeleteRequest.php new file mode 100644 index 000000000..ebf2846dd --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Series/SeriesForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Series/SeriesIndexRequest.php b/app/Http/Requests/Api/Wiki/Series/SeriesIndexRequest.php new file mode 100644 index 000000000..e7adf14ea --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Series/SeriesIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Series/SeriesRestoreRequest.php b/app/Http/Requests/Api/Wiki/Series/SeriesRestoreRequest.php new file mode 100644 index 000000000..8df243d45 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Series/SeriesRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Series/SeriesShowRequest.php b/app/Http/Requests/Api/Wiki/Series/SeriesShowRequest.php new file mode 100644 index 000000000..1edbedba0 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Series/SeriesShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Series/SeriesStoreRequest.php b/app/Http/Requests/Api/Wiki/Series/SeriesStoreRequest.php new file mode 100644 index 000000000..fa21a09a9 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Series/SeriesStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Series/SeriesUpdateRequest.php b/app/Http/Requests/Api/Wiki/Series/SeriesUpdateRequest.php new file mode 100644 index 000000000..83f1586e9 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Series/SeriesUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Song/SongDestroyRequest.php b/app/Http/Requests/Api/Wiki/Song/SongDestroyRequest.php new file mode 100644 index 000000000..90b8315bf --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Song/SongDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Song/SongForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Song/SongForceDeleteRequest.php new file mode 100644 index 000000000..37c70b9b9 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Song/SongForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Song/SongIndexRequest.php b/app/Http/Requests/Api/Wiki/Song/SongIndexRequest.php new file mode 100644 index 000000000..52023a761 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Song/SongIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Song/SongRestoreRequest.php b/app/Http/Requests/Api/Wiki/Song/SongRestoreRequest.php new file mode 100644 index 000000000..036f41b5a --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Song/SongRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Song/SongShowRequest.php b/app/Http/Requests/Api/Wiki/Song/SongShowRequest.php new file mode 100644 index 000000000..37e05864e --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Song/SongShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Song/SongStoreRequest.php b/app/Http/Requests/Api/Wiki/Song/SongStoreRequest.php new file mode 100644 index 000000000..19815a51d --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Song/SongStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Song/SongUpdateRequest.php b/app/Http/Requests/Api/Wiki/Song/SongUpdateRequest.php new file mode 100644 index 000000000..600308530 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Song/SongUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Studio/StudioDestroyRequest.php b/app/Http/Requests/Api/Wiki/Studio/StudioDestroyRequest.php new file mode 100644 index 000000000..8ab85b6c5 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Studio/StudioDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Studio/StudioForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Studio/StudioForceDeleteRequest.php new file mode 100644 index 000000000..968bd19c9 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Studio/StudioForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Studio/StudioIndexRequest.php b/app/Http/Requests/Api/Wiki/Studio/StudioIndexRequest.php new file mode 100644 index 000000000..16f42dceb --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Studio/StudioIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Studio/StudioRestoreRequest.php b/app/Http/Requests/Api/Wiki/Studio/StudioRestoreRequest.php new file mode 100644 index 000000000..f86b1422a --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Studio/StudioRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Studio/StudioShowRequest.php b/app/Http/Requests/Api/Wiki/Studio/StudioShowRequest.php new file mode 100644 index 000000000..8f112631f --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Studio/StudioShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Studio/StudioStoreRequest.php b/app/Http/Requests/Api/Wiki/Studio/StudioStoreRequest.php new file mode 100644 index 000000000..f0e0f8fa2 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Studio/StudioStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Studio/StudioUpdateRequest.php b/app/Http/Requests/Api/Wiki/Studio/StudioUpdateRequest.php new file mode 100644 index 000000000..de0f1bcda --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Studio/StudioUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Video/VideoDestroyRequest.php b/app/Http/Requests/Api/Wiki/Video/VideoDestroyRequest.php new file mode 100644 index 000000000..5e2124986 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Video/VideoDestroyRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Video/VideoForceDeleteRequest.php b/app/Http/Requests/Api/Wiki/Video/VideoForceDeleteRequest.php new file mode 100644 index 000000000..2b34906c0 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Video/VideoForceDeleteRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Video/VideoIndexRequest.php b/app/Http/Requests/Api/Wiki/Video/VideoIndexRequest.php new file mode 100644 index 000000000..e09ae7ba1 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Video/VideoIndexRequest.php @@ -0,0 +1,38 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Video/VideoRestoreRequest.php b/app/Http/Requests/Api/Wiki/Video/VideoRestoreRequest.php new file mode 100644 index 000000000..b4e597845 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Video/VideoRestoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Video/VideoShowRequest.php b/app/Http/Requests/Api/Wiki/Video/VideoShowRequest.php new file mode 100644 index 000000000..915c958c5 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Video/VideoShowRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Video/VideoStoreRequest.php b/app/Http/Requests/Api/Wiki/Video/VideoStoreRequest.php new file mode 100644 index 000000000..a74d34432 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Video/VideoStoreRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/Wiki/Video/VideoUpdateRequest.php b/app/Http/Requests/Api/Wiki/Video/VideoUpdateRequest.php new file mode 100644 index 000000000..ea6ad15d5 --- /dev/null +++ b/app/Http/Requests/Api/Wiki/Video/VideoUpdateRequest.php @@ -0,0 +1,37 @@ +validated()); + } +} diff --git a/app/Http/Requests/Api/WriteRequest.php b/app/Http/Requests/Api/WriteRequest.php new file mode 100644 index 000000000..8bfc6c11d --- /dev/null +++ b/app/Http/Requests/Api/WriteRequest.php @@ -0,0 +1,108 @@ +user('sanctum'); + + return $user instanceof User && $user->can($this->ability(), $this->arguments()); + } + + /** + * The policy ability to authorize. + * + * @return string + */ + abstract protected function ability(): string; + + /** + * The arguments for the policy ability to authorize. + * + * @return mixed + */ + abstract protected function arguments(): mixed; + + /** + * Prepare the data for validation. + * + * @return void + */ + protected function prepareForValidation(): void + { + parent::prepareForValidation(); + + foreach ($this->getFieldsForPreparation() as $field) { + if ($field instanceof EnumField) { + $this->convertEnumDescriptionToValue($field->getColumn(), $field->getEnumClass()); + } + if ($field instanceof BooleanField) { + $this->convertBoolean($field->getColumn()); + } + } + } + + /** + * Get fields for validation preparation. + * + * @return Field[] + */ + protected function getFieldsForPreparation(): array + { + return []; + } + + /** + * Convert enum description parameter value to enum value. + * + * @param string $attribute + * @param class-string $enumClass + * @return void + */ + protected function convertEnumDescriptionToValue(string $attribute, string $enumClass): void + { + $description = $this->input($attribute); + if (is_string($description)) { + $enumInstance = $enumClass::fromDescription($description); + if ($enumInstance !== null) { + $this->merge([ + $attribute => $enumInstance->value, + ]); + } + } + } + + /** + * Convert enum parameter values. + * + * @param string $attribute + * @return void + */ + protected function convertBoolean(string $attribute): void + { + $booleanValue = $this->input($attribute); + if (filter_var($booleanValue, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null) { + $this->merge([ + $attribute => filter_var($booleanValue, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE), + ]); + } + } +} diff --git a/app/Http/Requests/Billing/TransparencyRequest.php b/app/Http/Requests/Billing/TransparencyRequest.php new file mode 100644 index 000000000..073c36978 --- /dev/null +++ b/app/Http/Requests/Billing/TransparencyRequest.php @@ -0,0 +1,167 @@ + + */ + protected readonly Collection $validDates; + + /** + * Create a new rule instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->validDates = $this->initializeValidDates(); + } + + /** + * Determine if the user is authorized to make this request. + * + * @return bool + */ + public function authorize(): bool + { + return true; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'date' => [ + 'nullable', + 'date_format:Y-m', + new TransparencyDateRule($this->getValidDates()), + ], + ]; + } + + /** + * Initialize list of year/month combinations for transparency filtering. + * + * @return Collection + */ + protected function initializeValidDates(): Collection + { + $balanceDates = Balance::query() + ->distinct(Balance::ATTRIBUTE_DATE) + ->pluck(Balance::ATTRIBUTE_DATE); + + $transactionDates = Transaction::query() + ->distinct(Transaction::ATTRIBUTE_DATE) + ->pluck(Transaction::ATTRIBUTE_DATE); + + $validDates = $balanceDates->concat($transactionDates); + + $validDates = $validDates->unique(fn (Carbon $date) => $date->format(AllowedDateFormat::YM)); + + return $validDates->sortDesc(); + } + + /** + * Get the list of valid year/month combinations for transparency filtering. + * + * @return Collection + */ + public function getValidDates(): Collection + { + return $this->validDates; + } + + /** + * Get the validated year/month combination for the transparency filter. + * + * @return Carbon|null + */ + public function getSelectedDate(): ?Carbon + { + $validDate = Arr::get($this->validated(), 'date'); + + if ($validDate === null) { + return $this->getValidDates()->first(); + } + + return Carbon::instance(DateTime::createFromFormat('!'.AllowedDateFormat::YM, $validDate)); + } + + /** + * Get Balances for selected month. + * + * @return Collection + */ + public function getBalances(): Collection + { + $date = $this->getSelectedDate(); + + if ($date === null) { + return Collection::make(); + } + + return Balance::query() + ->select([ + Balance::ATTRIBUTE_BALANCE, + Balance::ATTRIBUTE_FREQUENCY, + Balance::ATTRIBUTE_SERVICE, + Balance::ATTRIBUTE_USAGE, + ]) + ->whereMonth(Balance::ATTRIBUTE_DATE, ComparisonOperator::EQ, $date) + ->whereYear(Balance::ATTRIBUTE_DATE, ComparisonOperator::EQ, $date) + ->orderBy(Balance::ATTRIBUTE_USAGE, Direction::DESCENDING) + ->get(); + } + + /** + * Get Transactions for selected month. + * + * @return Collection + */ + public function getTransactions(): Collection + { + $date = $this->getSelectedDate(); + + if ($date === null) { + return Collection::make(); + } + + return Transaction::query() + ->select([ + Transaction::ATTRIBUTE_AMOUNT, + Transaction::ATTRIBUTE_DATE, + Transaction::ATTRIBUTE_DESCRIPTION, + Transaction::ATTRIBUTE_SERVICE, + ]) + ->whereMonth(Transaction::ATTRIBUTE_DATE, ComparisonOperator::EQ, $date) + ->whereYear(Transaction::ATTRIBUTE_DATE, ComparisonOperator::EQ, $date) + ->orderBy(Transaction::ATTRIBUTE_DATE, Direction::DESCENDING) + ->get(); + } +} diff --git a/app/Http/Resources/Admin/Collection/AnnouncementCollection.php b/app/Http/Resources/Admin/Collection/AnnouncementCollection.php new file mode 100644 index 000000000..1204e53e0 --- /dev/null +++ b/app/Http/Resources/Admin/Collection/AnnouncementCollection.php @@ -0,0 +1,38 @@ +collection->map( + fn (Announcement $announcement) => new AnnouncementResource($announcement, $this->query) + )->all(); + } +} diff --git a/app/Http/Resources/Admin/Resource/AnnouncementResource.php b/app/Http/Resources/Admin/Resource/AnnouncementResource.php new file mode 100644 index 000000000..628268b97 --- /dev/null +++ b/app/Http/Resources/Admin/Resource/AnnouncementResource.php @@ -0,0 +1,74 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Announcement::ATTRIBUTE_CONTENT)) { + $result[Announcement::ATTRIBUTE_CONTENT] = $this->content; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + return $result; + } +} diff --git a/app/Http/Resources/BaseCollection.php b/app/Http/Resources/BaseCollection.php new file mode 100644 index 000000000..1ec3842e8 --- /dev/null +++ b/app/Http/Resources/BaseCollection.php @@ -0,0 +1,26 @@ +query->getFieldCriteria(static::$wrap); + + return $criteria === null || $criteria->isAllowedField($field); + } +} diff --git a/app/Http/Resources/Billing/Collection/BalanceCollection.php b/app/Http/Resources/Billing/Collection/BalanceCollection.php new file mode 100644 index 000000000..158780b8e --- /dev/null +++ b/app/Http/Resources/Billing/Collection/BalanceCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Balance $balance) => new BalanceResource($balance, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Billing/Collection/TransactionCollection.php b/app/Http/Resources/Billing/Collection/TransactionCollection.php new file mode 100644 index 000000000..aa3f731a4 --- /dev/null +++ b/app/Http/Resources/Billing/Collection/TransactionCollection.php @@ -0,0 +1,38 @@ +collection->map( + fn (Transaction $transaction) => new TransactionResource($transaction, $this->query) + )->all(); + } +} diff --git a/app/Http/Resources/Billing/Resource/BalanceResource.php b/app/Http/Resources/Billing/Resource/BalanceResource.php new file mode 100644 index 000000000..b33eba9f9 --- /dev/null +++ b/app/Http/Resources/Billing/Resource/BalanceResource.php @@ -0,0 +1,92 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Balance::ATTRIBUTE_DATE)) { + $result[Balance::ATTRIBUTE_DATE] = $this->date; + } + + if ($this->isAllowedField(Balance::ATTRIBUTE_SERVICE)) { + $result[Balance::ATTRIBUTE_SERVICE] = $this->service->description; + } + + if ($this->isAllowedField(Balance::ATTRIBUTE_FREQUENCY)) { + $result[Balance::ATTRIBUTE_FREQUENCY] = $this->frequency->description; + } + + if ($this->isAllowedField(Balance::ATTRIBUTE_USAGE)) { + $result[Balance::ATTRIBUTE_USAGE] = $this->usage; + } + + if ($this->isAllowedField(BalanceResource::ATTRIBUTE_BALANCE)) { + $result[BalanceResource::ATTRIBUTE_BALANCE] = $this->balance; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + return $result; + } +} diff --git a/app/Http/Resources/Billing/Resource/TransactionResource.php b/app/Http/Resources/Billing/Resource/TransactionResource.php new file mode 100644 index 000000000..badee1b02 --- /dev/null +++ b/app/Http/Resources/Billing/Resource/TransactionResource.php @@ -0,0 +1,90 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Transaction::ATTRIBUTE_DATE)) { + $result[Transaction::ATTRIBUTE_DATE] = $this->date; + } + + if ($this->isAllowedField(Transaction::ATTRIBUTE_SERVICE)) { + $result[Transaction::ATTRIBUTE_SERVICE] = $this->service->description; + } + + if ($this->isAllowedField(Transaction::ATTRIBUTE_DESCRIPTION)) { + $result[Transaction::ATTRIBUTE_DESCRIPTION] = $this->description; + } + + if ($this->isAllowedField(Transaction::ATTRIBUTE_AMOUNT)) { + $result[Transaction::ATTRIBUTE_AMOUNT] = $this->amount; + } + + if ($this->isAllowedField(Transaction::ATTRIBUTE_EXTERNAL_ID)) { + $result[Transaction::ATTRIBUTE_EXTERNAL_ID] = $this->external_id; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + return $result; + } +} diff --git a/app/Http/Resources/Config/Resource/FlagsResource.php b/app/Http/Resources/Config/Resource/FlagsResource.php new file mode 100644 index 000000000..d1346b19a --- /dev/null +++ b/app/Http/Resources/Config/Resource/FlagsResource.php @@ -0,0 +1,62 @@ +isAllowedField(FlagConstants::ALLOW_VIDEO_STREAMS_FLAG)) { + $result[FlagConstants::ALLOW_VIDEO_STREAMS_FLAG] = config(FlagConstants::ALLOW_VIDEO_STREAMS_FLAG_QUALIFIED, false); + } + + if ($this->isAllowedField(FlagConstants::ALLOW_DISCORD_NOTIFICATIONS_FLAG)) { + $result[FlagConstants::ALLOW_DISCORD_NOTIFICATIONS_FLAG] = config(FlagConstants::ALLOW_DISCORD_NOTIFICATIONS_FLAG_QUALIFIED, false); + } + + if ($this->isAllowedField(FlagConstants::ALLOW_VIEW_RECORDING_FLAG)) { + $result[FlagConstants::ALLOW_VIEW_RECORDING_FLAG] = config(FlagConstants::ALLOW_VIEW_RECORDING_FLAG_QUALIFIED, false); + } + + return $result; + } +} diff --git a/app/Http/Resources/Config/Resource/WikiResource.php b/app/Http/Resources/Config/Resource/WikiResource.php new file mode 100644 index 000000000..6a392f3a1 --- /dev/null +++ b/app/Http/Resources/Config/Resource/WikiResource.php @@ -0,0 +1,70 @@ +isAllowedField(WikiConstants::FEATURED_THEME_SETTING)) { + /** @var AnimeThemeEntryVideo|null */ + $pivot = AnimeThemeEntryVideo::query() + ->where(AnimeThemeEntryVideo::ATTRIBUTE_ENTRY, Config::get('wiki.featured_entry')) + ->where(AnimeThemeEntryVideo::ATTRIBUTE_VIDEO, Config::get('wiki.featured_video')) + ->with([ + AnimeThemeEntryVideo::RELATION_ANIME, + AnimeThemeEntryVideo::RELATION_ARTISTS, + AnimeThemeEntryVideo::RELATION_IMAGES, + AnimeThemeEntryVideo::RELATION_SONG, + AnimeThemeEntryVideo::RELATION_VIDEO, + ]) + ->first(); + + $result[WikiConstants::FEATURED_THEME_SETTING] = new AnimeThemeEntryVideoResource($pivot, $this->query); + } + + return $result; + } +} diff --git a/app/Http/Resources/Document/Collection/PageCollection.php b/app/Http/Resources/Document/Collection/PageCollection.php new file mode 100644 index 000000000..772cde5a1 --- /dev/null +++ b/app/Http/Resources/Document/Collection/PageCollection.php @@ -0,0 +1,38 @@ +collection->map( + fn (Page $page) => new PageResource($page, $this->query) + )->all(); + } +} diff --git a/app/Http/Resources/Document/Resource/PageResource.php b/app/Http/Resources/Document/Resource/PageResource.php new file mode 100644 index 000000000..1ef2e085c --- /dev/null +++ b/app/Http/Resources/Document/Resource/PageResource.php @@ -0,0 +1,82 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Page::ATTRIBUTE_NAME)) { + $result[Page::ATTRIBUTE_NAME] = $this->name; + } + + if ($this->isAllowedField(Page::ATTRIBUTE_SLUG)) { + $result[Page::ATTRIBUTE_SLUG] = $this->slug; + } + + if ($this->isAllowedField(Page::ATTRIBUTE_BODY)) { + $result[Page::ATTRIBUTE_BODY] = $this->body; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + return $result; + } +} diff --git a/app/Http/Resources/Pivot/Resource/AnimeThemeEntryVideoResource.php b/app/Http/Resources/Pivot/Resource/AnimeThemeEntryVideoResource.php new file mode 100644 index 000000000..ae87faf66 --- /dev/null +++ b/app/Http/Resources/Pivot/Resource/AnimeThemeEntryVideoResource.php @@ -0,0 +1,67 @@ +isAllowedField(BasePivot::ATTRIBUTE_CREATED_AT)) { + $result[BasePivot::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BasePivot::ATTRIBUTE_UPDATED_AT)) { + $result[BasePivot::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + $result[AnimeThemeEntryVideo::RELATION_ENTRY] = new EntryResource($this->whenLoaded(AnimeThemeEntryVideo::RELATION_ENTRY), $this->query); + $result[AnimeThemeEntryVideo::RELATION_VIDEO] = new VideoResource($this->whenLoaded(AnimeThemeEntryVideo::RELATION_VIDEO), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Anime/Collection/SynonymCollection.php b/app/Http/Resources/Wiki/Anime/Collection/SynonymCollection.php new file mode 100644 index 000000000..020374ddb --- /dev/null +++ b/app/Http/Resources/Wiki/Anime/Collection/SynonymCollection.php @@ -0,0 +1,38 @@ +collection->map( + fn (AnimeSynonym $synonym) => new SynonymResource($synonym, $this->query) + )->all(); + } +} diff --git a/app/Http/Resources/Wiki/Anime/Collection/ThemeCollection.php b/app/Http/Resources/Wiki/Anime/Collection/ThemeCollection.php new file mode 100644 index 000000000..6f1d5d42f --- /dev/null +++ b/app/Http/Resources/Wiki/Anime/Collection/ThemeCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (AnimeTheme $theme) => new ThemeResource($theme, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Anime/Resource/SynonymResource.php b/app/Http/Resources/Wiki/Anime/Resource/SynonymResource.php new file mode 100644 index 000000000..213651da2 --- /dev/null +++ b/app/Http/Resources/Wiki/Anime/Resource/SynonymResource.php @@ -0,0 +1,77 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(AnimeSynonym::ATTRIBUTE_TEXT)) { + $result[AnimeSynonym::ATTRIBUTE_TEXT] = $this->text; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[AnimeSynonym::RELATION_ANIME] = new AnimeResource($this->whenLoaded(AnimeSynonym::RELATION_ANIME), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Anime/Resource/ThemeResource.php b/app/Http/Resources/Wiki/Anime/Resource/ThemeResource.php new file mode 100644 index 000000000..4c7739da4 --- /dev/null +++ b/app/Http/Resources/Wiki/Anime/Resource/ThemeResource.php @@ -0,0 +1,93 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(AnimeTheme::ATTRIBUTE_TYPE)) { + $result[AnimeTheme::ATTRIBUTE_TYPE] = $this->type?->description; + } + + if ($this->isAllowedField(AnimeTheme::ATTRIBUTE_SEQUENCE)) { + $result[AnimeTheme::ATTRIBUTE_SEQUENCE] = $this->sequence; + } + + if ($this->isAllowedField(AnimeTheme::ATTRIBUTE_GROUP)) { + $result[AnimeTheme::ATTRIBUTE_GROUP] = $this->group; + } + + if ($this->isAllowedField(AnimeTheme::ATTRIBUTE_SLUG)) { + $result[AnimeTheme::ATTRIBUTE_SLUG] = $this->slug; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[AnimeTheme::RELATION_ANIME] = new AnimeResource($this->whenLoaded(AnimeTheme::RELATION_ANIME), $this->query); + $result[AnimeTheme::RELATION_SONG] = new SongResource($this->whenLoaded(AnimeTheme::RELATION_SONG), $this->query); + $result[AnimeTheme::RELATION_ENTRIES] = new EntryCollection($this->whenLoaded(AnimeTheme::RELATION_ENTRIES), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Anime/Theme/Collection/EntryCollection.php b/app/Http/Resources/Wiki/Anime/Theme/Collection/EntryCollection.php new file mode 100644 index 000000000..5b3d57573 --- /dev/null +++ b/app/Http/Resources/Wiki/Anime/Theme/Collection/EntryCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (AnimeThemeEntry $entry) => new EntryResource($entry, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Anime/Theme/Resource/EntryResource.php b/app/Http/Resources/Wiki/Anime/Theme/Resource/EntryResource.php new file mode 100644 index 000000000..e57382748 --- /dev/null +++ b/app/Http/Resources/Wiki/Anime/Theme/Resource/EntryResource.php @@ -0,0 +1,95 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(AnimeThemeEntry::ATTRIBUTE_VERSION)) { + $result[AnimeThemeEntry::ATTRIBUTE_VERSION] = $this->version; + } + + if ($this->isAllowedField(AnimeThemeEntry::ATTRIBUTE_EPISODES)) { + $result[AnimeThemeEntry::ATTRIBUTE_EPISODES] = $this->episodes; + } + + if ($this->isAllowedField(AnimeThemeEntry::ATTRIBUTE_NSFW)) { + $result[AnimeThemeEntry::ATTRIBUTE_NSFW] = $this->nsfw; + } + + if ($this->isAllowedField(AnimeThemeEntry::ATTRIBUTE_SPOILER)) { + $result[AnimeThemeEntry::ATTRIBUTE_SPOILER] = $this->spoiler; + } + + if ($this->isAllowedField(AnimeThemeEntry::ATTRIBUTE_NOTES)) { + $result[AnimeThemeEntry::ATTRIBUTE_NOTES] = $this->notes; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[AnimeThemeEntry::RELATION_THEME] = new ThemeResource($this->whenLoaded(AnimeThemeEntry::RELATION_THEME), $this->query); + $result[AnimeThemeEntry::RELATION_VIDEOS] = new VideoCollection($this->whenLoaded(AnimeThemeEntry::RELATION_VIDEOS), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Collection/AnimeCollection.php b/app/Http/Resources/Wiki/Collection/AnimeCollection.php new file mode 100644 index 000000000..edf33bec4 --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/AnimeCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Anime $anime) => new AnimeResource($anime, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Collection/ArtistCollection.php b/app/Http/Resources/Wiki/Collection/ArtistCollection.php new file mode 100644 index 000000000..81a1f12fc --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/ArtistCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Artist $artist) => new ArtistResource($artist, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Collection/ExternalResourceCollection.php b/app/Http/Resources/Wiki/Collection/ExternalResourceCollection.php new file mode 100644 index 000000000..3f98f1e9c --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/ExternalResourceCollection.php @@ -0,0 +1,38 @@ +collection->map( + fn (ExternalResource $resource) => new ExternalResourceResource($resource, $this->query) + )->all(); + } +} diff --git a/app/Http/Resources/Wiki/Collection/ImageCollection.php b/app/Http/Resources/Wiki/Collection/ImageCollection.php new file mode 100644 index 000000000..d3989adae --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/ImageCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Image $image) => new ImageResource($image, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Collection/SeriesCollection.php b/app/Http/Resources/Wiki/Collection/SeriesCollection.php new file mode 100644 index 000000000..3b76a99ba --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/SeriesCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Series $series) => new SeriesResource($series, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Collection/SongCollection.php b/app/Http/Resources/Wiki/Collection/SongCollection.php new file mode 100644 index 000000000..1a4925bcc --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/SongCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Song $song) => new SongResource($song, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Collection/StudioCollection.php b/app/Http/Resources/Wiki/Collection/StudioCollection.php new file mode 100644 index 000000000..fbdc0131f --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/StudioCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Studio $studio) => new StudioResource($studio, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Collection/VideoCollection.php b/app/Http/Resources/Wiki/Collection/VideoCollection.php new file mode 100644 index 000000000..1b08a4b85 --- /dev/null +++ b/app/Http/Resources/Wiki/Collection/VideoCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (Video $video) => new VideoResource($video, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Wiki/Resource/AnimeResource.php b/app/Http/Resources/Wiki/Resource/AnimeResource.php new file mode 100644 index 000000000..de1204f1f --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/AnimeResource.php @@ -0,0 +1,109 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Anime::ATTRIBUTE_NAME)) { + $result[Anime::ATTRIBUTE_NAME] = $this->name; + } + + if ($this->isAllowedField(Anime::ATTRIBUTE_SLUG)) { + $result[Anime::ATTRIBUTE_SLUG] = $this->slug; + } + + if ($this->isAllowedField(Anime::ATTRIBUTE_YEAR)) { + $result[Anime::ATTRIBUTE_YEAR] = $this->year; + } + + if ($this->isAllowedField(Anime::ATTRIBUTE_SEASON)) { + $result[Anime::ATTRIBUTE_SEASON] = $this->season?->description; + } + + if ($this->isAllowedField(Anime::ATTRIBUTE_SYNOPSIS)) { + $result[Anime::ATTRIBUTE_SYNOPSIS] = $this->synopsis; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[Anime::RELATION_SYNONYMS] = new SynonymCollection($this->whenLoaded(Anime::RELATION_SYNONYMS), $this->query); + $result[Anime::RELATION_THEMES] = new ThemeCollection($this->whenLoaded(Anime::RELATION_THEMES), $this->query); + $result[Anime::RELATION_SERIES] = new SeriesCollection($this->whenLoaded(Anime::RELATION_SERIES), $this->query); + $result[Anime::RELATION_RESOURCES] = new ExternalResourceCollection($this->whenLoaded(Anime::RELATION_RESOURCES), $this->query); + + if ($this->isAllowedField(AnimeResourcePivot::ATTRIBUTE_AS)) { + $result[AnimeResourcePivot::ATTRIBUTE_AS] = $this->whenPivotLoaded(AnimeResourcePivot::TABLE, fn () => $this->pivot->getAttribute(AnimeResourcePivot::ATTRIBUTE_AS)); + } + + $result[Anime::RELATION_IMAGES] = new ImageCollection($this->whenLoaded(Anime::RELATION_IMAGES), $this->query); + $result[Anime::RELATION_STUDIOS] = new StudioCollection($this->whenLoaded(Anime::RELATION_STUDIOS), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Resource/ArtistResource.php b/app/Http/Resources/Wiki/Resource/ArtistResource.php new file mode 100644 index 000000000..afaf4368a --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/ArtistResource.php @@ -0,0 +1,106 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Artist::ATTRIBUTE_NAME)) { + $result[Artist::ATTRIBUTE_NAME] = $this->name; + } + + if ($this->isAllowedField(Artist::ATTRIBUTE_SLUG)) { + $result[Artist::ATTRIBUTE_SLUG] = $this->slug; + } + + if ($this->isAllowedField(ArtistSong::ATTRIBUTE_AS)) { + $result[ArtistSong::ATTRIBUTE_AS] = $this->whenPivotLoaded( + ArtistSong::TABLE, + fn () => $this->pivot->getAttribute(ArtistSong::ATTRIBUTE_AS), + $this->whenPivotLoaded( + ArtistMember::TABLE, + fn () => $this->pivot->getAttribute(ArtistMember::ATTRIBUTE_AS), + $this->whenPivotLoaded( + ArtistResourcePivot::TABLE, + fn () => $this->pivot->getAttribute(ArtistResourcePivot::ATTRIBUTE_AS) + ) + ) + ); + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[Artist::RELATION_SONGS] = new SongCollection($this->whenLoaded(Artist::RELATION_SONGS), $this->query); + $result[Artist::RELATION_MEMBERS] = new ArtistCollection($this->whenLoaded(Artist::RELATION_MEMBERS), $this->query); + $result[Artist::RELATION_GROUPS] = new ArtistCollection($this->whenLoaded(Artist::RELATION_GROUPS), $this->query); + $result[Artist::RELATION_RESOURCES] = new ExternalResourceCollection($this->whenLoaded(Artist::RELATION_RESOURCES), $this->query); + $result[Artist::RELATION_IMAGES] = new ImageCollection($this->whenLoaded(Artist::RELATION_IMAGES), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Resource/ExternalResourceResource.php b/app/Http/Resources/Wiki/Resource/ExternalResourceResource.php new file mode 100644 index 000000000..24b73af5f --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/ExternalResourceResource.php @@ -0,0 +1,107 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(ExternalResource::ATTRIBUTE_LINK)) { + $result[ExternalResource::ATTRIBUTE_LINK] = $this->link; + } + + if ($this->isAllowedField(ExternalResource::ATTRIBUTE_EXTERNAL_ID)) { + $result[ExternalResource::ATTRIBUTE_EXTERNAL_ID] = $this->external_id; + } + + if ($this->isAllowedField(ExternalResource::ATTRIBUTE_SITE)) { + $result[ExternalResource::ATTRIBUTE_SITE] = $this->site?->description; + } + + if ($this->isAllowedField(AnimeResourcePivot::ATTRIBUTE_AS)) { + $result[AnimeResourcePivot::ATTRIBUTE_AS] = $this->whenPivotLoaded( + AnimeResourcePivot::TABLE, + fn () => $this->pivot->getAttribute(AnimeResourcePivot::ATTRIBUTE_AS), + $this->whenPivotLoaded( + ArtistResourcePivot::TABLE, + fn () => $this->pivot->getAttribute(ArtistResourcePivot::ATTRIBUTE_AS), + $this->whenPivotLoaded( + StudioResourcePivot::TABLE, + fn () => $this->pivot->getAttribute(StudioResourcePivot::ATTRIBUTE_AS) + ) + ) + ); + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[ExternalResource::RELATION_ARTISTS] = new ArtistCollection($this->whenLoaded(ExternalResource::RELATION_ARTISTS), $this->query); + $result[ExternalResource::RELATION_ANIME] = new AnimeCollection($this->whenLoaded(ExternalResource::RELATION_ANIME), $this->query); + $result[ExternalResource::RELATION_STUDIOS] = new StudioCollection($this->whenLoaded(ExternalResource::RELATION_STUDIOS), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Resource/ImageResource.php b/app/Http/Resources/Wiki/Resource/ImageResource.php new file mode 100644 index 000000000..1315cf984 --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/ImageResource.php @@ -0,0 +1,90 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Image::ATTRIBUTE_PATH)) { + $result[Image::ATTRIBUTE_PATH] = $this->path; + } + + if ($this->isAllowedField(Image::ATTRIBUTE_FACET)) { + $result[Image::ATTRIBUTE_FACET] = $this->facet?->description; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + if ($this->isAllowedField(ImageResource::ATTRIBUTE_LINK)) { + $result[ImageResource::ATTRIBUTE_LINK] = Storage::disk('images')->url($this->path); + } + + $result[Image::RELATION_ARTISTS] = new ArtistCollection($this->whenLoaded(Image::RELATION_ARTISTS), $this->query); + $result[Image::RELATION_ANIME] = new AnimeCollection($this->whenLoaded(Image::RELATION_ANIME), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Resource/SearchResource.php b/app/Http/Resources/Wiki/Resource/SearchResource.php new file mode 100644 index 000000000..ed8dd00f8 --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/SearchResource.php @@ -0,0 +1,127 @@ +isAllowedField(AnimeCollection::$wrap)) { + $animeQuery = $this->query->getQuery(AnimeReadQuery::class); + if ($animeQuery !== null) { + $result[AnimeCollection::$wrap] = $animeQuery->search(PaginationStrategy::LIMIT()); + } + } + + if ($this->isAllowedField(ThemeCollection::$wrap)) { + $themeQuery = $this->query->getQuery(ThemeReadQuery::class); + if ($themeQuery !== null) { + $result[ThemeCollection::$wrap] = $themeQuery->search(PaginationStrategy::LIMIT()); + } + } + + if ($this->isAllowedField(ArtistCollection::$wrap)) { + $artistQuery = $this->query->getQuery(ArtistReadQuery::class); + if ($artistQuery !== null) { + $result[ArtistCollection::$wrap] = $artistQuery->search(PaginationStrategy::LIMIT()); + } + } + + if ($this->isAllowedField(SeriesCollection::$wrap)) { + $seriesQuery = $this->query->getQuery(SeriesReadQuery::class); + if ($seriesQuery !== null) { + $result[SeriesCollection::$wrap] = $seriesQuery->search(PaginationStrategy::LIMIT()); + } + } + + if ($this->isAllowedField(SongCollection::$wrap)) { + $songQuery = $this->query->getQuery(SongReadQuery::class); + if ($songQuery !== null) { + $result[SongCollection::$wrap] = $songQuery->search(PaginationStrategy::LIMIT()); + } + } + + if ($this->isAllowedField(StudioCollection::$wrap)) { + $studioQuery = $this->query->getQuery(StudioReadQuery::class); + if ($studioQuery !== null) { + $result[StudioCollection::$wrap] = $studioQuery->search(PaginationStrategy::LIMIT()); + } + } + + if ($this->isAllowedField(VideoCollection::$wrap)) { + $videoQuery = $this->query->getQuery(VideoReadQuery::class); + if ($videoQuery !== null) { + $result[VideoCollection::$wrap] = $videoQuery->search(PaginationStrategy::LIMIT()); + } + } + + return $result; + } + + /** + * Determine if field should be included in the response for this resource. + * + * @param string $field + * @return bool + */ + protected function isAllowedField(string $field): bool + { + $criteria = $this->query->getFieldCriteria(static::$wrap); + + return $criteria === null || $criteria->isAllowedField($field); + } +} diff --git a/app/Http/Resources/Wiki/Resource/SeriesResource.php b/app/Http/Resources/Wiki/Resource/SeriesResource.php new file mode 100644 index 000000000..f5d497138 --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/SeriesResource.php @@ -0,0 +1,81 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Series::ATTRIBUTE_NAME)) { + $result[Series::ATTRIBUTE_NAME] = $this->name; + } + + if ($this->isAllowedField(Series::ATTRIBUTE_SLUG)) { + $result[Series::ATTRIBUTE_SLUG] = $this->slug; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[Series::RELATION_ANIME] = new AnimeCollection($this->whenLoaded(Series::RELATION_ANIME), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Resource/SongResource.php b/app/Http/Resources/Wiki/Resource/SongResource.php new file mode 100644 index 000000000..d3e65af3a --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/SongResource.php @@ -0,0 +1,84 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Song::ATTRIBUTE_TITLE)) { + $result[Song::ATTRIBUTE_TITLE] = $this->title; + } + + if ($this->isAllowedField(ArtistSong::ATTRIBUTE_AS)) { + $result[ArtistSong::ATTRIBUTE_AS] = $this->whenPivotLoaded(ArtistSong::TABLE, fn () => $this->pivot->getAttribute(ArtistSong::ATTRIBUTE_AS)); + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[Song::RELATION_ANIMETHEMES] = new ThemeCollection($this->whenLoaded(Song::RELATION_ANIMETHEMES), $this->query); + $result[Song::RELATION_ARTISTS] = new ArtistCollection($this->whenLoaded(Song::RELATION_ARTISTS), $this->query); + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Resource/StudioResource.php b/app/Http/Resources/Wiki/Resource/StudioResource.php new file mode 100644 index 000000000..b1ce3ff82 --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/StudioResource.php @@ -0,0 +1,88 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Studio::ATTRIBUTE_NAME)) { + $result[Studio::ATTRIBUTE_NAME] = $this->name; + } + + if ($this->isAllowedField(Studio::ATTRIBUTE_SLUG)) { + $result[Studio::ATTRIBUTE_SLUG] = $this->slug; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + $result[Studio::RELATION_ANIME] = new AnimeCollection($this->whenLoaded(Studio::RELATION_ANIME), $this->query); + $result[Studio::RELATION_RESOURCES] = new ExternalResourceCollection($this->whenLoaded(Studio::RELATION_RESOURCES), $this->query); + + if ($this->isAllowedField(StudioResourcePivot::ATTRIBUTE_AS)) { + $result[StudioResourcePivot::ATTRIBUTE_AS] = $this->whenPivotLoaded(StudioResourcePivot::TABLE, fn () => $this->pivot->getAttribute(StudioResourcePivot::ATTRIBUTE_AS)); + } + + return $result; + } +} diff --git a/app/Http/Resources/Wiki/Resource/VideoResource.php b/app/Http/Resources/Wiki/Resource/VideoResource.php new file mode 100644 index 000000000..d424c2969 --- /dev/null +++ b/app/Http/Resources/Wiki/Resource/VideoResource.php @@ -0,0 +1,131 @@ +isAllowedField(BaseResource::ATTRIBUTE_ID)) { + $result[BaseResource::ATTRIBUTE_ID] = $this->getKey(); + } + + if ($this->isAllowedField(Video::ATTRIBUTE_BASENAME)) { + $result[Video::ATTRIBUTE_BASENAME] = $this->basename; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_FILENAME)) { + $result[Video::ATTRIBUTE_FILENAME] = $this->filename; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_PATH)) { + $result[Video::ATTRIBUTE_PATH] = $this->path; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_SIZE)) { + $result[Video::ATTRIBUTE_SIZE] = $this->size; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_MIMETYPE)) { + $result[Video::ATTRIBUTE_MIMETYPE] = $this->mimetype; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_RESOLUTION)) { + $result[Video::ATTRIBUTE_RESOLUTION] = $this->resolution; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_NC)) { + $result[Video::ATTRIBUTE_NC] = $this->nc; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_SUBBED)) { + $result[Video::ATTRIBUTE_SUBBED] = $this->subbed; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_LYRICS)) { + $result[Video::ATTRIBUTE_LYRICS] = $this->lyrics; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_UNCEN)) { + $result[Video::ATTRIBUTE_UNCEN] = $this->uncen; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_SOURCE)) { + $result[Video::ATTRIBUTE_SOURCE] = $this->source?->description; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_OVERLAP)) { + $result[Video::ATTRIBUTE_OVERLAP] = $this->overlap->description; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_CREATED_AT)) { + $result[BaseModel::ATTRIBUTE_CREATED_AT] = $this->created_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_UPDATED_AT)) { + $result[BaseModel::ATTRIBUTE_UPDATED_AT] = $this->updated_at; + } + + if ($this->isAllowedField(BaseModel::ATTRIBUTE_DELETED_AT)) { + $result[BaseModel::ATTRIBUTE_DELETED_AT] = $this->deleted_at; + } + + if ($this->isAllowedField(Video::ATTRIBUTE_TAGS)) { + $result[Video::ATTRIBUTE_TAGS] = implode('', $this->tags); + } + + if ($this->isAllowedField(VideoResource::ATTRIBUTE_LINK)) { + $result[VideoResource::ATTRIBUTE_LINK] = route('video.show', $this); + } + + $result[Video::RELATION_ANIMETHEMEENTRIES] = new EntryCollection($this->whenLoaded(Video::RELATION_ANIMETHEMEENTRIES), $this->query); + + return $result; + } +} diff --git a/app/Jobs/Middleware/RateLimited.php b/app/Jobs/Middleware/RateLimited.php new file mode 100644 index 000000000..0a0ecf736 --- /dev/null +++ b/app/Jobs/Middleware/RateLimited.php @@ -0,0 +1,48 @@ +appUsesRedis()) { + Redis::throttle('key') + ->block(0) + ->allow(1) + ->every(15) + ->then( + function () use ($job, $next) { + // Lock obtained... + $next($job); + }, + function () use ($job) { + // Could not obtain lock... + $job->release(5); + } + ); + } else { + $next($job); + } + } +} diff --git a/app/Jobs/SendDiscordNotificationJob.php b/app/Jobs/SendDiscordNotificationJob.php new file mode 100644 index 000000000..fc7271776 --- /dev/null +++ b/app/Jobs/SendDiscordNotificationJob.php @@ -0,0 +1,71 @@ +event->getDiscordChannel()) + ->notify(new DiscordNotification($this->event->getDiscordMessage())); + } + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [new RateLimited()]; + } + + /** + * Determine the time at which the job should time out. + * + * @return DateTime + */ + public function retryUntil(): DateTime + { + return now()->addMinutes(15); + } +} diff --git a/app/Listeners/Auth/Invitation/SendInvitationMail.php b/app/Listeners/Auth/Invitation/SendInvitationMail.php new file mode 100644 index 000000000..b50c34d5b --- /dev/null +++ b/app/Listeners/Auth/Invitation/SendInvitationMail.php @@ -0,0 +1,32 @@ +getModel(); + + Mail::to($invitation->email)->queue(new InvitationMail($invitation)); + } +} diff --git a/app/Listeners/Auth/LogAttempting.php b/app/Listeners/Auth/LogAttempting.php new file mode 100644 index 000000000..a2f768575 --- /dev/null +++ b/app/Listeners/Auth/LogAttempting.php @@ -0,0 +1,30 @@ + $event->guard, + 'remember' => $event->remember, + 'credentials' => Arr::except($event->credentials, 'password'), + ]); + } +} diff --git a/app/Listeners/Auth/LogCurrentDeviceLogout.php b/app/Listeners/Auth/LogCurrentDeviceLogout.php new file mode 100644 index 000000000..d3f194910 --- /dev/null +++ b/app/Listeners/Auth/LogCurrentDeviceLogout.php @@ -0,0 +1,28 @@ + $event->guard, + 'user' => $event->user->getAuthIdentifier(), + ]); + } +} diff --git a/app/Listeners/Auth/LogFailed.php b/app/Listeners/Auth/LogFailed.php new file mode 100644 index 000000000..2f58051c9 --- /dev/null +++ b/app/Listeners/Auth/LogFailed.php @@ -0,0 +1,30 @@ + $event->guard, + 'user' => $event->user?->getAuthIdentifier(), + 'credentials' => Arr::except($event->credentials, 'password'), + ]); + } +} diff --git a/app/Listeners/Auth/LogLockout.php b/app/Listeners/Auth/LogLockout.php new file mode 100644 index 000000000..0bf531591 --- /dev/null +++ b/app/Listeners/Auth/LogLockout.php @@ -0,0 +1,27 @@ + $event->guard, + 'remember' => $event->remember, + 'user' => $event->user->getAuthIdentifier(), + ]); + } +} diff --git a/app/Listeners/Auth/LogLogout.php b/app/Listeners/Auth/LogLogout.php new file mode 100644 index 000000000..873824da3 --- /dev/null +++ b/app/Listeners/Auth/LogLogout.php @@ -0,0 +1,28 @@ + $event->guard, + 'user' => $event->user->getAuthIdentifier(), + ]); + } +} diff --git a/app/Listeners/Auth/LogOtherDeviceLogout.php b/app/Listeners/Auth/LogOtherDeviceLogout.php new file mode 100644 index 000000000..b77190b16 --- /dev/null +++ b/app/Listeners/Auth/LogOtherDeviceLogout.php @@ -0,0 +1,28 @@ + $event->guard, + 'user' => $event->user->getAuthIdentifier(), + ]); + } +} diff --git a/app/Listeners/Auth/LogPasswordReset.php b/app/Listeners/Auth/LogPasswordReset.php new file mode 100644 index 000000000..db936562f --- /dev/null +++ b/app/Listeners/Auth/LogPasswordReset.php @@ -0,0 +1,27 @@ + $event->user->getAuthIdentifier(), + ]); + } +} diff --git a/app/Listeners/Auth/LogRegistered.php b/app/Listeners/Auth/LogRegistered.php new file mode 100644 index 000000000..6a5220014 --- /dev/null +++ b/app/Listeners/Auth/LogRegistered.php @@ -0,0 +1,27 @@ + $event->user->getAuthIdentifier(), + ]); + } +} diff --git a/app/Listeners/CascadesDeletes.php b/app/Listeners/CascadesDeletes.php new file mode 100644 index 000000000..9e39faf6c --- /dev/null +++ b/app/Listeners/CascadesDeletes.php @@ -0,0 +1,24 @@ +cascadeDeletes(); + } +} diff --git a/app/Listeners/CascadesRestores.php b/app/Listeners/CascadesRestores.php new file mode 100644 index 000000000..324f870ca --- /dev/null +++ b/app/Listeners/CascadesRestores.php @@ -0,0 +1,24 @@ +cascadeRestores(); + } +} diff --git a/app/Listeners/NotifiesNovaUsers.php b/app/Listeners/NotifiesNovaUsers.php new file mode 100644 index 000000000..e4dbbcd14 --- /dev/null +++ b/app/Listeners/NotifiesNovaUsers.php @@ -0,0 +1,33 @@ +shouldSend()) { + $notification = $event->getNotification(); + foreach ($event->getUsers() as $user) { + $user->notify($notification); + } + } + } +} diff --git a/app/Listeners/SendDiscordNotification.php b/app/Listeners/SendDiscordNotification.php new file mode 100644 index 000000000..2c876997e --- /dev/null +++ b/app/Listeners/SendDiscordNotification.php @@ -0,0 +1,32 @@ +updateRelatedIndices(); + } +} diff --git a/app/Listeners/Wiki/Anime/Theme/CreateThemeSlug.php b/app/Listeners/Wiki/Anime/Theme/CreateThemeSlug.php new file mode 100644 index 000000000..5f8a6ee07 --- /dev/null +++ b/app/Listeners/Wiki/Anime/Theme/CreateThemeSlug.php @@ -0,0 +1,30 @@ +getModel(); + + $slug = $theme->type?->key; + if (! empty($theme->sequence)) { + $slug .= $theme->sequence; + } + $theme->slug = $slug; + } +} diff --git a/app/Listeners/Wiki/Image/RemoveImageFromStorage.php b/app/Listeners/Wiki/Image/RemoveImageFromStorage.php new file mode 100644 index 000000000..4c624ff9d --- /dev/null +++ b/app/Listeners/Wiki/Image/RemoveImageFromStorage.php @@ -0,0 +1,33 @@ +getModel(); + + if ($image->isForceDeleting()) { + Storage::disk('images')->delete($image->path); + } + } +} diff --git a/app/Listeners/Wiki/Video/InitializeVideoTags.php b/app/Listeners/Wiki/Video/InitializeVideoTags.php new file mode 100644 index 000000000..3b5918950 --- /dev/null +++ b/app/Listeners/Wiki/Video/InitializeVideoTags.php @@ -0,0 +1,68 @@ +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()); + } + } +} diff --git a/app/Mail/InvitationMail.php b/app/Mail/InvitationMail.php new file mode 100644 index 000000000..7925d5339 --- /dev/null +++ b/app/Mail/InvitationMail.php @@ -0,0 +1,48 @@ +addMinutes(30), + ['invitation' => $this->invitation] + ); + + return $this->subject(__('nova.invitation_subject')) + ->markdown('mail.invitation') + ->with('url', $url); + } +} diff --git a/app/Models/Admin/Announcement.php b/app/Models/Admin/Announcement.php new file mode 100644 index 000000000..243e621c8 --- /dev/null +++ b/app/Models/Admin/Announcement.php @@ -0,0 +1,78 @@ + AnnouncementCreated::class, + 'deleted' => AnnouncementDeleted::class, + 'restored' => AnnouncementRestored::class, + 'updated' => AnnouncementUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Announcement::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Announcement::ATTRIBUTE_ID; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return strval($this->getKey()); + } +} diff --git a/app/Models/Announcement.php b/app/Models/Announcement.php deleted file mode 100644 index cfe481768..000000000 --- a/app/Models/Announcement.php +++ /dev/null @@ -1,11 +0,0 @@ - InvitationCreated::class, + 'deleted' => InvitationDeleted::class, + 'restored' => InvitationRestored::class, + 'updated' => InvitationUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Invitation::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Invitation::ATTRIBUTE_ID; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + Invitation::ATTRIBUTE_STATUS => InvitationStatus::class, + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Is invitation open? + * + * @return bool + */ + public function isOpen(): bool + { + return $this->status->is(InvitationStatus::OPEN); + } +} diff --git a/app/Models/Auth/Permission.php b/app/Models/Auth/Permission.php new file mode 100644 index 000000000..0e21326fc --- /dev/null +++ b/app/Models/Auth/Permission.php @@ -0,0 +1,34 @@ + $tokens + * @property string|null $two_factor_recovery_codes + * @property string|null $two_factor_secret + * @property Carbon $updated_at + * + * @method static UserFactory factory(...$parameters) + */ +class User extends Authenticatable implements MustVerifyEmail, Nameable +{ + use Actionable; + use HasApiTokens; + use HasFactory; + use HasRoles; + use Impersonatable; + use Notifiable; + use SoftDeletes; + use TwoFactorAuthenticatable; + + final public const TABLE = 'users'; + + final public const ATTRIBUTE_EMAIL = 'email'; + final public const ATTRIBUTE_DELETED_AT = 'deleted_at'; + final public const ATTRIBUTE_EMAIL_VERIFIED_AT = 'email_verified_at'; + final public const ATTRIBUTE_ID = 'id'; + final public const ATTRIBUTE_NAME = 'name'; + final public const ATTRIBUTE_PASSWORD = 'password'; + final public const ATTRIBUTE_REMEMBER_TOKEN = 'remember_token'; + final public const ATTRIBUTE_TWO_FACTOR_RECOVERY_CODES = 'two_factor_recovery_codes'; + final public const ATTRIBUTE_TWO_FACTOR_SECRET = 'two_factor_secret'; + + final public const RELATION_PERMISSIONS = 'permissions'; + final public const RELATION_ROLES = 'roles'; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + User::ATTRIBUTE_EMAIL, + User::ATTRIBUTE_NAME, + User::ATTRIBUTE_PASSWORD, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => UserCreated::class, + 'deleted' => UserDeleted::class, + 'restored' => UserRestored::class, + 'updated' => UserUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = User::TABLE; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + User::ATTRIBUTE_PASSWORD, + User::ATTRIBUTE_REMEMBER_TOKEN, + User::ATTRIBUTE_TWO_FACTOR_RECOVERY_CODES, + User::ATTRIBUTE_TWO_FACTOR_SECRET, + ]; + + /** + * The storage format of the model's date columns. + * + * @var string + */ + protected $dateFormat = 'Y-m-d\TH:i:s.u'; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + User::ATTRIBUTE_EMAIL_VERIFIED_AT => 'datetime', + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Restore a soft-deleted model instance. + * + * @return bool|null + */ + public function restore(): ?bool + { + // If the restoring event does not return false, we will proceed with this + // restore operation. Otherwise, we bail out so the developer will stop + // the restore totally. We will clear the deleted timestamp and save. + if ($this->fireModelEvent('restoring') === false) { + return false; + } + + $this->{$this->getDeletedAtColumn()} = null; + + // Once we have saved the model, we will fire the "restored" event so this + // developer will do anything they need to after a restore operation is + // totally finished. Then we will return the result of the save call. + $this->exists = true; + + // Save quietly so that we do not fire an updated event on restore + $result = $this->saveQuietly(); + + $this->fireModelEvent('restored', false); + + return $result; + } +} diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php new file mode 100644 index 000000000..3abf42078 --- /dev/null +++ b/app/Models/BaseModel.php @@ -0,0 +1,97 @@ +newQuery()->where($field ?? $this->getRouteKeyName(), $value) + ->withoutGlobalScope(SoftDeletingScope::class) + ->first(); + } + + /** + * Restore a soft-deleted model instance. + * + * @return bool|null + */ + public function restore(): ?bool + { + // If the restoring event does not return false, we will proceed with this + // restore operation. Otherwise, we bail out so the developer will stop + // the restore totally. We will clear the deleted timestamp and save. + if ($this->fireModelEvent('restoring') === false) { + return false; + } + + $this->{$this->getDeletedAtColumn()} = null; + + // Once we have saved the model, we will fire the "restored" event so this + // developer will do anything they need to after a restore operation is + // totally finished. Then we will return the result of the save call. + $this->exists = true; + + // Save quietly so that we do not fire an updated event on restore + $result = $this->saveQuietly(); + + $this->fireModelEvent('restored', false); + + return $result; + } + + /** + * Get the prunable model query. + * + * @return Builder + */ + public function prunable(): Builder + { + return static::onlyTrashed()->where(BaseModel::ATTRIBUTE_DELETED_AT, ComparisonOperator::LTE, now()->subWeek()); + } +} diff --git a/app/Models/Billing/Balance.php b/app/Models/Billing/Balance.php new file mode 100644 index 000000000..55217ddf7 --- /dev/null +++ b/app/Models/Billing/Balance.php @@ -0,0 +1,112 @@ + BalanceCreated::class, + 'deleted' => BalanceDeleted::class, + 'restored' => BalanceRestored::class, + 'updated' => BalanceUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Balance::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Balance::ATTRIBUTE_ID; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + Balance::ATTRIBUTE_BALANCE => 'decimal:2', + Balance::ATTRIBUTE_DATE => 'date:Y-m-d', + Balance::ATTRIBUTE_FREQUENCY => BalanceFrequency::class, + Balance::ATTRIBUTE_SERVICE => Service::class, + Balance::ATTRIBUTE_USAGE => 'decimal:2', + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return Str::of($this->service->description) + ->append(' ') + ->append($this->date->format(AllowedDateFormat::YM)) + ->__toString(); + } +} diff --git a/app/Models/Billing/Transaction.php b/app/Models/Billing/Transaction.php new file mode 100644 index 000000000..20c6f0822 --- /dev/null +++ b/app/Models/Billing/Transaction.php @@ -0,0 +1,109 @@ + TransactionCreated::class, + 'deleted' => TransactionDeleted::class, + 'restored' => TransactionRestored::class, + 'updated' => TransactionUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Transaction::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Transaction::ATTRIBUTE_ID; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + Transaction::ATTRIBUTE_AMOUNT => 'decimal:2', + Transaction::ATTRIBUTE_DATE => 'date:Y-m-d', + Transaction::ATTRIBUTE_SERVICE => Service::class, + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return Str::of($this->service->description) + ->append(' ') + ->append($this->date->format(AllowedDateFormat::YMD)) + ->__toString(); + } +} diff --git a/app/Models/Document/Page.php b/app/Models/Document/Page.php new file mode 100644 index 000000000..48472c916 --- /dev/null +++ b/app/Models/Document/Page.php @@ -0,0 +1,105 @@ + + */ + protected $hidden = [ + Page::ATTRIBUTE_BODY, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => PageCreated::class, + 'deleted' => PageDeleted::class, + 'restored' => PageRestored::class, + 'updated' => PageUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Page::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Page::ATTRIBUTE_ID; + + /** + * Get the route key for the model. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function getRouteKeyName(): string + { + return Page::ATTRIBUTE_SLUG; + } + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } +} diff --git a/app/Models/User.php b/app/Models/User.php deleted file mode 100644 index 59df87025..000000000 --- a/app/Models/User.php +++ /dev/null @@ -1,39 +0,0 @@ - 'datetime', - ]; -} diff --git a/app/Models/Video.php b/app/Models/Video.php deleted file mode 100644 index f83a9507e..000000000 --- a/app/Models/Video.php +++ /dev/null @@ -1,10 +0,0 @@ - $animesynonyms + * @property Collection $animethemes + * @property Collection $images + * @property string $name + * @property BasePivot $pivot + * @property Collection $resources + * @property Enum|null $season + * @property Collection $series + * @property string $slug + * @property Collection $studios + * @property string|null $synopsis + * @property int|null $year + * + * @method static AnimeFactory factory(...$parameters) + */ +class Anime extends BaseModel +{ + use Actionable; + use Searchable; + + final public const TABLE = 'anime'; + + final public const ATTRIBUTE_ID = 'anime_id'; + final public const ATTRIBUTE_NAME = 'name'; + final public const ATTRIBUTE_SEASON = 'season'; + final public const ATTRIBUTE_SLUG = 'slug'; + final public const ATTRIBUTE_SYNOPSIS = 'synopsis'; + final public const ATTRIBUTE_YEAR = 'year'; + + final public const RELATION_ARTISTS = 'animethemes.song.artists'; + final public const RELATION_ENTRIES = 'animethemes.animethemeentries'; + final public const RELATION_IMAGES = 'images'; + final public const RELATION_RESOURCES = 'resources'; + final public const RELATION_SERIES = 'series'; + final public const RELATION_SONG = 'animethemes.song'; + final public const RELATION_STUDIOS = 'studios'; + final public const RELATION_SYNONYMS = 'animesynonyms'; + final public const RELATION_THEMES = 'animethemes'; + final public const RELATION_VIDEOS = 'animethemes.animethemeentries.videos'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + Anime::ATTRIBUTE_NAME, + Anime::ATTRIBUTE_SEASON, + Anime::ATTRIBUTE_SLUG, + Anime::ATTRIBUTE_SYNOPSIS, + Anime::ATTRIBUTE_YEAR, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => AnimeCreated::class, + 'deleted' => AnimeDeleted::class, + 'deleting' => AnimeDeleting::class, + 'restored' => AnimeRestored::class, + 'updated' => AnimeUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Anime::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Anime::ATTRIBUTE_ID; + + /** + * Modify the query used to retrieve models when making all of the models searchable. + * + * @param Builder $query + * @return Builder + */ + protected function makeAllSearchableUsing(Builder $query): Builder + { + return $query->with(Anime::RELATION_SYNONYMS); + } + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + $array = $this->toArray(); + $array['synonyms'] = $this->animesynonyms->toArray(); + + return $array; + } + + /** + * Get the route key for the model. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function getRouteKeyName(): string + { + return Anime::ATTRIBUTE_SLUG; + } + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + Anime::ATTRIBUTE_SEASON => AnimeSeason::class, + Anime::ATTRIBUTE_YEAR => 'int', + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get the synonyms for the anime. + * + * @return HasMany + */ + public function animesynonyms(): HasMany + { + return $this->hasMany(AnimeSynonym::class, AnimeSynonym::ATTRIBUTE_ANIME); + } + + /** + * Get the series the anime is included in. + * + * @return BelongsToMany + */ + public function series(): BelongsToMany + { + return $this->belongsToMany(Series::class, AnimeSeries::TABLE, Anime::ATTRIBUTE_ID, Series::ATTRIBUTE_ID) + ->using(AnimeSeries::class) + ->withTimestamps(); + } + + /** + * Get the themes for the anime. + * + * @return HasMany + */ + public function animethemes(): HasMany + { + return $this->hasMany(AnimeTheme::class, AnimeTheme::ATTRIBUTE_ANIME); + } + + /** + * Get the resources for the anime. + * + * @return BelongsToMany + */ + public function resources(): BelongsToMany + { + return $this->belongsToMany(ExternalResource::class, AnimeResource::TABLE, Anime::ATTRIBUTE_ID, ExternalResource::ATTRIBUTE_ID) + ->using(AnimeResource::class) + ->withPivot(AnimeResource::ATTRIBUTE_AS) + ->withTimestamps(); + } + + /** + * Get the images for the anime. + * + * @return BelongsToMany + */ + public function images(): BelongsToMany + { + return $this->belongsToMany(Image::class, AnimeImage::TABLE, Anime::ATTRIBUTE_ID, Image::ATTRIBUTE_ID) + ->using(AnimeImage::class) + ->withTimestamps(); + } + + /** + * Get the studios that produced the anime. + * + * @return BelongsToMany + */ + public function studios(): BelongsToMany + { + return $this->belongsToMany(Studio::class, AnimeStudio::TABLE, Anime::ATTRIBUTE_ID, Studio::ATTRIBUTE_ID) + ->using(AnimeStudio::class) + ->withTimestamps(); + } +} diff --git a/app/Models/Wiki/Anime/AnimeSynonym.php b/app/Models/Wiki/Anime/AnimeSynonym.php new file mode 100644 index 000000000..b26c9a9da --- /dev/null +++ b/app/Models/Wiki/Anime/AnimeSynonym.php @@ -0,0 +1,99 @@ + SynonymCreated::class, + 'deleted' => SynonymDeleted::class, + 'restored' => SynonymRestored::class, + 'updated' => SynonymUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = AnimeSynonym::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = AnimeSynonym::ATTRIBUTE_ID; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->text; + } + + /** + * Gets the anime that owns the synonym. + * + * @return BelongsTo + */ + public function anime(): BelongsTo + { + return $this->belongsTo(Anime::class, AnimeSynonym::ATTRIBUTE_ANIME); + } +} diff --git a/app/Models/Wiki/Anime/AnimeTheme.php b/app/Models/Wiki/Anime/AnimeTheme.php new file mode 100644 index 000000000..b3ca0bac3 --- /dev/null +++ b/app/Models/Wiki/Anime/AnimeTheme.php @@ -0,0 +1,188 @@ + $animethemeentries + * @property string|null $group + * @property int|null $sequence + * @property string $slug + * @property Song|null $song + * @property int|null $song_id + * @property int $theme_id + * @property Enum|null $type + * + * @method static AnimeThemeFactory factory(...$parameters) + */ +class AnimeTheme extends BaseModel +{ + use Actionable; + use Searchable; + + final public const TABLE = 'anime_themes'; + + final public const ATTRIBUTE_ANIME = 'anime_id'; + final public const ATTRIBUTE_GROUP = 'group'; + final public const ATTRIBUTE_ID = 'theme_id'; + final public const ATTRIBUTE_SEQUENCE = 'sequence'; + final public const ATTRIBUTE_SLUG = 'slug'; + final public const ATTRIBUTE_SONG = 'song_id'; + final public const ATTRIBUTE_TYPE = 'type'; + + final public const RELATION_ANIME = 'anime'; + final public const RELATION_ARTISTS = 'song.artists'; + final public const RELATION_ENTRIES = 'animethemeentries'; + final public const RELATION_IMAGES = 'anime.images'; + final public const RELATION_SONG = 'song'; + final public const RELATION_SYNONYMS = 'anime.animesynonyms'; + final public const RELATION_VIDEOS = 'animethemeentries.videos'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + AnimeTheme::ATTRIBUTE_ANIME, + AnimeTheme::ATTRIBUTE_GROUP, + AnimeTheme::ATTRIBUTE_SEQUENCE, + AnimeTheme::ATTRIBUTE_SONG, + AnimeTheme::ATTRIBUTE_TYPE, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => ThemeCreated::class, + 'creating' => ThemeCreating::class, + 'deleted' => ThemeDeleted::class, + 'deleting' => ThemeDeleting::class, + 'restored' => ThemeRestored::class, + 'updated' => ThemeUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = AnimeTheme::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = AnimeTheme::ATTRIBUTE_ID; + + /** + * Modify the query used to retrieve models when making all of the models searchable. + * + * @param Builder $query + * @return Builder + */ + protected function makeAllSearchableUsing(Builder $query): Builder + { + return $query->with([ + AnimeTheme::RELATION_SYNONYMS, + AnimeTheme::RELATION_SONG, + ]); + } + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + $array = $this->toArray(); + $array['anime'] = $this->anime->toSearchableArray(); + if ($this->song !== null) { + $array['song'] = $this->song->toSearchableArray() + ['title_keyword' => $this->song->title]; + } + + return $array; + } + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + AnimeTheme::ATTRIBUTE_SEQUENCE => 'int', + AnimeTheme::ATTRIBUTE_TYPE => ThemeType::class, + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->slug; + } + + /** + * Gets the anime that owns the theme. + * + * @return BelongsTo + */ + public function anime(): BelongsTo + { + return $this->belongsTo(Anime::class, AnimeTheme::ATTRIBUTE_ANIME); + } + + /** + * Gets the song that the theme uses. + * + * @return BelongsTo + */ + public function song(): BelongsTo + { + return $this->belongsTo(Song::class, AnimeTheme::ATTRIBUTE_SONG); + } + + /** + * Get the entries for the theme. + * + * @return HasMany + */ + public function animethemeentries(): HasMany + { + return $this->hasMany(AnimeThemeEntry::class, AnimeThemeEntry::ATTRIBUTE_THEME); + } +} diff --git a/app/Models/Wiki/Anime/Theme/AnimeThemeEntry.php b/app/Models/Wiki/Anime/Theme/AnimeThemeEntry.php new file mode 100644 index 000000000..9ddd559ad --- /dev/null +++ b/app/Models/Wiki/Anime/Theme/AnimeThemeEntry.php @@ -0,0 +1,209 @@ + $videos + * + * @method static AnimeThemeEntryFactory factory(...$parameters) + */ +class AnimeThemeEntry extends BaseModel +{ + use Actionable; + use Searchable; + use \Znck\Eloquent\Traits\BelongsToThrough; + + final public const TABLE = 'anime_theme_entries'; + + final public const ATTRIBUTE_EPISODES = 'episodes'; + final public const ATTRIBUTE_ID = 'entry_id'; + final public const ATTRIBUTE_NOTES = 'notes'; + final public const ATTRIBUTE_NSFW = 'nsfw'; + final public const ATTRIBUTE_SPOILER = 'spoiler'; + final public const ATTRIBUTE_THEME = 'theme_id'; + final public const ATTRIBUTE_VERSION = 'version'; + + final public const RELATION_ANIME = 'animetheme.anime'; + final public const RELATION_ANIME_SHALLOW = 'anime'; + final public const RELATION_SONG = 'animetheme.song'; + final public const RELATION_SYNONYMS = 'animetheme.anime.animesynonyms'; + final public const RELATION_THEME = 'animetheme'; + final public const RELATION_VIDEOS = 'videos'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + AnimeThemeEntry::ATTRIBUTE_EPISODES, + AnimeThemeEntry::ATTRIBUTE_NOTES, + AnimeThemeEntry::ATTRIBUTE_NSFW, + AnimeThemeEntry::ATTRIBUTE_SPOILER, + AnimeThemeEntry::ATTRIBUTE_THEME, + AnimeThemeEntry::ATTRIBUTE_VERSION, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => EntryCreated::class, + 'deleted' => EntryDeleted::class, + 'deleting' => EntryDeleting::class, + 'restored' => EntryRestored::class, + 'updated' => EntryUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = AnimeThemeEntry::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = AnimeThemeEntry::ATTRIBUTE_ID; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + AnimeThemeEntry::ATTRIBUTE_NSFW => 'boolean', + AnimeThemeEntry::ATTRIBUTE_SPOILER => 'boolean', + AnimeThemeEntry::ATTRIBUTE_VERSION => 'int', + ]; + + /** + * Modify the query used to retrieve models when making all of the models searchable. + * + * @param Builder $query + * @return Builder + */ + protected function makeAllSearchableUsing(Builder $query): Builder + { + return $query->with([ + AnimeThemeEntry::RELATION_SYNONYMS, + AnimeThemeEntry::RELATION_SONG, + ]); + } + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + $array = $this->toArray(); + $array['theme'] = $this->animetheme->toSearchableArray(); + + // Overwrite version with readable format "V{#}" + $array['version'] = Str::of(empty($this->version) ? '1' : $this->version)->prepend('V')->__toString(); + + return $array; + } + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return Str::of($this->anime->name) + ->append(' ') + ->append($this->animetheme->slug) + ->append(empty($this->version) ? '' : " V$this->version") + ->__toString(); + } + + /** + * Get the theme that owns the entry. + * + * @return BelongsTo + */ + public function animetheme(): BelongsTo + { + return $this->belongsTo(AnimeTheme::class, AnimeThemeEntry::ATTRIBUTE_THEME); + } + + /** + * Get the videos linked in the theme entry. + * + * @return BelongsToMany + */ + public function videos(): BelongsToMany + { + return $this->belongsToMany( + Video::class, + AnimeThemeEntryVideo::class, + AnimeThemeEntry::ATTRIBUTE_ID, + Video::ATTRIBUTE_ID + ) + ->using(AnimeThemeEntryVideo::class) + ->withTimestamps(); + } + + /** + * Get the anime that owns the entry through the theme. + * + * @return BelongsToThrough + */ + public function anime(): BelongsToThrough + { + return $this->belongsToThrough( + Anime::class, + AnimeTheme::class, + null, + '', + [ + Anime::class => Anime::ATTRIBUTE_ID, + AnimeTheme::class => AnimeTheme::ATTRIBUTE_ID, + ] + ); + } +} diff --git a/app/Models/Wiki/Artist.php b/app/Models/Wiki/Artist.php new file mode 100644 index 000000000..1e09de0ed --- /dev/null +++ b/app/Models/Wiki/Artist.php @@ -0,0 +1,205 @@ + $groups + * @property Collection $images + * @property Collection $members + * @property string $name + * @property BasePivot $pivot + * @property Collection $resources + * @property string $slug + * @property Collection $songs + * + * @method static ArtistFactory factory(...$parameters) + */ +class Artist extends BaseModel +{ + use Actionable; + use Searchable; + + final public const TABLE = 'artists'; + + final public const ATTRIBUTE_ID = 'artist_id'; + final public const ATTRIBUTE_NAME = 'name'; + final public const ATTRIBUTE_SLUG = 'slug'; + + final public const RELATION_ANIME = 'songs.animethemes.anime'; + final public const RELATION_ANIMETHEMES = 'songs.animethemes'; + final public const RELATION_GROUPS = 'groups'; + final public const RELATION_IMAGES = 'images'; + final public const RELATION_MEMBERS = 'members'; + final public const RELATION_RESOURCES = 'resources'; + final public const RELATION_SONGS = 'songs'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + Artist::ATTRIBUTE_NAME, + Artist::ATTRIBUTE_SLUG, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => ArtistCreated::class, + 'deleted' => ArtistDeleted::class, + 'restored' => ArtistRestored::class, + 'updated' => ArtistUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Artist::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Artist::ATTRIBUTE_ID; + + /** + * Modify the query used to retrieve models when making all of the models searchable. + * + * @param Builder $query + * @return Builder + */ + protected function makeAllSearchableUsing(Builder $query): Builder + { + return $query->with(Artist::RELATION_SONGS); + } + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + $array = $this->toArray(); + $array['songs'] = $this->songs->toArray(); + + return $array; + } + + /** + * Get the route key for the model. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function getRouteKeyName(): string + { + return Artist::ATTRIBUTE_SLUG; + } + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get the songs the artist has performed in. + * + * @return BelongsToMany + */ + public function songs(): BelongsToMany + { + return $this->belongsToMany(Song::class, ArtistSong::TABLE, Artist::ATTRIBUTE_ID, Song::ATTRIBUTE_ID) + ->using(ArtistSong::class) + ->withPivot(ArtistSong::ATTRIBUTE_AS) + ->withTimestamps(); + } + + /** + * Get the resources for the artist. + * + * @return BelongsToMany + */ + public function resources(): BelongsToMany + { + return $this->belongsToMany(ExternalResource::class, ArtistResource::TABLE, Artist::ATTRIBUTE_ID, ExternalResource::ATTRIBUTE_ID) + ->using(ArtistResource::class) + ->withPivot(ArtistResource::ATTRIBUTE_AS) + ->withTimestamps(); + } + + /** + * Get the members that comprise this group. + * + * @return BelongsToMany + */ + public function members(): BelongsToMany + { + return $this->belongsToMany(Artist::class, ArtistMember::TABLE, Artist::ATTRIBUTE_ID, 'member_id') + ->using(ArtistMember::class) + ->withPivot(ArtistMember::ATTRIBUTE_AS) + ->withTimestamps(); + } + + /** + * Get the groups the artist has performed in. + * + * @return BelongsToMany + */ + public function groups(): BelongsToMany + { + return $this->belongsToMany(Artist::class, ArtistMember::TABLE, 'member_id', Artist::ATTRIBUTE_ID) + ->using(ArtistMember::class) + ->withPivot(ArtistMember::ATTRIBUTE_AS) + ->withTimestamps(); + } + + /** + * Get the images for the artist. + * + * @return BelongsToMany + */ + public function images(): BelongsToMany + { + return $this->belongsToMany(Image::class, ArtistImage::TABLE, Artist::ATTRIBUTE_ID, Image::ATTRIBUTE_ID) + ->using(ArtistImage::class) + ->withTimestamps(); + } +} diff --git a/app/Models/Wiki/ExternalResource.php b/app/Models/Wiki/ExternalResource.php new file mode 100644 index 000000000..ab00685e9 --- /dev/null +++ b/app/Models/Wiki/ExternalResource.php @@ -0,0 +1,148 @@ + $anime + * @property Collection $artists + * @property int|null $external_id + * @property string|null $link + * @property BasePivot $pivot + * @property int $resource_id + * @property Enum|null $site + * + * @method static ExternalResourceFactory factory(...$parameters) + */ +class ExternalResource extends BaseModel +{ + use Actionable; + + final public const TABLE = 'resources'; + + final public const ATTRIBUTE_EXTERNAL_ID = 'external_id'; + final public const ATTRIBUTE_ID = 'resource_id'; + final public const ATTRIBUTE_LINK = 'link'; + final public const ATTRIBUTE_SITE = 'site'; + + final public const RELATION_ANIME = 'anime'; + final public const RELATION_ARTISTS = 'artists'; + final public const RELATION_STUDIOS = 'studios'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + ExternalResource::ATTRIBUTE_EXTERNAL_ID, + ExternalResource::ATTRIBUTE_LINK, + ExternalResource::ATTRIBUTE_SITE, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => ExternalResourceCreated::class, + 'deleted' => ExternalResourceDeleted::class, + 'restored' => ExternalResourceRestored::class, + 'updated' => ExternalResourceUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = ExternalResource::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = ExternalResource::ATTRIBUTE_ID; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + ExternalResource::ATTRIBUTE_EXTERNAL_ID => 'int', + ExternalResource::ATTRIBUTE_SITE => ResourceSite::class, + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return strval($this->link); + } + + /** + * Get the anime that reference this resource. + * + * @return BelongsToMany + */ + public function anime(): BelongsToMany + { + return $this->belongsToMany(Anime::class, AnimeResource::TABLE, ExternalResource::ATTRIBUTE_ID, Anime::ATTRIBUTE_ID) + ->using(AnimeResource::class) + ->withPivot(AnimeResource::ATTRIBUTE_AS) + ->withTimestamps(); + } + + /** + * Get the artists that reference this resource. + * + * @return BelongsToMany + */ + public function artists(): BelongsToMany + { + return $this->belongsToMany(Artist::class, ArtistResource::TABLE, ExternalResource::ATTRIBUTE_ID, Artist::ATTRIBUTE_ID) + ->using(ArtistResource::class) + ->withPivot(ArtistResource::ATTRIBUTE_AS) + ->withTimestamps(); + } + + /** + * Get the studios that reference this resource. + * + * @return BelongsToMany + */ + public function studios(): BelongsToMany + { + return $this->belongsToMany(Studio::class, StudioResource::TABLE, ExternalResource::ATTRIBUTE_ID, Studio::ATTRIBUTE_ID) + ->using(StudioResource::class) + ->withPivot(StudioResource::ATTRIBUTE_AS) + ->withTimestamps(); + } +} diff --git a/app/Models/Wiki/Image.php b/app/Models/Wiki/Image.php new file mode 100644 index 000000000..d063ef587 --- /dev/null +++ b/app/Models/Wiki/Image.php @@ -0,0 +1,129 @@ + $anime + * @property Collection $artists + * @property Enum|null $facet + * @property int $image_id + * @property string $mimetype + * @property string $path + * @property int $size + * + * @method static ImageFactory factory(...$parameters) + */ +class Image extends BaseModel +{ + use Actionable; + + final public const TABLE = 'images'; + + final public const ATTRIBUTE_FACET = 'facet'; + final public const ATTRIBUTE_ID = 'image_id'; + final public const ATTRIBUTE_PATH = 'path'; + + final public const RELATION_ANIME = 'anime'; + final public const RELATION_ARTISTS = 'artists'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + Image::ATTRIBUTE_FACET, + Image::ATTRIBUTE_PATH, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => ImageCreated::class, + 'deleted' => ImageDeleted::class, + 'deleting' => ImageDeleting::class, + 'restored' => ImageRestored::class, + 'updated' => ImageUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Image::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Image::ATTRIBUTE_ID; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + Image::ATTRIBUTE_FACET => ImageFacet::class, + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->path; + } + + /** + * Get the anime that use this image. + * + * @return BelongsToMany + */ + public function anime(): BelongsToMany + { + return $this->belongsToMany(Anime::class, AnimeImage::TABLE, Image::ATTRIBUTE_ID, Anime::ATTRIBUTE_ID) + ->using(AnimeImage::class) + ->withTimestamps(); + } + + /** + * Get the artists that use this image. + * + * @return BelongsToMany + */ + public function artists(): BelongsToMany + { + return $this->belongsToMany(Artist::class, ArtistImage::TABLE, Image::ATTRIBUTE_ID, Artist::ATTRIBUTE_ID) + ->using(ArtistImage::class) + ->withTimestamps(); + } +} diff --git a/app/Models/Wiki/Series.php b/app/Models/Wiki/Series.php new file mode 100644 index 000000000..6ad70fb58 --- /dev/null +++ b/app/Models/Wiki/Series.php @@ -0,0 +1,113 @@ + $anime + * @property string $name + * @property int $series_id + * @property string $slug + * + * @method static SeriesFactory factory(...$parameters) + */ +class Series extends BaseModel +{ + use Actionable; + use Searchable; + + final public const TABLE = 'series'; + + final public const ATTRIBUTE_ID = 'series_id'; + final public const ATTRIBUTE_NAME = 'name'; + final public const ATTRIBUTE_SLUG = 'slug'; + + final public const RELATION_ANIME = 'anime'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + Series::ATTRIBUTE_NAME, + Series::ATTRIBUTE_SLUG, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => SeriesCreated::class, + 'deleted' => SeriesDeleted::class, + 'restored' => SeriesRestored::class, + 'updated' => SeriesUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Series::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Series::ATTRIBUTE_ID; + + /** + * Get the route key for the model. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function getRouteKeyName(): string + { + return Series::ATTRIBUTE_SLUG; + } + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get the anime included in the series. + * + * @return BelongsToMany + */ + public function anime(): BelongsToMany + { + return $this->belongsToMany(Anime::class, AnimeSeries::TABLE, Series::ATTRIBUTE_ID, Anime::ATTRIBUTE_ID) + ->using(AnimeSeries::class) + ->withTimestamps(); + } +} diff --git a/app/Models/Wiki/Song.php b/app/Models/Wiki/Song.php new file mode 100644 index 000000000..573d6a656 --- /dev/null +++ b/app/Models/Wiki/Song.php @@ -0,0 +1,123 @@ + $animethemes + * @property Collection $artists + * @property BasePivot $pivot + * @property int $song_id + * @property string|null $title + * + * @method static SongFactory factory(...$parameters) + */ +class Song extends BaseModel +{ + use Actionable; + use Searchable; + + final public const TABLE = 'songs'; + + final public const ATTRIBUTE_ID = 'song_id'; + final public const ATTRIBUTE_TITLE = 'title'; + + final public const RELATION_ANIME = 'animethemes.anime'; + final public const RELATION_ANIMETHEMES = 'animethemes'; + final public const RELATION_ARTISTS = 'artists'; + final public const RELATION_VIDEOS = 'animethemes.animethemeentries.videos'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + Song::ATTRIBUTE_TITLE, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => SongCreated::class, + 'deleted' => SongDeleted::class, + 'deleting' => SongDeleting::class, + 'restored' => SongRestored::class, + 'updated' => SongUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Song::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Song::ATTRIBUTE_ID; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + if (empty($this->title)) { + return strval($this->getKey()); + } + + return $this->title; + } + + /** + * Get the anime themes that use this song. + * + * @return HasMany + */ + public function animethemes(): HasMany + { + return $this->hasMany(AnimeTheme::class, AnimeTheme::ATTRIBUTE_SONG); + } + + /** + * Get the artists included in the performance. + * + * @return BelongsToMany + */ + public function artists(): BelongsToMany + { + return $this->belongsToMany(Artist::class, ArtistSong::TABLE, Song::ATTRIBUTE_ID, Artist::ATTRIBUTE_ID) + ->using(ArtistSong::class) + ->withPivot(ArtistSong::ATTRIBUTE_AS) + ->withTimestamps(); + } +} diff --git a/app/Models/Wiki/Studio.php b/app/Models/Wiki/Studio.php new file mode 100644 index 000000000..bf43ebd2a --- /dev/null +++ b/app/Models/Wiki/Studio.php @@ -0,0 +1,130 @@ + $anime + * @property string $name + * @property string $slug + * @property int $studio_id + * @property BasePivot $pivot + * + * @method static StudioFactory factory(...$parameters) + */ +class Studio extends BaseModel +{ + use Actionable; + use Searchable; + + final public const TABLE = 'studios'; + + final public const ATTRIBUTE_ID = 'studio_id'; + final public const ATTRIBUTE_NAME = 'name'; + final public const ATTRIBUTE_SLUG = 'slug'; + + final public const RELATION_ANIME = 'anime'; + final public const RELATION_RESOURCES = 'resources'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + Studio::ATTRIBUTE_NAME, + Studio::ATTRIBUTE_SLUG, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => StudioCreated::class, + 'deleted' => StudioDeleted::class, + 'restored' => StudioRestored::class, + 'updated' => StudioUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Studio::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Studio::ATTRIBUTE_ID; + + /** + * Get the route key for the model. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function getRouteKeyName(): string + { + return Studio::ATTRIBUTE_SLUG; + } + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get the anime that the studio produced. + * + * @return BelongsToMany + */ + public function anime(): BelongsToMany + { + return $this->belongsToMany(Anime::class, AnimeStudio::TABLE, Studio::ATTRIBUTE_ID, Anime::ATTRIBUTE_ID) + ->using(AnimeStudio::class) + ->withTimestamps(); + } + + /** + * Get the resources for the studio. + * + * @return BelongsToMany + */ + public function resources(): BelongsToMany + { + return $this->belongsToMany(ExternalResource::class, StudioResource::TABLE, Studio::ATTRIBUTE_ID, ExternalResource::ATTRIBUTE_ID) + ->using(StudioResource::class) + ->withPivot(StudioResource::ATTRIBUTE_AS) + ->withTimestamps(); + } +} diff --git a/app/Models/Wiki/Video.php b/app/Models/Wiki/Video.php new file mode 100644 index 000000000..adfbf2d5f --- /dev/null +++ b/app/Models/Wiki/Video.php @@ -0,0 +1,241 @@ + $animethemeentries + * @property string $basename + * @property string $filename + * @property bool $lyrics + * @property string $mimetype + * @property bool $nc + * @property Enum $overlap + * @property string $path + * @property int|null $resolution + * @property int $size + * @property Enum|null $source + * @property bool $subbed + * @property string[] $tags + * @property bool $uncen + * @property int $video_id + * + * @method static VideoFactory factory(...$parameters) + */ +class Video extends BaseModel implements Viewable +{ + use Actionable; + use Searchable; + use InteractsWithViews; + + final public const TABLE = 'videos'; + + final public const ATTRIBUTE_BASENAME = 'basename'; + final public const ATTRIBUTE_FILENAME = 'filename'; + final public const ATTRIBUTE_ID = 'video_id'; + final public const ATTRIBUTE_LYRICS = 'lyrics'; + final public const ATTRIBUTE_MIMETYPE = 'mimetype'; + final public const ATTRIBUTE_NC = 'nc'; + final public const ATTRIBUTE_OVERLAP = 'overlap'; + final public const ATTRIBUTE_PATH = 'path'; + final public const ATTRIBUTE_RESOLUTION = 'resolution'; + final public const ATTRIBUTE_SIZE = 'size'; + final public const ATTRIBUTE_SOURCE = 'source'; + final public const ATTRIBUTE_SUBBED = 'subbed'; + final public const ATTRIBUTE_TAGS = 'tags'; + final public const ATTRIBUTE_UNCEN = 'uncen'; + + final public const RELATION_ANIME = 'animethemeentries.animetheme.anime'; + final public const RELATION_ANIMESYNONYMS = 'animethemeentries.animetheme.anime.animesynonyms'; + final public const RELATION_ANIMETHEME = 'animethemeentries.animetheme'; + final public const RELATION_ANIMETHEMEENTRIES = 'animethemeentries'; + final public const RELATION_SONG = 'animethemeentries.animetheme.song'; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + Video::ATTRIBUTE_BASENAME, + Video::ATTRIBUTE_FILENAME, + Video::ATTRIBUTE_LYRICS, + Video::ATTRIBUTE_MIMETYPE, + Video::ATTRIBUTE_NC, + Video::ATTRIBUTE_OVERLAP, + Video::ATTRIBUTE_PATH, + Video::ATTRIBUTE_RESOLUTION, + Video::ATTRIBUTE_SIZE, + Video::ATTRIBUTE_SOURCE, + Video::ATTRIBUTE_SUBBED, + Video::ATTRIBUTE_UNCEN, + ]; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => VideoCreated::class, + 'creating' => VideoCreating::class, + 'deleted' => VideoDeleted::class, + 'restored' => VideoRestored::class, + 'updated' => VideoUpdated::class, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = Video::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = Video::ATTRIBUTE_ID; + + /** + * The accessors to append to the model's array form. + * + * @var array + */ + protected $appends = [ + Video::ATTRIBUTE_TAGS, + ]; + + /** + * The array of tags used to uniquely identify the video within the context of a theme. + * + * @return array + */ + public function getTagsAttribute(): array + { + $tags = []; + + if ($this->nc) { + $tags[] = 'NC'; + } + if (! empty($this->source) && ($this->source->is(VideoSource::BD) || $this->source->is(VideoSource::DVD))) { + $tags[] = $this->source->description; + } + if (! empty($this->resolution) && $this->resolution !== 720) { + $tags[] = strval($this->resolution); + } + + if ($this->subbed) { + $tags[] = 'Subbed'; + } elseif ($this->lyrics) { + $tags[] = 'Lyrics'; + } + + return $tags; + } + + /** + * Modify the query used to retrieve models when making all of the models searchable. + * + * @param Builder $query + * @return Builder + */ + protected function makeAllSearchableUsing(Builder $query): Builder + { + return $query->with([ + Video::RELATION_SONG, + Video::RELATION_ANIMESYNONYMS, + ]); + } + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + $array = $this->toArray(); + + $array['entries'] = $this->animethemeentries->map( + fn (AnimeThemeEntry $entry) => $entry->toSearchableArray() + )->toArray(); + + return $array; + } + + /** + * Get the route key for the model. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function getRouteKeyName(): string + { + return Video::ATTRIBUTE_BASENAME; + } + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + Video::ATTRIBUTE_LYRICS => 'boolean', + Video::ATTRIBUTE_NC => 'boolean', + Video::ATTRIBUTE_OVERLAP => VideoOverlap::class, + Video::ATTRIBUTE_SIZE => 'int', + Video::ATTRIBUTE_SOURCE => VideoSource::class, + Video::ATTRIBUTE_SUBBED => 'boolean', + Video::ATTRIBUTE_UNCEN => 'boolean', + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->basename; + } + + /** + * Get the related entries. + * + * @return BelongsToMany + */ + public function animethemeentries(): BelongsToMany + { + return $this->belongsToMany(AnimeThemeEntry::class, AnimeThemeEntryVideo::TABLE, Video::ATTRIBUTE_ID, AnimeThemeEntry::ATTRIBUTE_ID) + ->using(AnimeThemeEntryVideo::class) + ->withTimestamps(); + } +} diff --git a/app/Notifications/DiscordNotification.php b/app/Notifications/DiscordNotification.php new file mode 100644 index 000000000..ca9904d7c --- /dev/null +++ b/app/Notifications/DiscordNotification.php @@ -0,0 +1,70 @@ +message; + } + + /** + * Determines if the notification can be sent. + * + * @param mixed $notifiable + * @param string $channel + * @return mixed + * + * @noinspection PhpUnusedParameterInspection + */ + public function shouldSend(mixed $notifiable, string $channel): mixed + { + return config(FlagConstants::ALLOW_DISCORD_NOTIFICATIONS_FLAG_QUALIFIED, false); + } +} diff --git a/app/Nova/Actions/Auth/Invitation/ResendInvitationAction.php b/app/Nova/Actions/Auth/Invitation/ResendInvitationAction.php new file mode 100644 index 000000000..97688db49 --- /dev/null +++ b/app/Nova/Actions/Auth/Invitation/ResendInvitationAction.php @@ -0,0 +1,66 @@ + $models + * @return array + * + * @noinspection PhpUnusedParameterInspection + */ + public function handle(ActionFields $fields, Collection $models): array + { + $resentInvitations = []; + + foreach ($models as $invitation) { + // Don't send mail for invitation that have been claimed + if ($invitation->isOpen()) { + // Send replacement email + Mail::to($invitation->email)->queue(new InvitationMail($invitation)); + $resentInvitations[] = $invitation->name; + } + } + + if (! empty($resentInvitations)) { + return Action::message( + __('nova.resent_invitations_for_users', ['users' => implode(', ', $resentInvitations)]) + ); + } + + return Action::danger(__('nova.resent_invitations_for_none')); + } +} diff --git a/app/Nova/Actions/Auth/Permission/GiveRoleAction.php b/app/Nova/Actions/Auth/Permission/GiveRoleAction.php new file mode 100644 index 000000000..0ed361d0f --- /dev/null +++ b/app/Nova/Actions/Auth/Permission/GiveRoleAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $role = Role::findById(intval($fields->get(self::FIELD_ROLE))); + + foreach ($models as $permission) { + $permission->assignRole($role); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $roles = Role::all([Role::ATTRIBUTE_ID, Role::ATTRIBUTE_NAME]) + ->keyBy(Role::ATTRIBUTE_ID) + ->map(fn (Role $role) => $role->name) + ->toArray(); + + return [ + Select::make(__('nova.role'), self::FIELD_ROLE) + ->searchable() + ->required() + ->options($roles) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Auth/Permission/RevokeRoleAction.php b/app/Nova/Actions/Auth/Permission/RevokeRoleAction.php new file mode 100644 index 000000000..489dbb93a --- /dev/null +++ b/app/Nova/Actions/Auth/Permission/RevokeRoleAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $role = Role::findById(intval($fields->get(self::FIELD_ROLE))); + + foreach ($models as $permission) { + $permission->removeRole($role); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $roles = Role::all([Role::ATTRIBUTE_ID, Role::ATTRIBUTE_NAME]) + ->keyBy(Role::ATTRIBUTE_ID) + ->map(fn (Role $role) => $role->name) + ->toArray(); + + return [ + Select::make(__('nova.role'), self::FIELD_ROLE) + ->searchable() + ->required() + ->options($roles) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Auth/Role/GivePermissionAction.php b/app/Nova/Actions/Auth/Role/GivePermissionAction.php new file mode 100644 index 000000000..d40c19755 --- /dev/null +++ b/app/Nova/Actions/Auth/Role/GivePermissionAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $permission = Permission::findById(intval($fields->get(self::FIELD_PERMISSION))); + + foreach ($models as $role) { + $role->givePermissionTo($permission); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $permissions = Permission::all([Permission::ATTRIBUTE_ID, Permission::ATTRIBUTE_NAME]) + ->keyBy(Permission::ATTRIBUTE_ID) + ->map(fn (Permission $permission) => $permission->name) + ->toArray(); + + return [ + Select::make(__('nova.permission'), self::FIELD_PERMISSION) + ->searchable() + ->required() + ->options($permissions) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Auth/Role/RevokePermissionAction.php b/app/Nova/Actions/Auth/Role/RevokePermissionAction.php new file mode 100644 index 000000000..28515e388 --- /dev/null +++ b/app/Nova/Actions/Auth/Role/RevokePermissionAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $permission = Permission::findById(intval($fields->get(self::FIELD_PERMISSION))); + + foreach ($models as $role) { + $role->revokePermissionTo($permission); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $permissions = Permission::all([Permission::ATTRIBUTE_ID, Permission::ATTRIBUTE_NAME]) + ->keyBy(Permission::ATTRIBUTE_ID) + ->map(fn (Permission $permission) => $permission->name) + ->toArray(); + + return [ + Select::make(__('nova.permission'), self::FIELD_PERMISSION) + ->searchable() + ->required() + ->options($permissions) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Auth/User/GivePermissionAction.php b/app/Nova/Actions/Auth/User/GivePermissionAction.php new file mode 100644 index 000000000..a9e76ee51 --- /dev/null +++ b/app/Nova/Actions/Auth/User/GivePermissionAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $permission = Permission::findById(intval($fields->get(self::FIELD_PERMISSION))); + + foreach ($models as $user) { + $user->givePermissionTo($permission); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $permissions = Permission::all([Permission::ATTRIBUTE_ID, Permission::ATTRIBUTE_NAME]) + ->keyBy(Permission::ATTRIBUTE_ID) + ->map(fn (Permission $permission) => $permission->name) + ->toArray(); + + return [ + Select::make(__('nova.permission'), self::FIELD_PERMISSION) + ->searchable() + ->required() + ->options($permissions) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Auth/User/GiveRoleAction.php b/app/Nova/Actions/Auth/User/GiveRoleAction.php new file mode 100644 index 000000000..1812e566b --- /dev/null +++ b/app/Nova/Actions/Auth/User/GiveRoleAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $role = Role::findById(intval($fields->get(self::FIELD_ROLE))); + + foreach ($models as $user) { + $user->assignRole($role); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $roles = Role::all([Role::ATTRIBUTE_ID, Role::ATTRIBUTE_NAME]) + ->keyBy(Role::ATTRIBUTE_ID) + ->map(fn (Role $role) => $role->name) + ->toArray(); + + return [ + Select::make(__('nova.role'), self::FIELD_ROLE) + ->searchable() + ->required() + ->options($roles) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Auth/User/RevokePermissionAction.php b/app/Nova/Actions/Auth/User/RevokePermissionAction.php new file mode 100644 index 000000000..b8ccd1bbf --- /dev/null +++ b/app/Nova/Actions/Auth/User/RevokePermissionAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $permission = Permission::findById(intval($fields->get(self::FIELD_PERMISSION))); + + foreach ($models as $user) { + $user->revokePermissionTo($permission); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $permissions = Permission::all([Permission::ATTRIBUTE_ID, Permission::ATTRIBUTE_NAME]) + ->keyBy(Permission::ATTRIBUTE_ID) + ->map(fn (Permission $permission) => $permission->name) + ->toArray(); + + return [ + Select::make(__('nova.permission'), self::FIELD_PERMISSION) + ->searchable() + ->required() + ->options($permissions) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Auth/User/RevokeRoleAction.php b/app/Nova/Actions/Auth/User/RevokeRoleAction.php new file mode 100644 index 000000000..23fd08084 --- /dev/null +++ b/app/Nova/Actions/Auth/User/RevokeRoleAction.php @@ -0,0 +1,75 @@ + $models + * @return Collection + */ + public function handle(ActionFields $fields, Collection $models): Collection + { + $role = Role::findById(intval($fields->get(self::FIELD_ROLE))); + + foreach ($models as $user) { + $user->removeRole($role); + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $roles = Role::all([Role::ATTRIBUTE_ID, Role::ATTRIBUTE_NAME]) + ->keyBy(Role::ATTRIBUTE_ID) + ->map(fn (Role $role) => $role->name) + ->toArray(); + + return [ + Select::make(__('nova.role'), self::FIELD_ROLE) + ->searchable() + ->required() + ->options($roles) + ->rules('required'), + ]; + } +} diff --git a/app/Nova/Actions/Wiki/Anime/BackfillAnimeAction.php b/app/Nova/Actions/Wiki/Anime/BackfillAnimeAction.php new file mode 100644 index 000000000..59844f04e --- /dev/null +++ b/app/Nova/Actions/Wiki/Anime/BackfillAnimeAction.php @@ -0,0 +1,202 @@ + $models + * @return mixed + */ + public function handle(ActionFields $fields, Collection $models): mixed + { + foreach ($models as $anime) { + if ($anime->resources()->doesntExist()) { + $this->markAsFailed($anime, 'At least one Resource is required to backfill Anime'); + continue; + } + + $pipes = $this->getPipes($fields, $anime); + + $pipeline = new Pipeline(Container::getInstance()); + + try { + $pipeline->send($this->user) + ->through($pipes) + ->then(fn () => $this->markAsFinished($anime)); + } catch (Exception $e) { + $this->markAsFailed($anime, $e); + } finally { + // Try not to upset third-party APIs + sleep(rand(3, 5)); + } + } + + return $models; + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function fields(NovaRequest $request): array + { + $anime = $request->resourceId !== null + ? $request->findModel() + : null; + + return [ + Heading::make(__('nova.backfill_resources')), + + Boolean::make(__('nova.backfill_kitsu_resource'), self::BACKFILL_KITSU_RESOURCE) + ->help(__('nova.backfill_kitsu_resource_help')) + ->default(fn () => $anime instanceof Anime && $anime->resources()->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::KITSU)->doesntExist()), + + Boolean::make(__('nova.backfill_anilist_resource'), self::BACKFILL_ANILIST_RESOURCE) + ->help(__('nova.backfill_anilist_resource_help')) + ->default(fn () => $anime instanceof Anime && $anime->resources()->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST)->doesntExist()), + + Boolean::make(__('nova.backfill_mal_resource'), self::BACKFILL_MAL_RESOURCE) + ->help(__('nova.backfill_mal_resource_help')) + ->default(fn () => $anime instanceof Anime && $anime->resources()->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL)->doesntExist()), + + Boolean::make(__('nova.backfill_anidb_resource'), self::BACKFILL_ANIDB_RESOURCE) + ->help(__('nova.backfill_anidb_resource_help')) + ->default(fn () => $anime instanceof Anime && $anime->resources()->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIDB)->doesntExist()), + + Boolean::make(__('nova.backfill_ann_resource'), self::BACKFILL_ANN_RESOURCE) + ->help(__('nova.backfill_ann_resource_help')) + ->default(fn () => $anime instanceof Anime && $anime->resources()->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANN)->doesntExist()), + + Heading::make(__('nova.backfill_images')), + + Boolean::make(__('nova.backfill_large_cover'), self::BACKFILL_LARGE_COVER) + ->help(__('nova.backfill_large_cover_help')) + ->default(fn () => $anime instanceof Anime && $anime->images()->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_LARGE)->doesntExist()), + + Boolean::make(__('nova.backfill_small_cover'), self::BACKFILL_SMALL_COVER) + ->help(__('nova.backfill_small_cover_help')) + ->default(fn () => $anime instanceof Anime && $anime->images()->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_SMALL)->doesntExist()), + + Heading::make(__('nova.backfill_studios')), + + Boolean::make(__('nova.backfill_anime_studios'), self::BACKFILL_STUDIOS) + ->help(__('nova.backfill_anime_studios_help')) + ->default(fn () => $anime instanceof Anime && $anime->studios()->doesntExist()), + ]; + } + + /** + * Get the selected pipes for backfilling anime. + * + * @param ActionFields $fields + * @param Anime $anime + * @return BackfillAnimePipe[] + */ + protected function getPipes(ActionFields $fields, Anime $anime): array + { + $pipes = []; + + foreach ($this->getPipeMapping($anime) as $field => $pipe) { + if (Arr::get($fields, $field) === true) { + $pipes[] = $pipe; + } + } + + return $pipes; + } + + /** + * Get the mapping of anime pipes to their form fields. + * + * @param Anime $anime + * @return array + */ + protected function getPipeMapping(Anime $anime): array + { + return [ + self::BACKFILL_KITSU_RESOURCE => new BackfillKitsuResource($anime), + self::BACKFILL_ANILIST_RESOURCE => new BackfillAnilistResource($anime), + self::BACKFILL_MAL_RESOURCE => new BackfillMalResource($anime), + self::BACKFILL_ANIDB_RESOURCE => new BackfillAnidbResource($anime), + self::BACKFILL_ANN_RESOURCE => new BackfillAnnResource($anime), + self::BACKFILL_LARGE_COVER => new BackfillLargeCoverImage($anime), + self::BACKFILL_SMALL_COVER => new BackfillSmallCoverImage($anime), + self::BACKFILL_STUDIOS => new BackfillAnimeStudios($anime), + ]; + } +} diff --git a/app/Nova/Actions/Wiki/Anime/CreateExternalResourceSiteForAnimeAction.php b/app/Nova/Actions/Wiki/Anime/CreateExternalResourceSiteForAnimeAction.php new file mode 100644 index 000000000..eed391e25 --- /dev/null +++ b/app/Nova/Actions/Wiki/Anime/CreateExternalResourceSiteForAnimeAction.php @@ -0,0 +1,86 @@ + ResourceSite::getDescription($this->site)]); + } + + /** + * Perform the action on the given models. + * + * @param ActionFields $fields + * @param Collection $models + * @return array + */ + public function handle(ActionFields $fields, Collection $models): array + { + // Create Resource Model with link and provided site + $resource = ExternalResource::factory()->createOne([ + ExternalResource::ATTRIBUTE_EXTERNAL_ID => null, + ExternalResource::ATTRIBUTE_LINK => $fields->get('link'), + ExternalResource::ATTRIBUTE_SITE => $this->site, + ]); + + // Attach Resource to Anime and provide success message + $resource->anime()->attach($models); + + return Action::message(__('nova.anime_create_resource_action_success')); + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return array_merge( + parent::fields($request), + [ + Text::make(__('nova.link'), 'link') + ->rules([ + 'required', + 'max:192', + 'url', + Rule::unique(ExternalResource::TABLE), + new ResourceLinkMatchesSiteRule($this->site), + ]) + ->help(__('nova.resource_link_help')), + ] + ); + } +} diff --git a/app/Nova/Actions/Wiki/Artist/CreateExternalResourceSiteForArtistAction.php b/app/Nova/Actions/Wiki/Artist/CreateExternalResourceSiteForArtistAction.php new file mode 100644 index 000000000..dc2b8240c --- /dev/null +++ b/app/Nova/Actions/Wiki/Artist/CreateExternalResourceSiteForArtistAction.php @@ -0,0 +1,86 @@ + ResourceSite::getDescription($this->site)]); + } + + /** + * Perform the action on the given models. + * + * @param ActionFields $fields + * @param Collection $models + * @return array + */ + public function handle(ActionFields $fields, Collection $models): array + { + // Create Resource Model with link and provided site + $resource = ExternalResource::factory()->createOne([ + ExternalResource::ATTRIBUTE_EXTERNAL_ID => null, + ExternalResource::ATTRIBUTE_LINK => $fields->get('link'), + ExternalResource::ATTRIBUTE_SITE => $this->site, + ]); + + // Attach Resource to Anime and provide success message + $resource->artists()->attach($models); + + return Action::message(__('nova.artist_create_resource_action_success')); + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return array_merge( + parent::fields($request), + [ + Text::make(__('nova.link'), 'link') + ->rules([ + 'required', + 'max:192', + 'url', + Rule::unique(ExternalResource::TABLE), + new ResourceLinkMatchesSiteRule($this->site), + ]) + ->help(__('nova.resource_link_help')), + ] + ); + } +} diff --git a/app/Nova/Actions/Wiki/Studio/CreateExternalResourceSiteForStudioAction.php b/app/Nova/Actions/Wiki/Studio/CreateExternalResourceSiteForStudioAction.php new file mode 100644 index 000000000..2b8340ecb --- /dev/null +++ b/app/Nova/Actions/Wiki/Studio/CreateExternalResourceSiteForStudioAction.php @@ -0,0 +1,86 @@ + ResourceSite::getDescription($this->site)]); + } + + /** + * Perform the action on the given models. + * + * @param ActionFields $fields + * @param Collection $models + * @return array + */ + public function handle(ActionFields $fields, Collection $models): array + { + // Create Resource Model with link and provided site + $resource = ExternalResource::factory()->createOne([ + ExternalResource::ATTRIBUTE_EXTERNAL_ID => null, + ExternalResource::ATTRIBUTE_LINK => $fields->get('link'), + ExternalResource::ATTRIBUTE_SITE => $this->site, + ]); + + // Attach Resource to Studio and provide success message + $resource->studios()->attach($models); + + return Action::message(__('nova.studio_create_resource_action_success')); + } + + /** + * Get the fields available on the action. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return array_merge( + parent::fields($request), + [ + Text::make(__('nova.link'), 'link') + ->rules([ + 'required', + 'max:192', + 'url', + Rule::unique(ExternalResource::TABLE), + new ResourceLinkMatchesSiteRule($this->site), + ]) + ->help(__('nova.resource_link_help')), + ] + ); + } +} diff --git a/app/Nova/Dashboards/Main.php b/app/Nova/Dashboards/Main.php new file mode 100644 index 000000000..d5aa9df79 --- /dev/null +++ b/app/Nova/Dashboards/Main.php @@ -0,0 +1,46 @@ +width(Card::ONE_HALF_WIDTH), + (new AnimePerDay())->width(Card::ONE_HALF_WIDTH), + + (new NewArtists())->width(Card::ONE_HALF_WIDTH), + (new ArtistsPerDay())->width(Card::ONE_HALF_WIDTH), + + (new NewSeries())->width(Card::ONE_HALF_WIDTH), + (new SeriesPerDay())->width(Card::ONE_HALF_WIDTH), + + (new NewVideos())->width(Card::ONE_HALF_WIDTH), + (new VideosPerDay())->width(Card::ONE_HALF_WIDTH), + ]; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeAniDbResourceLens.php b/app/Nova/Lenses/Anime/AnimeAniDbResourceLens.php new file mode 100644 index 000000000..0a371e15c --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeAniDbResourceLens.php @@ -0,0 +1,117 @@ + ResourceSite::getDescription(ResourceSite::ANIDB)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIDB); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForAnimeAction(ResourceSite::ANIDB))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-anidb-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeAnilistResourceLens.php b/app/Nova/Lenses/Anime/AnimeAnilistResourceLens.php new file mode 100644 index 000000000..e40f7ca5c --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeAnilistResourceLens.php @@ -0,0 +1,117 @@ + ResourceSite::getDescription(ResourceSite::ANILIST)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForAnimeAction(ResourceSite::ANILIST))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-anilist-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeAnnResourceLens.php b/app/Nova/Lenses/Anime/AnimeAnnResourceLens.php new file mode 100644 index 000000000..4f20885ad --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeAnnResourceLens.php @@ -0,0 +1,117 @@ + ResourceSite::getDescription(ResourceSite::ANN)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANN); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForAnimeAction(ResourceSite::ANN))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-ann-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeCoverLargeLens.php b/app/Nova/Lenses/Anime/AnimeCoverLargeLens.php new file mode 100644 index 000000000..1013c8cc4 --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeCoverLargeLens.php @@ -0,0 +1,109 @@ + ImageFacet::getDescription(ImageFacet::COVER_LARGE)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_IMAGES, function (Builder $imageQuery) { + $imageQuery->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_LARGE); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-cover-large-lens'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeCoverSmallLens.php b/app/Nova/Lenses/Anime/AnimeCoverSmallLens.php new file mode 100644 index 000000000..9458c9d46 --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeCoverSmallLens.php @@ -0,0 +1,109 @@ + ImageFacet::getDescription(ImageFacet::COVER_SMALL)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_IMAGES, function (Builder $imageQuery) { + $imageQuery->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_SMALL); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-cover-small-lens'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeKitsuResourceLens.php b/app/Nova/Lenses/Anime/AnimeKitsuResourceLens.php new file mode 100644 index 000000000..39b0af5d3 --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeKitsuResourceLens.php @@ -0,0 +1,117 @@ + ResourceSite::getDescription(ResourceSite::KITSU)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::KITSU); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForAnimeAction(ResourceSite::KITSU))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-kitsu-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeMalResourceLens.php b/app/Nova/Lenses/Anime/AnimeMalResourceLens.php new file mode 100644 index 000000000..d0e66e2f6 --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeMalResourceLens.php @@ -0,0 +1,117 @@ + ResourceSite::getDescription(ResourceSite::MAL)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForAnimeAction(ResourceSite::MAL))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-mal-resource'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimePlanetResourceLens.php b/app/Nova/Lenses/Anime/AnimePlanetResourceLens.php new file mode 100644 index 000000000..fa6db9146 --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimePlanetResourceLens.php @@ -0,0 +1,118 @@ + ResourceSite::getDescription(ResourceSite::ANIME_PLANET)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIME_PLANET); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForAnimeAction(ResourceSite::ANIME_PLANET)) + ->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-planet-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Anime/AnimeStudioLens.php b/app/Nova/Lenses/Anime/AnimeStudioLens.php new file mode 100644 index 000000000..23ae55890 --- /dev/null +++ b/app/Nova/Lenses/Anime/AnimeStudioLens.php @@ -0,0 +1,105 @@ +withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_STUDIOS) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Anime::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Anime::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Anime::ATTRIBUTE_SLUG) + ->sortable(), + + Number::make(__('nova.year'), Anime::ATTRIBUTE_YEAR) + ->sortable() + ->filterable(), + + Select::make(__('nova.season'), Anime::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-studio-lens'; + } +} diff --git a/app/Nova/Lenses/Artist/ArtistAniDbResourceLens.php b/app/Nova/Lenses/Artist/ArtistAniDbResourceLens.php new file mode 100644 index 000000000..d877f224f --- /dev/null +++ b/app/Nova/Lenses/Artist/ArtistAniDbResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::ANIDB)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Artist::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIDB); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Artist::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Artist::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Artist::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForArtistAction(ResourceSite::ANIDB))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artist-anidb-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Artist/ArtistAnilistResourceLens.php b/app/Nova/Lenses/Artist/ArtistAnilistResourceLens.php new file mode 100644 index 000000000..016b979b6 --- /dev/null +++ b/app/Nova/Lenses/Artist/ArtistAnilistResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::ANILIST)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Artist::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Artist::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Artist::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Artist::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForArtistAction(ResourceSite::ANILIST))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artist-anilist-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Artist/ArtistAnnResourceLens.php b/app/Nova/Lenses/Artist/ArtistAnnResourceLens.php new file mode 100644 index 000000000..7f75ba8ce --- /dev/null +++ b/app/Nova/Lenses/Artist/ArtistAnnResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::ANN)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Artist::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANN); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Artist::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Artist::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Artist::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForArtistAction(ResourceSite::ANN))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artist-ann-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Artist/ArtistCoverLargeLens.php b/app/Nova/Lenses/Artist/ArtistCoverLargeLens.php new file mode 100644 index 000000000..3f9e24c60 --- /dev/null +++ b/app/Nova/Lenses/Artist/ArtistCoverLargeLens.php @@ -0,0 +1,97 @@ + ImageFacet::getDescription(ImageFacet::COVER_LARGE)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_IMAGES, function (Builder $imageQuery) { + $imageQuery->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_LARGE); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Artist::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Artist::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Artist::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artist-cover-large-lens'; + } +} diff --git a/app/Nova/Lenses/Artist/ArtistCoverSmallLens.php b/app/Nova/Lenses/Artist/ArtistCoverSmallLens.php new file mode 100644 index 000000000..a370b6895 --- /dev/null +++ b/app/Nova/Lenses/Artist/ArtistCoverSmallLens.php @@ -0,0 +1,97 @@ + ImageFacet::getDescription(ImageFacet::COVER_SMALL)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Anime::RELATION_IMAGES, function (Builder $imageQuery) { + $imageQuery->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_SMALL); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Artist::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Artist::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Artist::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artist-cover-small-lens'; + } +} diff --git a/app/Nova/Lenses/Artist/ArtistMalResourceLens.php b/app/Nova/Lenses/Artist/ArtistMalResourceLens.php new file mode 100644 index 000000000..2c02d6608 --- /dev/null +++ b/app/Nova/Lenses/Artist/ArtistMalResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::MAL)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Artist::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Artist::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Artist::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Artist::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForArtistAction(ResourceSite::MAL))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artist-mal-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Artist/ArtistSongLens.php b/app/Nova/Lenses/Artist/ArtistSongLens.php new file mode 100644 index 000000000..4b57fa204 --- /dev/null +++ b/app/Nova/Lenses/Artist/ArtistSongLens.php @@ -0,0 +1,92 @@ +withOrdering($request->withFilters( + $query->whereDoesntHave(Artist::RELATION_SONGS) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Artist::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Artist::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Artist::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artist-song-lens'; + } +} diff --git a/app/Nova/Lenses/BaseLens.php b/app/Nova/Lenses/BaseLens.php new file mode 100644 index 000000000..d5abc069a --- /dev/null +++ b/app/Nova/Lenses/BaseLens.php @@ -0,0 +1,34 @@ +withOrdering($request->withFilters( + $query->whereDoesntHave(ExternalResource::RELATION_ANIME) + ->whereDoesntHave(ExternalResource::RELATION_ARTISTS) + ->whereDoesntHave(ExternalResource::RELATION_STUDIOS) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), ExternalResource::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.link'), ExternalResource::ATTRIBUTE_LINK) + ->sortable() + ->filterable(), + + Number::make(__('nova.external_id'), ExternalResource::ATTRIBUTE_EXTERNAL_ID) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'external-resource-unlinked-lens'; + } +} diff --git a/app/Nova/Lenses/Image/ImageUnlinkedLens.php b/app/Nova/Lenses/Image/ImageUnlinkedLens.php new file mode 100644 index 000000000..d1dba77ab --- /dev/null +++ b/app/Nova/Lenses/Image/ImageUnlinkedLens.php @@ -0,0 +1,97 @@ +withOrdering($request->withFilters( + $query->whereNot(Image::ATTRIBUTE_FACET, ImageFacet::GRILL) + ->whereDoesntHave(Image::RELATION_ANIME)->whereDoesntHave(Image::RELATION_ARTISTS) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Image::ATTRIBUTE_ID) + ->sortable(), + + Select::make(__('nova.facet'), Image::ATTRIBUTE_FACET) + ->options(ImageFacet::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->filterable(), + + NovaImage::make(__('nova.image'), Image::ATTRIBUTE_PATH) + ->disk('images'), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'image-unlinked-lens'; + } +} diff --git a/app/Nova/Lenses/Song/SongArtistLens.php b/app/Nova/Lenses/Song/SongArtistLens.php new file mode 100644 index 000000000..9f3c8e22f --- /dev/null +++ b/app/Nova/Lenses/Song/SongArtistLens.php @@ -0,0 +1,88 @@ +withOrdering($request->withFilters( + $query->whereDoesntHave(Song::RELATION_ARTISTS) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Song::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.title'), Song::ATTRIBUTE_TITLE) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'song-artist-lens'; + } +} diff --git a/app/Nova/Lenses/Studio/StudioAniDbResourceLens.php b/app/Nova/Lenses/Studio/StudioAniDbResourceLens.php new file mode 100644 index 000000000..700d01cc3 --- /dev/null +++ b/app/Nova/Lenses/Studio/StudioAniDbResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::ANIDB)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Studio::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIDB); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Studio::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Studio::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Studio::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return [ + (new CreateExternalResourceSiteForStudioAction(ResourceSite::ANIDB))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'studio-anidb-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Studio/StudioAnilistResourceLens.php b/app/Nova/Lenses/Studio/StudioAnilistResourceLens.php new file mode 100644 index 000000000..adc817ce8 --- /dev/null +++ b/app/Nova/Lenses/Studio/StudioAnilistResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::ANILIST)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Studio::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Studio::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Studio::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Studio::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param Request $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(Request $request): array + { + return [ + (new CreateExternalResourceSiteForStudioAction(ResourceSite::ANILIST))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'studio-anilist-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Studio/StudioAnimePlanetResourceLens.php b/app/Nova/Lenses/Studio/StudioAnimePlanetResourceLens.php new file mode 100644 index 000000000..5f5fe32f1 --- /dev/null +++ b/app/Nova/Lenses/Studio/StudioAnimePlanetResourceLens.php @@ -0,0 +1,105 @@ + ResourceSite::getDescription(ResourceSite::ANIME_PLANET)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Studio::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIME_PLANET); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Studio::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Studio::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Studio::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param Request $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(Request $request): array + { + return [ + (new CreateExternalResourceSiteForStudioAction(ResourceSite::ANIME_PLANET)) + ->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'studio-anime-planet-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Studio/StudioAnnResourceLens.php b/app/Nova/Lenses/Studio/StudioAnnResourceLens.php new file mode 100644 index 000000000..1287f3040 --- /dev/null +++ b/app/Nova/Lenses/Studio/StudioAnnResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::ANN)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Studio::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANN); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Studio::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Studio::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Studio::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param Request $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(Request $request): array + { + return [ + (new CreateExternalResourceSiteForStudioAction(ResourceSite::ANN))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'studio-ann-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Studio/StudioMalResourceLens.php b/app/Nova/Lenses/Studio/StudioMalResourceLens.php new file mode 100644 index 000000000..7c8f58769 --- /dev/null +++ b/app/Nova/Lenses/Studio/StudioMalResourceLens.php @@ -0,0 +1,104 @@ + ResourceSite::getDescription(ResourceSite::MAL)]); + } + + /** + * Get the query builder / paginator for the lens. + * + * @param LensRequest $request + * @param Builder $query + * @return Builder + */ + public static function query(LensRequest $request, $query): Builder + { + return $request->withOrdering($request->withFilters( + $query->whereDoesntHave(Studio::RELATION_RESOURCES, function (Builder $resourceQuery) { + $resourceQuery->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL); + }) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Studio::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Studio::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Studio::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param Request $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(Request $request): array + { + return [ + (new CreateExternalResourceSiteForStudioAction(ResourceSite::MAL))->canSee(function (Request $request) { + $user = $request->user(); + + return $user->can('create external resource'); + }), + ]; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'studio-mal-resource-lens'; + } +} diff --git a/app/Nova/Lenses/Studio/StudioUnlinkedLens.php b/app/Nova/Lenses/Studio/StudioUnlinkedLens.php new file mode 100644 index 000000000..92a6a6aaa --- /dev/null +++ b/app/Nova/Lenses/Studio/StudioUnlinkedLens.php @@ -0,0 +1,92 @@ +withOrdering($request->withFilters( + $query->whereDoesntHave(Studio::RELATION_ANIME) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Studio::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.name'), Studio::ATTRIBUTE_NAME) + ->sortable() + ->filterable(), + + Text::make(__('nova.slug'), Studio::ATTRIBUTE_SLUG) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'studio-unlinked-lens'; + } +} diff --git a/app/Nova/Lenses/Video/VideoResolutionLens.php b/app/Nova/Lenses/Video/VideoResolutionLens.php new file mode 100644 index 000000000..c85ff78aa --- /dev/null +++ b/app/Nova/Lenses/Video/VideoResolutionLens.php @@ -0,0 +1,105 @@ +withOrdering($request->withFilters( + $query->whereNull(Video::ATTRIBUTE_RESOLUTION) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Video::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.filename'), Video::ATTRIBUTE_FILENAME) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.nc'), Video::ATTRIBUTE_NC) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.subbed'), Video::ATTRIBUTE_SUBBED) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.lyrics'), Video::ATTRIBUTE_LYRICS) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.uncen'), Video::ATTRIBUTE_UNCEN) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'video-resolution-lens'; + } +} diff --git a/app/Nova/Lenses/Video/VideoSourceLens.php b/app/Nova/Lenses/Video/VideoSourceLens.php new file mode 100644 index 000000000..74ced3478 --- /dev/null +++ b/app/Nova/Lenses/Video/VideoSourceLens.php @@ -0,0 +1,110 @@ +withOrdering($request->withFilters( + $query->whereNull(Video::ATTRIBUTE_SOURCE) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Video::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.filename'), Video::ATTRIBUTE_FILENAME) + ->sortable() + ->filterable(), + + Number::make(__('nova.resolution'), Video::ATTRIBUTE_RESOLUTION) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.nc'), Video::ATTRIBUTE_NC) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.subbed'), Video::ATTRIBUTE_SUBBED) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.lyrics'), Video::ATTRIBUTE_LYRICS) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.uncen'), Video::ATTRIBUTE_UNCEN) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'video-source-lens'; + } +} diff --git a/app/Nova/Lenses/Video/VideoUnlinkedLens.php b/app/Nova/Lenses/Video/VideoUnlinkedLens.php new file mode 100644 index 000000000..348e03938 --- /dev/null +++ b/app/Nova/Lenses/Video/VideoUnlinkedLens.php @@ -0,0 +1,110 @@ +withOrdering($request->withFilters( + $query->whereDoesntHave(Video::RELATION_ANIMETHEMEENTRIES) + )); + } + + /** + * Get the fields available to the lens. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), Video::ATTRIBUTE_ID) + ->sortable(), + + Text::make(__('nova.filename'), Video::ATTRIBUTE_FILENAME) + ->sortable() + ->filterable(), + + Number::make(__('nova.resolution'), Video::ATTRIBUTE_RESOLUTION) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.nc'), Video::ATTRIBUTE_NC) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.subbed'), Video::ATTRIBUTE_SUBBED) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.lyrics'), Video::ATTRIBUTE_LYRICS) + ->sortable() + ->filterable(), + + Boolean::make(__('nova.uncen'), Video::ATTRIBUTE_UNCEN) + ->sortable() + ->filterable(), + ]; + } + + /** + * Get the actions available on the lens. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function actions(NovaRequest $request): array + { + return []; + } + + /** + * Get the URI key for the lens. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'video-unlinked-lens'; + } +} diff --git a/app/Nova/Metrics/Anime/AnimePerDay.php b/app/Nova/Metrics/Anime/AnimePerDay.php new file mode 100644 index 000000000..234309e86 --- /dev/null +++ b/app/Nova/Metrics/Anime/AnimePerDay.php @@ -0,0 +1,69 @@ +countByDays($request, Anime::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 90 => '90 Days', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(5); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'anime-per-day'; + } +} diff --git a/app/Nova/Metrics/Anime/NewAnime.php b/app/Nova/Metrics/Anime/NewAnime.php new file mode 100644 index 000000000..3be257490 --- /dev/null +++ b/app/Nova/Metrics/Anime/NewAnime.php @@ -0,0 +1,74 @@ +count($request, Anime::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 365 => '365 Days', + 'TODAY' => 'Today', + 'YESTERDAY' => 'Yesterday', + 'MTD' => 'Month To Date', + 'QTD' => 'Quarter To Date', + 'YTD' => 'Year To Date', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(5); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'new-anime'; + } +} diff --git a/app/Nova/Metrics/Artist/ArtistsPerDay.php b/app/Nova/Metrics/Artist/ArtistsPerDay.php new file mode 100644 index 000000000..4f91f2838 --- /dev/null +++ b/app/Nova/Metrics/Artist/ArtistsPerDay.php @@ -0,0 +1,69 @@ +countByDays($request, Artist::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 90 => '90 Days', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(5); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'artists-per-day'; + } +} diff --git a/app/Nova/Metrics/Artist/NewArtists.php b/app/Nova/Metrics/Artist/NewArtists.php new file mode 100644 index 000000000..4b069ed13 --- /dev/null +++ b/app/Nova/Metrics/Artist/NewArtists.php @@ -0,0 +1,74 @@ +count($request, Artist::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 365 => '365 Days', + 'TODAY' => 'Today', + 'YESTERDAY' => 'Yesterday', + 'MTD' => 'Month To Date', + 'QTD' => 'Quarter To Date', + 'YTD' => 'Year To Date', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(5); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'new-artists'; + } +} diff --git a/app/Nova/Metrics/Series/NewSeries.php b/app/Nova/Metrics/Series/NewSeries.php new file mode 100644 index 000000000..f885a3793 --- /dev/null +++ b/app/Nova/Metrics/Series/NewSeries.php @@ -0,0 +1,74 @@ +count($request, Series::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 365 => '365 Days', + 'TODAY' => 'Today', + 'YESTERDAY' => 'Yesterday', + 'MTD' => 'Month To Date', + 'QTD' => 'Quarter To Date', + 'YTD' => 'Year To Date', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(5); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'new-series'; + } +} diff --git a/app/Nova/Metrics/Series/SeriesPerDay.php b/app/Nova/Metrics/Series/SeriesPerDay.php new file mode 100644 index 000000000..d0fbfe724 --- /dev/null +++ b/app/Nova/Metrics/Series/SeriesPerDay.php @@ -0,0 +1,69 @@ +countByDays($request, Series::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 90 => '90 Days', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(5); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'series-per-day'; + } +} diff --git a/app/Nova/Metrics/Video/NewVideos.php b/app/Nova/Metrics/Video/NewVideos.php new file mode 100644 index 000000000..69b0fdf9c --- /dev/null +++ b/app/Nova/Metrics/Video/NewVideos.php @@ -0,0 +1,74 @@ +count($request, Video::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 365 => '365 Days', + 'TODAY' => 'Today', + 'YESTERDAY' => 'Yesterday', + 'MTD' => 'Month To Date', + 'QTD' => 'Quarter To Date', + 'YTD' => 'Year To Date', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(60); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'new-videos'; + } +} diff --git a/app/Nova/Metrics/Video/VideosPerDay.php b/app/Nova/Metrics/Video/VideosPerDay.php new file mode 100644 index 000000000..e1ba7c047 --- /dev/null +++ b/app/Nova/Metrics/Video/VideosPerDay.php @@ -0,0 +1,69 @@ +countByDays($request, Video::class); + } + + /** + * Get the ranges available for the metric. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function ranges(): array + { + return [ + 30 => '30 Days', + 60 => '60 Days', + 90 => '90 Days', + ]; + } + + /** + * Determine for how many minutes the metric should be cached. + * + * @return DateTimeInterface|DateInterval|float|int + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function cacheFor(): DateInterval|float|DateTimeInterface|int + { + return now()->addMinutes(5); + } + + /** + * Get the URI key for the metric. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function uriKey(): string + { + return 'videos-per-day'; + } +} diff --git a/app/Nova/Resources/Admin/Announcement.php b/app/Nova/Resources/Admin/Announcement.php new file mode 100644 index 000000000..11526eb1a --- /dev/null +++ b/app/Nova/Resources/Admin/Announcement.php @@ -0,0 +1,149 @@ +sortable() + ->showOnPreview(), + + Code::make(__('nova.content'), AnnouncementModel::ATTRIBUTE_CONTENT) + ->rules(['required', 'max:65535']) + ->language('htmlmixed') + ->showOnPreview(), + + Panel::make(__('nova.timestamps'), $this->timestamps()), + ]; + } + + /** + * Get the fields displayed by the resource on index page. + * + * @param NovaRequest $request + * @return array + * + * @noinspection PhpUnusedParameterInspection + */ + public function fieldsForIndex(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), AnnouncementModel::ATTRIBUTE_ID) + ->sortable() + ->showOnPreview(), + + Text::make(__('nova.content'), AnnouncementModel::ATTRIBUTE_CONTENT) + ->sortable() + ->showOnPreview(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } +} diff --git a/app/Nova/Resources/Auth/Invitation.php b/app/Nova/Resources/Auth/Invitation.php new file mode 100644 index 000000000..d50b0e46a --- /dev/null +++ b/app/Nova/Resources/Auth/Invitation.php @@ -0,0 +1,183 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), InvitationModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192', 'alpha_dash']) + ->showOnPreview() + ->filterable(), + + Email::make(__('nova.email'), InvitationModel::ATTRIBUTE_EMAIL) + ->sortable() + ->rules(['required', 'email', 'max:192']) + ->creationRules(Rule::unique(InvitationModel::TABLE)->__toString()) + ->updateRules( + Rule::unique(InvitationModel::TABLE) + ->ignore($request->get('resourceId'), InvitationModel::ATTRIBUTE_ID) + ->__toString() + ) + ->showOnPreview() + ->filterable(), + + Select::make(__('nova.status'), InvitationModel::ATTRIBUTE_STATUS) + ->hideWhenCreating() + ->options(InvitationStatus::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(InvitationStatus::class, false)]) + ->showOnPreview() + ->filterable(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * 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 ResendInvitationAction()) + ->confirmText(__('nova.resend_invitation_confirm_message')) + ->confirmButtonText(__('nova.resend')) + ->cancelButtonText(__('nova.cancel')), + ] + ); + } +} diff --git a/app/Nova/Resources/Auth/Permission.php b/app/Nova/Resources/Auth/Permission.php new file mode 100644 index 000000000..48bef08ee --- /dev/null +++ b/app/Nova/Resources/Auth/Permission.php @@ -0,0 +1,165 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), PermissionModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->showOnPreview() + ->filterable(), + + BelongsToMany::make(__('nova.roles'), PermissionModel::RELATION_ROLES, Role::class) + ->filterable(), + + BelongsToMany::make(__('nova.users'), PermissionModel::RELATION_USERS, User::class) + ->filterable(), + ]; + } + + /** + * 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 GiveRoleAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline() + ->canRun(fn (NovaRequest $novaRequest) => $novaRequest->user()->can('view permission')), + + (new RevokeRoleAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline() + ->canRun(fn (NovaRequest $novaRequest) => $novaRequest->user()->can('view permission')), + ] + ); + } +} diff --git a/app/Nova/Resources/Auth/Role.php b/app/Nova/Resources/Auth/Role.php new file mode 100644 index 000000000..4677adb26 --- /dev/null +++ b/app/Nova/Resources/Auth/Role.php @@ -0,0 +1,163 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), RoleModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->showOnPreview() + ->filterable(), + + BelongsToMany::make(__('nova.permissions'), RoleModel::RELATION_PERMISSIONS, Permission::class) + ->filterable(), + + BelongsToMany::make(__('nova.users'), RoleModel::RELATION_USERS, User::class) + ->filterable(), + ]; + } + + /** + * 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 GivePermissionAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline(), + + (new RevokePermissionAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline(), + ] + ); + } +} diff --git a/app/Nova/Resources/Auth/User.php b/app/Nova/Resources/Auth/User.php new file mode 100644 index 000000000..8cd137846 --- /dev/null +++ b/app/Nova/Resources/Auth/User.php @@ -0,0 +1,207 @@ +sortable() + ->showOnPreview(), + + Gravatar::make()->maxWidth(50) + ->showOnPreview(), + + Text::make(__('nova.name'), UserModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192', 'alpha_dash']) + ->showOnPreview() + ->filterable(), + + Email::make(__('nova.email'), UserModel::ATTRIBUTE_EMAIL) + ->sortable() + ->rules(['required', 'email', 'max:192']) + ->creationRules(Rule::unique(UserModel::TABLE)->__toString()) + ->updateRules( + Rule::unique(UserModel::TABLE) + ->ignore($request->get('resourceId'), UserModel::ATTRIBUTE_ID) + ->__toString() + ) + ->showOnPreview() + ->filterable(), + + BelongsToMany::make(__('nova.roles'), UserModel::RELATION_ROLES, Role::class) + ->filterable(), + + BelongsToMany::make(__('nova.permissions'), UserModel::RELATION_PERMISSIONS, Permission::class) + ->filterable(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * 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 GivePermissionAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline(), + + (new GiveRoleAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline(), + + (new RevokePermissionAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline(), + + (new RevokeRoleAction()) + ->confirmButtonText(__('nova.confirm')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline(), + ] + ); + } +} diff --git a/app/Nova/Resources/Billing/Balance.php b/app/Nova/Resources/Billing/Balance.php new file mode 100644 index 000000000..81decf6e9 --- /dev/null +++ b/app/Nova/Resources/Billing/Balance.php @@ -0,0 +1,165 @@ +sortable() + ->showOnPreview(), + + Date::make(__('nova.date'), BalanceModel::ATTRIBUTE_DATE) + ->sortable() + ->rules('required') + ->help(__('nova.balance_date_help')) + ->showOnPreview() + ->filterable(), + + Select::make(__('nova.service'), BalanceModel::ATTRIBUTE_SERVICE) + ->options(Service::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(Service::class, false)]) + ->help(__('nova.billing_service_help')) + ->showOnPreview() + ->filterable(), + + Select::make(__('nova.frequency'), BalanceModel::ATTRIBUTE_FREQUENCY) + ->options(BalanceFrequency::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(BalanceFrequency::class, false)]) + ->help(__('nova.balance_frequency_help')) + ->showOnPreview() + ->filterable(), + + Currency::make(__('nova.usage'), BalanceModel::ATTRIBUTE_USAGE) + ->sortable() + ->rules('required') + ->help(__('nova.balance_usage_help')) + ->showOnPreview() + ->filterable(), + + Currency::make(__('nova.balance'), BalanceModel::ATTRIBUTE_BALANCE) + ->sortable() + ->rules('required') + ->help(__('nova.balance_balance_help')) + ->showOnPreview() + ->filterable(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } +} diff --git a/app/Nova/Resources/Billing/Transaction.php b/app/Nova/Resources/Billing/Transaction.php new file mode 100644 index 000000000..2a3cc556a --- /dev/null +++ b/app/Nova/Resources/Billing/Transaction.php @@ -0,0 +1,169 @@ +sortable() + ->showOnPreview(), + + Date::make(__('nova.date'), TransactionModel::ATTRIBUTE_DATE) + ->sortable() + ->rules('required') + ->help(__('nova.transaction_date_help')) + ->showOnPreview() + ->filterable(), + + Select::make(__('nova.service'), TransactionModel::ATTRIBUTE_SERVICE) + ->options(Service::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(Service::class, false)]) + ->help(__('nova.billing_service_help')) + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.description'), TransactionModel::ATTRIBUTE_DESCRIPTION) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->help(__('nova.transaction_description_help')) + ->showOnPreview() + ->filterable(), + + Currency::make(__('nova.amount'), TransactionModel::ATTRIBUTE_AMOUNT) + ->sortable() + ->rules('required') + ->help(__('nova.transaction_amount_help')) + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.external_id'), TransactionModel::ATTRIBUTE_EXTERNAL_ID) + ->nullable() + ->sortable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.transaction_external_id_help')) + ->showOnPreview() + ->filterable(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } +} diff --git a/app/Nova/Resources/Document/Page.php b/app/Nova/Resources/Document/Page.php new file mode 100644 index 000000000..71cfa13f7 --- /dev/null +++ b/app/Nova/Resources/Document/Page.php @@ -0,0 +1,157 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), PageModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->help(__('nova.page_name_help')) + ->showOnPreview() + ->filterable(), + + Slug::make(__('nova.slug'), PageModel::ATTRIBUTE_SLUG) + ->from(PageModel::ATTRIBUTE_NAME) + ->separator('_') + ->sortable() + ->rules(['required', 'max:192', 'regex:/^[\pL\pM\pN\/_-]+$/u']) + ->updateRules( + Rule::unique(PageModel::TABLE) + ->ignore($request->route('resourceId'), PageModel::ATTRIBUTE_ID) + ->__toString() + ) + ->help(__('nova.page_slug_help')) + ->showOnPreview(), + + Markdown::make(__('nova.body'), PageModel::ATTRIBUTE_BODY) + ->rules(['required', 'max:16777215']) + ->help(__('nova.page_body_help')), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } +} diff --git a/app/Nova/Resources/Resource.php b/app/Nova/Resources/Resource.php new file mode 100644 index 000000000..230b3f57e --- /dev/null +++ b/app/Nova/Resources/Resource.php @@ -0,0 +1,95 @@ +hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + + DateTime::make(__('nova.updated_at'), BaseModel::ATTRIBUTE_UPDATED_AT) + ->hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + + DateTime::make(__('nova.deleted_at'), BaseModel::ATTRIBUTE_DELETED_AT) + ->hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + ]; + } +} diff --git a/app/Nova/Resources/Wiki/Anime.php b/app/Nova/Resources/Wiki/Anime.php new file mode 100644 index 000000000..52360c425 --- /dev/null +++ b/app/Nova/Resources/Wiki/Anime.php @@ -0,0 +1,334 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), AnimeModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->help(__('nova.anime_name_help')) + ->showOnPreview() + ->filterable(), + + Slug::make(__('nova.slug'), AnimeModel::ATTRIBUTE_SLUG) + ->from(AnimeModel::ATTRIBUTE_NAME) + ->separator('_') + ->sortable() + ->rules(['required', 'max:192', 'alpha_dash']) + ->updateRules( + Rule::unique(AnimeModel::TABLE) + ->ignore($request->route('resourceId'), AnimeModel::ATTRIBUTE_ID) + ->__toString() + ) + ->help(__('nova.anime_slug_help')) + ->showOnPreview(), + + Number::make(__('nova.year'), AnimeModel::ATTRIBUTE_YEAR) + ->sortable() + ->min(1960) + ->max(intval(date('Y')) + 1) + ->rules(['required', 'digits:4', 'integer']) + ->help(__('nova.anime_year_help')) + ->showOnPreview() + ->filterable(), + + Select::make(__('nova.season'), AnimeModel::ATTRIBUTE_SEASON) + ->options(AnimeSeason::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(AnimeSeason::class, false)]) + ->help(__('nova.anime_season_help')) + ->showOnPreview() + ->filterable(), + + Textarea::make(__('nova.synopsis'), AnimeModel::ATTRIBUTE_SYNOPSIS) + ->rules('max:65535') + ->nullable() + ->help(__('nova.anime_synopsis_help')) + ->showOnPreview(), + + HasMany::make(__('nova.anime_synonyms'), AnimeModel::RELATION_SYNONYMS, Synonym::class), + + HasMany::make(__('nova.anime_themes'), AnimeModel::RELATION_THEMES, Theme::class), + + BelongsToMany::make(__('nova.series'), AnimeModel::RELATION_SERIES, Series::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + BelongsToMany::make(__('nova.external_resources'), AnimeModel::RELATION_RESOURCES, ExternalResource::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), AnimeResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + BelongsToMany::make(__('nova.images'), AnimeModel::RELATION_IMAGES, Image::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + BelongsToMany::make(__('nova.studios'), AnimeModel::RELATION_STUDIOS, Studio::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * 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 BackfillAnimeAction($request->user())) + ->confirmButtonText(__('nova.backfill')) + ->cancelButtonText(__('nova.cancel')) + ->showOnIndex() + ->showOnDetail() + ->showInline() + ->canSee(function (Request $request) { + $user = $request->user(); + + return $user instanceof User && $user->can('update anime'); + }), + ] + ); + } + + /** + * Get the cards available for the request. + * + * @param NovaRequest $request + * @return array + */ + public function cards(NovaRequest $request): array + { + return array_merge( + parent::cards($request), + [ + (new NewAnime())->width(Card::ONE_HALF_WIDTH), + (new AnimePerDay())->width(Card::ONE_HALF_WIDTH), + ] + ); + } + + /** + * Get the lenses available for the resource. + * + * @param NovaRequest $request + * @return array + */ + public function lenses(NovaRequest $request): array + { + return array_merge( + parent::lenses($request), + [ + new AnimeAniDbResourceLens(), + new AnimeAnilistResourceLens(), + new AnimeCoverLargeLens(), + new AnimeCoverSmallLens(), + new AnimePlanetResourceLens(), + new AnimeAnnResourceLens(), + new AnimeKitsuResourceLens(), + new AnimeMalResourceLens(), + new AnimeStudioLens(), + ] + ); + } +} diff --git a/app/Nova/Resources/Wiki/Anime/Synonym.php b/app/Nova/Resources/Wiki/Anime/Synonym.php new file mode 100644 index 000000000..ded33e185 --- /dev/null +++ b/app/Nova/Resources/Wiki/Anime/Synonym.php @@ -0,0 +1,149 @@ +with(AnimeSynonym::RELATION_ANIME); + } + + /** + * Get the fields displayed by the resource. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + BelongsTo::make(__('nova.anime'), AnimeSynonym::RELATION_ANIME, Anime::class) + ->sortable() + ->filterable() + ->searchable(fn () => $request->viaResource() === null) + ->readonly(fn () => $request->viaResource() !== null) + ->required(fn () => $request->viaResource() === null) + ->withSubtitles() + ->showCreateRelationButton(fn () => $request->viaResource() === null) + ->showOnPreview(), + + ID::make(__('nova.id'), AnimeSynonym::ATTRIBUTE_ID) + ->sortable() + ->showOnPreview(), + + Text::make(__('nova.text'), AnimeSynonym::ATTRIBUTE_TEXT) + ->sortable() + ->rules(['required', 'max:192']) + ->help(__('nova.anime_synonym_text_help')) + ->showOnPreview() + ->filterable(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } +} diff --git a/app/Nova/Resources/Wiki/Anime/Theme.php b/app/Nova/Resources/Wiki/Anime/Theme.php new file mode 100644 index 000000000..e7b6ea5a2 --- /dev/null +++ b/app/Nova/Resources/Wiki/Anime/Theme.php @@ -0,0 +1,227 @@ +model(); + if ($theme instanceof AnimeTheme) { + return $theme->anime->getName(); + } + + return null; + } + + /** + * Get the displayable label of the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function label(): string + { + return __('nova.anime_themes'); + } + + /** + * Get the displayable singular label of the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function singularLabel(): string + { + return __('nova.anime_theme'); + } + + /** + * Get the URI key for the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function uriKey(): string + { + return 'anime-themes'; + } + + /** + * Get the searchable columns for the resource. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function searchableColumns(): array + { + return [ + new Column(AnimeTheme::ATTRIBUTE_SLUG), + ]; + } + + /** + * The logical group associated with the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function group(): string + { + return __('nova.wiki'); + } + + /** + * Build a "relatable" query for the given resource. + * + * This query determines which instances of the model may be attached to other resources. + * + * @param NovaRequest $request + * @param Builder $query + * @return Builder + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function indexQuery(NovaRequest $request, $query): Builder + { + return $query->with([AnimeTheme::RELATION_ANIME, AnimeTheme::RELATION_SONG]); + } + + /** + * Build a "relatable" query for the given resource. + * + * This query determines which instances of the model may be attached to other resources. + * + * @param NovaRequest $request + * @param Builder $query + * @return Builder + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function relatableQuery(NovaRequest $request, $query): Builder + { + return $query->with(AnimeTheme::RELATION_ANIME); + } + + /** + * Get the fields displayed by the resource. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), AnimeTheme::ATTRIBUTE_ID) + ->sortable() + ->showOnPreview(), + + BelongsTo::make(__('nova.anime'), AnimeTheme::RELATION_ANIME, Anime::class) + ->sortable() + ->filterable() + ->searchable(fn () => $request->viaResource() === null || Song::class === $request->viaResource()) + ->readonly(fn () => $request->viaResource() !== null && Song::class !== $request->viaResource()) + ->required(fn () => $request->viaResource() === null || Song::class === $request->viaResource()) + ->withSubtitles() + ->showCreateRelationButton(fn () => $request->viaResource() === null || Song::class === $request->viaResource()) + ->showOnPreview(), + + Select::make(__('nova.type'), AnimeTheme::ATTRIBUTE_TYPE) + ->options(ThemeType::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(ThemeType::class, false)]) + ->help(__('nova.anime_theme_type_help')) + ->showOnPreview() + ->filterable(), + + Number::make(__('nova.sequence'), AnimeTheme::ATTRIBUTE_SEQUENCE) + ->sortable() + ->nullable() + ->rules(['nullable', 'integer']) + ->help(__('nova.anime_theme_sequence_help')) + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.group'), AnimeTheme::ATTRIBUTE_GROUP) + ->sortable() + ->nullable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.anime_theme_group_help')) + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.slug'), AnimeTheme::ATTRIBUTE_SLUG) + ->hideWhenCreating() + ->sortable() + ->rules(['required', 'max:192', 'alpha_dash']) + ->help(__('nova.anime_theme_slug_help')) + ->showOnPreview() + ->filterable(), + + BelongsTo::make(__('nova.song'), AnimeTheme::RELATION_SONG, Song::class) + ->sortable() + ->filterable() + ->searchable() + ->withSubtitles() + ->nullable() + ->showCreateRelationButton() + ->showOnPreview(), + + HasMany::make(__('nova.anime_theme_entries'), AnimeTheme::RELATION_ENTRIES, Entry::class), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } +} diff --git a/app/Nova/Resources/Wiki/Anime/Theme/Entry.php b/app/Nova/Resources/Wiki/Anime/Theme/Entry.php new file mode 100644 index 000000000..b104ea9f5 --- /dev/null +++ b/app/Nova/Resources/Wiki/Anime/Theme/Entry.php @@ -0,0 +1,253 @@ +model(); + if ($entry instanceof AnimeThemeEntry) { + if (is_int($entry->version)) { + return "V$entry->version"; + } + + return "{$entry->getKey()}"; + } + + return ''; + } + + /** + * Get the search result subtitle for the resource. + * + * @return string|null + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function subtitle(): ?string + { + $entry = $this->model(); + if ($entry instanceof AnimeThemeEntry) { + return "{$entry->anime->getName()} {$entry->animetheme->getName()}"; + } + + return null; + } + + /** + * The logical group associated with the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function group(): string + { + return __('nova.wiki'); + } + + /** + * Get the displayable label of the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function label(): string + { + return __('nova.anime_theme_entries'); + } + + /** + * Get the displayable singular label of the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function singularLabel(): string + { + return __('nova.anime_theme_entry'); + } + + /** + * Get the URI key for the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function uriKey(): string + { + return 'anime-theme-entries'; + } + + /** + * Get the searchable columns for the resource. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function searchableColumns(): array + { + return [ + new Column(AnimeThemeEntry::ATTRIBUTE_ID), + ]; + } + + /** + * Build a "relatable" query for the given resource. + * + * This query determines which instances of the model may be attached to other resources. + * + * @param NovaRequest $request + * @param Builder $query + * @return Builder + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function indexQuery(NovaRequest $request, $query): Builder + { + return $query->with([AnimeThemeEntry::RELATION_THEME, AnimeThemeEntry::RELATION_ANIME_SHALLOW]); + } + + /** + * Build a "relatable" query for the given resource. + * + * This query determines which instances of the model may be attached to other resources. + * + * @param NovaRequest $request + * @param Builder $query + * @return Builder + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function relatableQuery(NovaRequest $request, $query): Builder + { + return $query->with([AnimeThemeEntry::RELATION_THEME, AnimeThemeEntry::RELATION_ANIME_SHALLOW]); + } + + /** + * Get the fields displayed by the resource. + * + * @param NovaRequest $request + * @return array + */ + public function fields(NovaRequest $request): array + { + return [ + BelongsTo::make(__('nova.anime'), AnimeThemeEntry::RELATION_ANIME_SHALLOW, Anime::class) + ->sortable() + ->hideFromIndex(fn () => $request->viaResource() !== null && Video::class !== $request->viaResource()) + ->hideWhenCreating() + ->readonly() + ->showOnPreview(), + + BelongsTo::make(__('nova.anime_theme'), AnimeThemeEntry::RELATION_THEME, Theme::class) + ->sortable() + ->filterable() + ->searchable(fn () => $request->viaResource() === null) + ->readonly(fn () => $request->viaResource() !== null) + ->required(fn () => $request->viaResource() === null) + ->withSubtitles() + ->showOnPreview(), + + ID::make(__('nova.id'), AnimeThemeEntry::ATTRIBUTE_ID) + ->sortable() + ->showOnPreview(), + + Number::make(__('nova.version'), AnimeThemeEntry::ATTRIBUTE_VERSION) + ->sortable() + ->nullable() + ->rules(['nullable', 'integer']) + ->help(__('nova.anime_theme_entry_version_help')) + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.episodes'), AnimeThemeEntry::ATTRIBUTE_EPISODES) + ->sortable() + ->nullable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.anime_theme_entry_episodes_help')) + ->showOnPreview() + ->filterable(), + + Boolean::make(__('nova.nsfw'), AnimeThemeEntry::ATTRIBUTE_NSFW) + ->sortable() + ->nullable() + ->rules(['nullable', 'boolean']) + ->help(__('nova.anime_theme_entry_nsfw_help')) + ->showOnPreview() + ->filterable(), + + Boolean::make(__('nova.spoiler'), AnimeThemeEntry::ATTRIBUTE_SPOILER) + ->sortable() + ->nullable() + ->rules(['nullable', 'boolean']) + ->help(__('nova.anime_theme_entry_spoiler_help')) + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.notes'), AnimeThemeEntry::ATTRIBUTE_NOTES) + ->sortable() + ->nullable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.anime_theme_entry_notes_help')) + ->showOnPreview() + ->filterable(), + + BelongsToMany::make(__('nova.videos'), AnimeThemeEntry::RELATION_VIDEOS, Video::class) + ->searchable() + ->filterable() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } +} diff --git a/app/Nova/Resources/Wiki/Artist.php b/app/Nova/Resources/Wiki/Artist.php new file mode 100644 index 000000000..b3f826f0c --- /dev/null +++ b/app/Nova/Resources/Wiki/Artist.php @@ -0,0 +1,290 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), ArtistModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->help(__('nova.artist_name_help')) + ->showOnPreview() + ->filterable(), + + Slug::make(__('nova.slug'), ArtistModel::ATTRIBUTE_SLUG) + ->from(ArtistModel::ATTRIBUTE_NAME) + ->separator('_') + ->sortable() + ->rules(['required', 'max:192', 'alpha_dash']) + ->updateRules( + Rule::unique(ArtistModel::TABLE) + ->ignore($request->route('resourceId'), ArtistModel::ATTRIBUTE_ID) + ->__toString() + ) + ->help(__('nova.artist_slug_help')) + ->showOnPreview(), + + BelongsToMany::make(__('nova.songs'), ArtistModel::RELATION_SONGS, Song::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), ArtistSong::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + BelongsToMany::make(__('nova.external_resources'), ArtistModel::RELATION_RESOURCES, ExternalResource::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), ArtistResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + BelongsToMany::make(__('nova.members'), ArtistModel::RELATION_MEMBERS, Artist::class) + ->searchable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), ArtistMember::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + BelongsToMany::make(__('nova.groups'), ArtistModel::RELATION_GROUPS, Artist::class) + ->searchable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), ArtistMember::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + BelongsToMany::make(__('nova.images'), ArtistModel::RELATION_IMAGES, Image::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * Get the cards available for the request. + * + * @param NovaRequest $request + * @return array + */ + public function cards(NovaRequest $request): array + { + return array_merge( + parent::cards($request), + [ + (new NewArtists())->width(Card::ONE_HALF_WIDTH), + (new ArtistsPerDay())->width(Card::ONE_HALF_WIDTH), + ] + ); + } + + /** + * Get the lenses available for the resource. + * + * @param NovaRequest $request + * @return array + */ + public function lenses(NovaRequest $request): array + { + return array_merge( + parent::lenses($request), + [ + new ArtistAniDbResourceLens(), + new ArtistAnilistResourceLens(), + new ArtistAnnResourceLens(), + new ArtistCoverLargeLens(), + new ArtistCoverSmallLens(), + new ArtistMalResourceLens(), + new ArtistSongLens(), + ] + ); + } +} diff --git a/app/Nova/Resources/Wiki/ExternalResource.php b/app/Nova/Resources/Wiki/ExternalResource.php new file mode 100644 index 000000000..d5637e7a3 --- /dev/null +++ b/app/Nova/Resources/Wiki/ExternalResource.php @@ -0,0 +1,273 @@ +sortable() + ->showOnPreview(), + + Select::make(__('nova.site'), ExternalResourceModel::ATTRIBUTE_SITE) + ->options(ResourceSite::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(ResourceSite::class, false)]) + ->help(__('nova.resource_site_help')) + ->showOnPreview() + ->filterable() + ->dependsOn( + [ExternalResourceModel::ATTRIBUTE_LINK], + function (Select $field, NovaRequest $novaRequest, FormData $formData) { + if ($formData->offsetExists(ExternalResourceModel::ATTRIBUTE_LINK)) { + $link = $formData->offsetGet(ExternalResourceModel::ATTRIBUTE_LINK); + $site = ResourceSite::valueOf($link); + $field->value = $site?->value; + } + } + ), + + URL::make(__('nova.link'), ExternalResourceModel::ATTRIBUTE_LINK) + ->sortable() + ->rules(['required', 'max:192', 'url', new ResourceLinkMatchesSiteRule(intval($request->input('site')))]) + ->creationRules(Rule::unique(ExternalResourceModel::TABLE)->__toString()) + ->updateRules( + Rule::unique(ExternalResourceModel::TABLE) + ->ignore($request->route('resourceId'), ExternalResourceModel::ATTRIBUTE_ID) + ->__toString() + ) + ->displayUsing(fn (mixed $value, mixed $resource, string $attribute) => $value) + ->help(__('nova.resource_link_help')) + ->showOnPreview() + ->filterable(), + + Number::make(__('nova.external_id'), ExternalResourceModel::ATTRIBUTE_EXTERNAL_ID) + ->nullable() + ->sortable() + ->rules(['nullable', 'integer']) + ->help(__('nova.resource_external_id_help')) + ->showOnPreview() + ->filterable() + ->dependsOn( + [ExternalResourceModel::ATTRIBUTE_LINK], + function (Text $field, NovaRequest $novaRequest, FormData $formData) { + if ($formData->offsetExists(ExternalResourceModel::ATTRIBUTE_LINK)) { + $link = $formData->offsetGet(ExternalResourceModel::ATTRIBUTE_LINK); + $field->value = ResourceSite::parseIdFromLink($link); + } + } + ), + + BelongsToMany::make(__('nova.artists'), ExternalResourceModel::RELATION_ARTISTS, Artist::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), ArtistResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + BelongsToMany::make(__('nova.anime'), ExternalResourceModel::RELATION_ANIME, Anime::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), AnimeResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + BelongsToMany::make(__('nova.studios'), ExternalResourceModel::RELATION_STUDIOS, Studio::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(function () { + return [ + Text::make(__('nova.as'), StudioResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]; + }), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * Get the lenses available for the resource. + * + * @param NovaRequest $request + * @return array + */ + public function lenses(NovaRequest $request): array + { + return array_merge( + parent::lenses($request), + [ + new ExternalResourceUnlinkedLens(), + ] + ); + } +} diff --git a/app/Nova/Resources/Wiki/Image.php b/app/Nova/Resources/Wiki/Image.php new file mode 100644 index 000000000..025295272 --- /dev/null +++ b/app/Nova/Resources/Wiki/Image.php @@ -0,0 +1,214 @@ +sortable() + ->showOnPreview(), + + Select::make(__('nova.facet'), ImageModel::ATTRIBUTE_FACET) + ->options(ImageFacet::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->sortable() + ->rules(['required', new EnumValue(ImageFacet::class, false)]) + ->help(__('nova.image_facet_help')) + ->showOnPreview() + ->filterable(), + + NovaImage::make(__('nova.image'), ImageModel::ATTRIBUTE_PATH, 'images') + ->creationRules('required') + ->showOnPreview(), + + BelongsToMany::make(__('nova.anime'), ImageModel::RELATION_ANIME, Anime::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + BelongsToMany::make(__('nova.artists'), ImageModel::RELATION_ARTISTS, Artist::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + Panel::make(__('nova.file_properties'), $this->fileProperties()) + ->collapsable(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * @return array + * + * @throws Exception + */ + protected function fileProperties(): array + { + return [ + Text::make(__('nova.path'), ImageModel::ATTRIBUTE_PATH) + ->copyable() + ->hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + ]; + } + + /** + * Get the lenses available for the resource. + * + * @param NovaRequest $request + * @return array + */ + public function lenses(NovaRequest $request): array + { + return array_merge( + parent::lenses($request), + [ + new ImageUnlinkedLens(), + ] + ); + } +} diff --git a/app/Nova/Resources/Wiki/Series.php b/app/Nova/Resources/Wiki/Series.php new file mode 100644 index 000000000..d04158ca4 --- /dev/null +++ b/app/Nova/Resources/Wiki/Series.php @@ -0,0 +1,176 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), SeriesModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->help(__('nova.series_name_help')) + ->showOnPreview() + ->filterable(), + + Slug::make(__('nova.slug'), SeriesModel::ATTRIBUTE_SLUG) + ->from(SeriesModel::ATTRIBUTE_NAME) + ->separator('_') + ->sortable() + ->rules(['required', 'max:192', 'alpha_dash']) + ->updateRules( + Rule::unique(SeriesModel::TABLE) + ->ignore($request->route('resourceId'), SeriesModel::ATTRIBUTE_ID) + ->__toString() + ) + ->help(__('nova.series_slug_help')) + ->showOnPreview(), + + BelongsToMany::make(__('nova.anime'), SeriesModel::RELATION_ANIME, Anime::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * Get the cards available for the request. + * + * @param NovaRequest $request + * @return array + */ + public function cards(NovaRequest $request): array + { + return array_merge( + parent::cards($request), + [ + (new NewSeries())->width(Card::ONE_HALF_WIDTH), + (new SeriesPerDay())->width(Card::ONE_HALF_WIDTH), + ] + ); + } +} diff --git a/app/Nova/Resources/Wiki/Song.php b/app/Nova/Resources/Wiki/Song.php new file mode 100644 index 000000000..13005b580 --- /dev/null +++ b/app/Nova/Resources/Wiki/Song.php @@ -0,0 +1,211 @@ +model(); + if ($song instanceof SongModel) { + $theme = $song->animethemes->first(); + if ($theme instanceof AnimeTheme) { + return $theme->anime->getName(); + } + } + + return null; + } + + /** + * Build a "relatable" query for the given resource. + * + * This query determines which instances of the model may be attached to other resources. + * + * @param NovaRequest $request + * @param Builder $query + * @return Builder + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function relatableQuery(NovaRequest $request, $query): Builder + { + return $query->with(SongModel::RELATION_ANIME); + } + + /** + * Build an "index" query for the given resource. + * + * @param NovaRequest $request + * @param Builder $query + * @return Builder + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function indexQuery(NovaRequest $request, $query): Builder + { + return $query->with(SongModel::RELATION_ANIME); + } + + /** + * The logical group associated with the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function group(): string + { + return __('nova.wiki'); + } + + /** + * Get the displayable label of the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function label(): string + { + return __('nova.songs'); + } + + /** + * Get the displayable singular label of the resource. + * + * @return string + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function singularLabel(): string + { + return __('nova.song'); + } + + /** + * Get the searchable columns for the resource. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function searchableColumns(): array + { + return [ + new Column(SongModel::ATTRIBUTE_TITLE), + ]; + } + + /** + * Get the fields displayed by the resource. + * + * @param NovaRequest $request + * @return array + * + * @throws Exception + */ + public function fields(NovaRequest $request): array + { + return [ + ID::make(__('nova.id'), SongModel::ATTRIBUTE_ID) + ->sortable() + ->showOnPreview(), + + Text::make(__('nova.title'), SongModel::ATTRIBUTE_TITLE) + ->sortable() + ->copyable() + ->nullable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.song_title_help')) + ->showOnPreview() + ->filterable(), + + BelongsToMany::make(__('nova.artists'), SongModel::RELATION_ARTISTS, Artist::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), ArtistSong::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + HasMany::make(__('nova.anime_themes'), SongModel::RELATION_ANIMETHEMES, Theme::class), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * Get the lenses available for the resource. + * + * @param NovaRequest $request + * @return array + */ + public function lenses(NovaRequest $request): array + { + return array_merge( + parent::lenses($request), + [ + new SongArtistLens(), + ] + ); + } +} diff --git a/app/Nova/Resources/Wiki/Studio.php b/app/Nova/Resources/Wiki/Studio.php new file mode 100644 index 000000000..f1eeea5a2 --- /dev/null +++ b/app/Nova/Resources/Wiki/Studio.php @@ -0,0 +1,204 @@ +sortable() + ->showOnPreview(), + + Text::make(__('nova.name'), StudioModel::ATTRIBUTE_NAME) + ->sortable() + ->copyable() + ->rules(['required', 'max:192']) + ->help(__('nova.studio_name_help')) + ->showOnPreview() + ->filterable(), + + Slug::make(__('nova.slug'), StudioModel::ATTRIBUTE_SLUG) + ->from(StudioModel::ATTRIBUTE_NAME) + ->separator('_') + ->sortable() + ->rules(['required', 'max:192', 'alpha_dash']) + ->updateRules( + Rule::unique(StudioModel::TABLE) + ->ignore($request->route('resourceId'), StudioModel::ATTRIBUTE_ID) + ->__toString() + ) + ->help(__('nova.studio_slug_help')) + ->showOnPreview(), + + BelongsToMany::make(__('nova.anime'), StudioModel::RELATION_ANIME, Anime::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + BelongsToMany::make(__('nova.external_resources'), StudioModel::RELATION_RESOURCES, ExternalResource::class) + ->searchable() + ->filterable() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.as'), StudioResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.resource_as_help')), + + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * Get the lenses available for the resource. + * + * @param NovaRequest $request + * @return array + */ + public function lenses(NovaRequest $request): array + { + return array_merge( + parent::lenses($request), + [ + new StudioAniDbResourceLens(), + new StudioAnilistResourceLens(), + new StudioAnimePlanetResourceLens(), + new StudioAnnResourceLens(), + new StudioMalResourceLens(), + new StudioUnlinkedLens(), + ] + ); + } +} diff --git a/app/Nova/Resources/Wiki/Video.php b/app/Nova/Resources/Wiki/Video.php new file mode 100644 index 000000000..5b4164f7c --- /dev/null +++ b/app/Nova/Resources/Wiki/Video.php @@ -0,0 +1,282 @@ +sortable() + ->showOnPreview(), + + Number::make(__('nova.resolution'), VideoModel::ATTRIBUTE_RESOLUTION) + ->sortable() + ->min(360) + ->max(1080) + ->nullable() + ->rules(['nullable', 'integer']) + ->help(__('nova.video_resolution_help')) + ->showOnPreview() + ->filterable(), + + Boolean::make(__('nova.nc'), VideoModel::ATTRIBUTE_NC) + ->sortable() + ->nullable() + ->rules(['nullable', 'boolean']) + ->help(__('nova.video_nc_help')) + ->showOnPreview() + ->filterable(), + + Boolean::make(__('nova.subbed'), VideoModel::ATTRIBUTE_SUBBED) + ->sortable() + ->nullable() + ->rules(['nullable', 'boolean']) + ->help(__('nova.video_subbed_help')) + ->showOnPreview() + ->filterable(), + + Boolean::make(__('nova.lyrics'), VideoModel::ATTRIBUTE_LYRICS) + ->sortable() + ->nullable() + ->rules(['nullable', 'boolean']) + ->help(__('nova.video_lyrics_help')) + ->showOnPreview() + ->filterable(), + + Boolean::make(__('nova.uncen'), VideoModel::ATTRIBUTE_UNCEN) + ->sortable() + ->nullable() + ->rules(['nullable', 'boolean']) + ->help(__('nova.video_uncen_help')) + ->showOnPreview() + ->filterable(), + + Select::make(__('nova.overlap'), VideoModel::ATTRIBUTE_OVERLAP) + ->options(VideoOverlap::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->nullable() + ->sortable() + ->rules(['nullable', new EnumValue(VideoOverlap::class, false)]) + ->help(__('nova.video_overlap_help')) + ->showOnPreview() + ->filterable(), + + Select::make(__('nova.source'), VideoModel::ATTRIBUTE_SOURCE) + ->options(VideoSource::asSelectArray()) + ->displayUsing(fn (?Enum $enum) => $enum?->description) + ->nullable() + ->sortable() + ->rules(['nullable', new EnumValue(VideoSource::class, false)]) + ->help(__('nova.video_source_help')) + ->showOnPreview() + ->filterable(), + + BelongsToMany::make(__('nova.anime_theme_entries'), VideoModel::RELATION_ANIMETHEMEENTRIES, Entry::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->fields(fn () => [ + DateTime::make(__('nova.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating(), + + DateTime::make(__('nova.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating(), + ]), + + Panel::make(__('nova.file_properties'), $this->fileProperties()) + ->collapsable(), + + Panel::make(__('nova.timestamps'), $this->timestamps()) + ->collapsable(), + ]; + } + + /** + * @return array + * + * @throws Exception + */ + protected function fileProperties(): array + { + return [ + Text::make(__('nova.basename'), VideoModel::ATTRIBUTE_BASENAME) + ->copyable() + ->hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.filename'), VideoModel::ATTRIBUTE_FILENAME) + ->sortable() + ->copyable() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.path'), VideoModel::ATTRIBUTE_PATH) + ->copyable() + ->hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + + Number::make(__('nova.size'), VideoModel::ATTRIBUTE_SIZE) + ->hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + + Text::make(__('nova.mimetype'), VideoModel::ATTRIBUTE_MIMETYPE) + ->copyable() + ->hideFromIndex() + ->hideWhenCreating() + ->hideWhenUpdating() + ->showOnPreview() + ->filterable(), + ]; + } + + /** + * Get the cards available for the request. + * + * @param NovaRequest $request + * @return array + */ + public function cards(NovaRequest $request): array + { + return array_merge( + parent::cards($request), + [ + (new NewVideos())->width(Card::ONE_HALF_WIDTH), + (new VideosPerDay())->width(Card::ONE_HALF_WIDTH), + ] + ); + } + + /** + * Get the lenses available for the resource. + * + * @param NovaRequest $request + * @return array + */ + public function lenses(NovaRequest $request): array + { + return array_merge( + parent::lenses($request), + [ + new VideoResolutionLens(), + new VideoSourceLens(), + new VideoUnlinkedLens(), + ] + ); + } +} diff --git a/app/Pipes/Wiki/Anime/BackfillAnimeImage.php b/app/Pipes/Wiki/Anime/BackfillAnimeImage.php new file mode 100644 index 000000000..df0e18804 --- /dev/null +++ b/app/Pipes/Wiki/Anime/BackfillAnimeImage.php @@ -0,0 +1,113 @@ +anime->images()->where(Image::ATTRIBUTE_FACET, $this->getFacet()->value)->exists()) { + Log::info("Anime '{$this->anime->getName()}' already has Image of Facet '{$this->getFacet()->value}'."); + + return $next($user); + } + + $image = $this->getImage(); + + if ($image !== null) { + $this->attachImageToAnime($image); + } + + if ($this->anime->images()->where(Image::ATTRIBUTE_FACET, $this->getFacet()->value)->doesntExist()) { + $this->sendNotification( + $user, + "Anime '{$this->anime->getName()}' has no {$this->getFacet()->description} Image after backfilling. Please review." + ); + } + + return $next($user); + } + + /** + * Create Image from response. + * + * @param string $url + * @return Image + */ + protected function createImage(string $url): Image + { + $imageResponse = Http::get($url); + + $image = $imageResponse->body(); + + $file = File::createWithContent(basename($url), $image); + + $fs = Storage::disk('images'); + + $fsFile = $fs->putFile('', $file); + + return Image::factory()->createOne([ + Image::ATTRIBUTE_FACET => $this->getFacet()->value, + Image::ATTRIBUTE_PATH => $fsFile, + ]); + } + + /** + * Attach Image to Anime. + * + * @param Image $image + * @return void + */ + protected function attachImageToAnime(Image $image): void + { + if (AnimeImage::query() + ->where($this->anime->getKeyName(), $this->anime->getKey()) + ->where($image->getKeyName(), $image->getKey()) + ->doesntExist() + ) { + Log::info("Attaching image '{$image->getName()}' to anime '{$this->anime->getName()}'"); + $image->anime()->attach($this->anime); + } + } + + /** + * Get the facet to backfill. + * + * @return ImageFacet + */ + abstract protected function getFacet(): ImageFacet; + + /** + * Query third-party APIs to find Image. + * + * @return Image|null + * + * @throws RequestException + */ + abstract protected function getImage(): ?Image; +} diff --git a/app/Pipes/Wiki/Anime/BackfillAnimePipe.php b/app/Pipes/Wiki/Anime/BackfillAnimePipe.php new file mode 100644 index 000000000..56ebbb407 --- /dev/null +++ b/app/Pipes/Wiki/Anime/BackfillAnimePipe.php @@ -0,0 +1,46 @@ +notify( + NovaNotification::make() + ->icon('flag') + ->message($message) + ->type(NovaNotification::WARNING_TYPE) + ->url("/resources/$uriKey/{$this->anime->getKey()}") + ); + } +} diff --git a/app/Pipes/Wiki/Anime/BackfillAnimeResource.php b/app/Pipes/Wiki/Anime/BackfillAnimeResource.php new file mode 100644 index 000000000..c41c31159 --- /dev/null +++ b/app/Pipes/Wiki/Anime/BackfillAnimeResource.php @@ -0,0 +1,114 @@ +anime->resources()->where(ExternalResource::ATTRIBUTE_SITE, $this->getSite()->value)->exists()) { + Log::info("Anime '{$this->anime->getName()}' already has Resource of Site '{$this->getSite()->value}'."); + + return $next($user); + } + + $resource = $this->getResource(); + + if ($resource !== null) { + $this->attachResourceToAnime($resource); + } + + if ($this->anime->resources()->where(ExternalResource::ATTRIBUTE_SITE, $this->getSite()->value)->doesntExist()) { + $this->sendNotification( + $user, + "Anime '{$this->anime->getName()}' has no {$this->getSite()->description} Resource after backfilling. Please review." + ); + } + + return $next($user); + } + + /** + * Get or Create Resource from response. + * + * @param int $id + * @param string|null $slug + * @return ExternalResource + */ + protected function getOrCreateResource(int $id, string $slug = null): ExternalResource + { + $resource = ExternalResource::query() + ->select([ExternalResource::ATTRIBUTE_ID, ExternalResource::ATTRIBUTE_LINK]) + ->where(ExternalResource::ATTRIBUTE_SITE, $this->getSite()->value) + ->where(ExternalResource::ATTRIBUTE_EXTERNAL_ID, $id) + ->first(); + + if ($resource === null) { + Log::info("Creating {$this->getSite()->description} resource '$id'"); + + $resource = ExternalResource::query()->create([ + ExternalResource::ATTRIBUTE_EXTERNAL_ID => $id, + ExternalResource::ATTRIBUTE_LINK => ResourceSite::formatAnimeResourceLink($this->getSite(), $id, $slug), + ExternalResource::ATTRIBUTE_SITE => $this->getSite()->value, + ]); + } + + return $resource; + } + + /** + * Attach Resource to Anime. + * + * @param ExternalResource $resource + * @return void + */ + protected function attachResourceToAnime(ExternalResource $resource): void + { + if (AnimeResource::query() + ->where($this->anime->getKeyName(), $this->anime->getKey()) + ->where($resource->getKeyName(), $resource->getKey()) + ->doesntExist() + ) { + Log::info("Attaching resource '{$resource->getName()}' to anime '{$this->anime->getName()}'"); + $resource->anime()->attach($this->anime); + } + } + + /** + * Get the site to backfill. + * + * @return ResourceSite + */ + abstract protected function getSite(): ResourceSite; + + /** + * Query third-party APIs to find Resource mapping. + * + * @return ExternalResource|null + * + * @throws RequestException + */ + abstract protected function getResource(): ?ExternalResource; +} diff --git a/app/Pipes/Wiki/Anime/Image/BackfillLargeCoverImage.php b/app/Pipes/Wiki/Anime/Image/BackfillLargeCoverImage.php new file mode 100644 index 000000000..7ae94a3d3 --- /dev/null +++ b/app/Pipes/Wiki/Anime/Image/BackfillLargeCoverImage.php @@ -0,0 +1,89 @@ +anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + if ($anilistResource instanceof ExternalResource) { + $anidbResource = $this->getAnidbImage($anilistResource); + if ($anidbResource !== null) { + return $anidbResource; + } + } + + return null; + } + + /** + * Query Anilist API for large cover image. + * + * @param ExternalResource $anilistResource + * @return Image|null + * + * @throws RequestException + */ + protected function getAnidbImage(ExternalResource $anilistResource): ?Image + { + $query = ' + query ($id: Int) { + Media (id: $id, type: ANIME) { + coverImage { + extraLarge + } + } + } + '; + + $variables = [ + 'id' => $anilistResource->external_id, + ]; + + $response = Http::post('https://graphql.anilist.co', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $anilistCoverLarge = Arr::get($response, 'data.Media.coverImage.extraLarge'); + if ($anilistCoverLarge !== null) { + return $this->createImage($anilistCoverLarge); + } + + return null; + } +} diff --git a/app/Pipes/Wiki/Anime/Image/BackfillSmallCoverImage.php b/app/Pipes/Wiki/Anime/Image/BackfillSmallCoverImage.php new file mode 100644 index 000000000..390967257 --- /dev/null +++ b/app/Pipes/Wiki/Anime/Image/BackfillSmallCoverImage.php @@ -0,0 +1,89 @@ +anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + if ($anilistResource instanceof ExternalResource) { + $anidbResource = $this->getAnidbImage($anilistResource); + if ($anidbResource !== null) { + return $anidbResource; + } + } + + return null; + } + + /** + * Query Anilist API for large cover image. + * + * @param ExternalResource $anilistResource + * @return Image|null + * + * @throws RequestException + */ + protected function getAnidbImage(ExternalResource $anilistResource): ?Image + { + $query = ' + query ($id: Int) { + Media (id: $id, type: ANIME) { + coverImage { + medium + } + } + } + '; + + $variables = [ + 'id' => $anilistResource->external_id, + ]; + + $response = Http::post('https://graphql.anilist.co', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $anilistCoverSmall = Arr::get($response, 'data.Media.coverImage.medium'); + if ($anilistCoverSmall !== null) { + return $this->createImage($anilistCoverSmall); + } + + return null; + } +} diff --git a/app/Pipes/Wiki/Anime/Resource/BackfillAnidbResource.php b/app/Pipes/Wiki/Anime/Resource/BackfillAnidbResource.php new file mode 100644 index 000000000..424da1356 --- /dev/null +++ b/app/Pipes/Wiki/Anime/Resource/BackfillAnidbResource.php @@ -0,0 +1,97 @@ +anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL); + if ($malResource instanceof ExternalResource) { + $anidbResource = $this->getAnidbMapping($malResource, 'myanimelist'); + if ($anidbResource !== null) { + return $anidbResource; + } + + // failed mapping, sleep before re-attempting + sleep(rand(1, 3)); + } + + $anilistResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + if ($anilistResource instanceof ExternalResource) { + $anidbResource = $this->getAnidbMapping($anilistResource, 'anilist'); + if ($anidbResource !== null) { + return $anidbResource; + } + + // failed mapping, sleep before re-attempting + sleep(rand(1, 3)); + } + + $kitsuResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::KITSU); + if ($kitsuResource instanceof ExternalResource) { + return $this->getAnidbMapping($kitsuResource, 'kitsu'); + } + + return null; + } + + /** + * Query Yuna API for AniDB mapping. + * + * @param ExternalResource $resource + * @param string $source + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getAnidbMapping(ExternalResource $resource, string $source): ?ExternalResource + { + $response = Http::get('https://relations.yuna.moe/api/ids', [ + 'source' => $source, + 'id' => $resource->external_id, + ]) + ->throw() + ->json(); + + $anidbId = Arr::get($response, 'anidb'); + + // Only proceed if we have a match + if ($anidbId !== null) { + return $this->getOrCreateResource($anidbId); + } + + return null; + } +} diff --git a/app/Pipes/Wiki/Anime/Resource/BackfillAnilistResource.php b/app/Pipes/Wiki/Anime/Resource/BackfillAnilistResource.php new file mode 100644 index 000000000..8c179e6e8 --- /dev/null +++ b/app/Pipes/Wiki/Anime/Resource/BackfillAnilistResource.php @@ -0,0 +1,178 @@ +anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL); + if ($malResource instanceof ExternalResource) { + $anilistResource = $this->getMalAnilistMapping($malResource); + if ($anilistResource !== null) { + return $anilistResource; + } + } + + $kitsuResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::KITSU); + if ($kitsuResource instanceof ExternalResource) { + $anilistResource = $this->getKitsuAnilistMapping($kitsuResource); + if ($anilistResource !== null) { + return $anilistResource; + } + } + + $anidbResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIDB); + if ($anidbResource instanceof ExternalResource) { + return $this->getAnidbAnilistMapping($anidbResource); + } + + return null; + } + + /** + * Query Anilist API for MAL mapping. + * + * @param ExternalResource $malResource + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getMalAnilistMapping(ExternalResource $malResource): ?ExternalResource + { + $query = ' + query ($id: Int) { + Media (idMal: $id, type: ANIME) { + id + } + } + '; + + $variables = [ + 'id' => $malResource->external_id, + ]; + + $response = Http::post('https://graphql.anilist.co', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $anilistId = Arr::get($response, 'data.Media.id'); + if ($anilistId === null) { + Log::info("Skipping null Anilist mapping for MAL id '$malResource->external_id'"); + + return null; + } + + return $this->getOrCreateResource($anilistId); + } + + /** + * Query Kitsu API for Anilist mapping. + * + * @param ExternalResource $kitsuResource + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getKitsuAnilistMapping(ExternalResource $kitsuResource): ?ExternalResource + { + $query = ' + query ($id: ID!) { + findAnimeById (id: $id) { + mappings(first:20) { + nodes { + externalId + externalSite + } + } + } + } + '; + + $variables = [ + 'id' => $kitsuResource->external_id, + ]; + + $response = Http::post('https://kitsu.io/api/graphql', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $kitsuMappings = Arr::get($response, 'data.findAnimeById.mappings.nodes', []); + foreach ($kitsuMappings as $kitsuMapping) { + $externalId = Arr::get($kitsuMapping, 'externalId'); + $externalSite = Arr::get($kitsuMapping, 'externalSite'); + if ($externalSite !== 'ANILIST_ANIME' || empty($externalId)) { + Log::info("Skipping mapping of externalId '$externalId' and externalSite '$externalSite'"); + continue; + } + + return $this->getOrCreateResource(intval($externalId)); + } + + return null; + } + + /** + * Query Yuna API for Anilist mapping. + * + * @param ExternalResource $anidbResource + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getAnidbAnilistMapping(ExternalResource $anidbResource): ?ExternalResource + { + $response = Http::get('https://relations.yuna.moe/api/ids', [ + 'source' => 'anidb', + 'id' => $anidbResource->external_id, + ]) + ->throw() + ->json(); + + $anilistId = Arr::get($response, 'anilist'); + + if ($anilistId !== null) { + return $this->getOrCreateResource($anilistId); + } + + return null; + } +} diff --git a/app/Pipes/Wiki/Anime/Resource/BackfillAnnResource.php b/app/Pipes/Wiki/Anime/Resource/BackfillAnnResource.php new file mode 100644 index 000000000..be022fede --- /dev/null +++ b/app/Pipes/Wiki/Anime/Resource/BackfillAnnResource.php @@ -0,0 +1,95 @@ +anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::KITSU); + if ($kitsuResource instanceof ExternalResource) { + return $this->getKitsuAnnMapping($kitsuResource); + } + + return null; + } + + /** + * Query Kitsu API for ANN mapping. + * + * @param ExternalResource $kitsuResource + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getKitsuAnnMapping(ExternalResource $kitsuResource): ?ExternalResource + { + $query = ' + query ($id: ID!) { + findAnimeById (id: $id) { + mappings(first:20) { + nodes { + externalId + externalSite + } + } + } + } + '; + + $variables = [ + 'id' => $kitsuResource->external_id, + ]; + + $response = Http::post('https://kitsu.io/api/graphql', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $kitsuMappings = Arr::get($response, 'data.findAnimeById.mappings.nodes', []); + foreach ($kitsuMappings as $kitsuMapping) { + $externalId = Arr::get($kitsuMapping, 'externalId'); + $externalSite = Arr::get($kitsuMapping, 'externalSite'); + if ($externalSite !== 'ANIMENEWSNETWORK' || empty($externalId)) { + Log::info("Skipping mapping of externalId '$externalId' and externalSite '$externalSite'"); + continue; + } + + return $this->getOrCreateResource(intval($externalId)); + } + + return null; + } +} diff --git a/app/Pipes/Wiki/Anime/Resource/BackfillKitsuResource.php b/app/Pipes/Wiki/Anime/Resource/BackfillKitsuResource.php new file mode 100644 index 000000000..6f125fa62 --- /dev/null +++ b/app/Pipes/Wiki/Anime/Resource/BackfillKitsuResource.php @@ -0,0 +1,131 @@ +anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL); + if ($malResource instanceof ExternalResource) { + $kitsuResource = $this->getKitsuMapping($malResource, 'MYANIMELIST_ANIME'); + if ($kitsuResource !== null) { + return $kitsuResource; + } + + // failed mapping, sleep before re-attempting + sleep(rand(1, 3)); + } + + $anilistResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + if ($anilistResource instanceof ExternalResource) { + $kitsuResource = $this->getKitsuMapping($anilistResource, 'ANILIST_ANIME'); + if ($kitsuResource !== null) { + return $kitsuResource; + } + + // failed mapping, sleep before re-attempting + sleep(rand(1, 3)); + } + + $anidbResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIDB); + if ($anidbResource instanceof ExternalResource) { + $kitsuResource = $this->getKitsuMapping($anidbResource, 'ANIDB'); + if ($kitsuResource !== null) { + return $kitsuResource; + } + + // failed mapping, sleep before re-attempting + sleep(rand(1, 3)); + } + + $annResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANN); + if ($annResource instanceof ExternalResource) { + return $this->getKitsuMapping($annResource, 'ANIMENEWSNETWORK'); + } + + return null; + } + + /** + * Query Kitsu API for MAL mapping. + * + * @param ExternalResource $resource + * @param string $externalSite + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getKitsuMapping(ExternalResource $resource, string $externalSite): ?ExternalResource + { + $query = ' + query ($externalId: ID!, $externalSite: MappingExternalSiteEnum!) { + lookupMapping(externalId: $externalId, externalSite: $externalSite) { + ... on Anime { + id + slug + } + } + } + '; + + $variables = [ + 'externalId' => $resource->external_id, + 'externalSite' => $externalSite, + ]; + + $response = Http::post('https://kitsu.io/api/graphql', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $kitsuMapping = Arr::get($response, 'data.lookupMapping'); + if ($kitsuMapping !== null) { + $id = Arr::get($kitsuMapping, 'id'); + $slug = Arr::get($kitsuMapping, 'slug'); + if (empty($id) || empty($slug)) { + Log::info("Skipping mapping of id '$id' and slug '$slug'"); + + return null; + } + + return $this->getOrCreateResource(intval($id), $slug); + } + + return null; + } +} diff --git a/app/Pipes/Wiki/Anime/Resource/BackfillMalResource.php b/app/Pipes/Wiki/Anime/Resource/BackfillMalResource.php new file mode 100644 index 000000000..9b63597aa --- /dev/null +++ b/app/Pipes/Wiki/Anime/Resource/BackfillMalResource.php @@ -0,0 +1,176 @@ +anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::KITSU); + if ($kitsuResource instanceof ExternalResource) { + $malResource = $this->getKitsuMalMapping($kitsuResource); + if ($malResource !== null) { + return $malResource; + } + } + + $anilistResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + if ($anilistResource instanceof ExternalResource) { + $malResource = $this->getAnilistMalMapping($anilistResource); + if ($malResource !== null) { + return $malResource; + } + } + + $anidbResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANIDB); + if ($anidbResource instanceof ExternalResource) { + return $this->getAnidbMalMapping($anidbResource); + } + + return null; + } + + /** + * Query Kitsu API for MAL mapping. + * + * @param ExternalResource $kitsuResource + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getKitsuMalMapping(ExternalResource $kitsuResource): ?ExternalResource + { + $query = ' + query ($id: ID!) { + findAnimeById (id: $id) { + mappings(first:20) { + nodes { + externalId + externalSite + } + } + } + } + '; + + $variables = [ + 'id' => $kitsuResource->external_id, + ]; + + $response = Http::post('https://kitsu.io/api/graphql', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $kitsuMappings = Arr::get($response, 'data.findAnimeById.mappings.nodes', []); + foreach ($kitsuMappings as $kitsuMapping) { + $externalId = Arr::get($kitsuMapping, 'externalId'); + $externalSite = Arr::get($kitsuMapping, 'externalSite'); + if ($externalSite !== 'MYANIMELIST_ANIME' || empty($externalId)) { + Log::info("Skipping mapping of externalId '$externalId' and externalSite '$externalSite'"); + continue; + } + + return $this->getOrCreateResource(intval($externalId)); + } + + return null; + } + + /** + * Query Anilist API for MAL mapping. + * + * @param ExternalResource $anilistResource + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getAnilistMalMapping(ExternalResource $anilistResource): ?ExternalResource + { + $query = ' + query ($id: Int) { + Media (id: $id, type: ANIME) { + idMal + } + } + '; + + $variables = [ + 'id' => $anilistResource->external_id, + ]; + + $response = Http::post('https://graphql.anilist.co', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $malId = Arr::get($response, 'data.Media.idMal'); + if ($malId === null) { + Log::info("Skipping null MAL mapping for Anilist id '$anilistResource->external_id'"); + + return null; + } + + return $this->getOrCreateResource($malId); + } + + /** + * Query Yuna API for Mal mapping. + * + * @param ExternalResource $anidbResource + * @return ExternalResource|null + * + * @throws RequestException + */ + protected function getAnidbMalMapping(ExternalResource $anidbResource): ?ExternalResource + { + $response = Http::get('https://relations.yuna.moe/api/ids', [ + 'source' => 'anidb', + 'id' => $anidbResource->external_id, + ]) + ->throw() + ->json(); + + $malId = Arr::get($response, 'myanimelist'); + + if ($malId !== null) { + return $this->getOrCreateResource($malId); + } + + return null; + } +} diff --git a/app/Pipes/Wiki/Anime/Studio/BackfillAnimeStudios.php b/app/Pipes/Wiki/Anime/Studio/BackfillAnimeStudios.php new file mode 100644 index 000000000..6849a468a --- /dev/null +++ b/app/Pipes/Wiki/Anime/Studio/BackfillAnimeStudios.php @@ -0,0 +1,380 @@ +anime->studios()->exists()) { + Log::info("Anime '{$this->anime->getName()}' already has Studios."); + + return $next($user); + } + + $studios = $this->getStudios(); + + if (! empty($studios)) { + $this->attachStudiosToAnime($studios); + } + + if ($this->anime->studios()->doesntExist()) { + $this->sendNotification($user, "Anime '{$this->anime->getName()}' has no Studios after backfilling. Please review."); + } + + return $next($user); + } + + /** + * Query third-party API for Anime Studios. + * + * @return Studio[] + * + * @throws RequestException + */ + protected function getStudios(): array + { + $malResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::MAL); + if ($malResource instanceof ExternalResource) { + $studios = $this->getMalAnimeStudios($malResource); + if (! empty($studios)) { + return $studios; + } + } + + $anilistResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST); + if ($anilistResource instanceof ExternalResource) { + $studios = $this->getAnilistAnimeStudios($anilistResource); + if (! empty($studios)) { + return $studios; + } + } + + $kitsuResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::KITSU); + if ($kitsuResource instanceof ExternalResource) { + $studios = $this->getKitsuAnimeStudios($kitsuResource); + if (! empty($studios)) { + return $studios; + } + } + + $annResource = $this->anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANN); + if ($annResource instanceof ExternalResource) { + return $this->getAnnAnimeStudios($annResource); + } + + return []; + } + + /** + * Query MAL API for Anime Studios. + * + * @param ExternalResource $malResource + * @return Studio[] + * + * @throws RequestException + */ + protected function getMalAnimeStudios(ExternalResource $malResource): array + { + $studios = []; + + $malClientID = Config::get('services.mal.client'); + if ($malClientID === null) { + throw new RuntimeException('MAL_CLIENT_ID must be configured in your env file.'); + } + + $response = Http::withHeaders(['X-MAL-CLIENT-ID' => $malClientID]) + ->get("https://api.myanimelist.net/v2/anime/$malResource->external_id", [ + 'fields' => 'studios', + ]) + ->throw() + ->json(); + + $malStudios = Arr::get($response, 'studios', []); + + foreach ($malStudios as $malStudio) { + $name = Arr::get($malStudio, 'name'); + $id = Arr::get($malStudio, 'id'); + if (empty($name) || empty($id)) { + Log::info("Skipping empty studio of name '$name' and id '$id' for MAL Resource '{$malResource->getName()}'"); + continue; + } + + $studio = $this->getOrCreateStudio($name); + + $studios[] = $studio; + + $this->ensureStudioHasResource($studio, ResourceSite::MAL(), $id); + } + + return $studios; + } + + /** + * Query Anilist API for Anime Studios. + * + * @param ExternalResource $anilistResource + * @return Studio[] + * + * @throws RequestException + */ + protected function getAnilistAnimeStudios(ExternalResource $anilistResource): array + { + $studios = []; + + $query = ' + query ($id: Int) { + Media (id: $id, type: ANIME) { + studios (isMain: true) { + nodes { + id + name + } + } + } + } + '; + + $variables = [ + 'id' => $anilistResource->external_id, + ]; + + $response = Http::post('https://graphql.anilist.co', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $anilistStudios = Arr::get($response, 'data.Media.studios.nodes', []); + + foreach ($anilistStudios as $anilistStudio) { + $name = Arr::get($anilistStudio, 'name'); + $id = Arr::get($anilistStudio, 'id'); + if (empty($name) || empty($id)) { + Log::info("Skipping empty studio of name '$name' and id '$id' for Anilist Resource '{$anilistResource->getName()}'"); + continue; + } + + $studio = $this->getOrCreateStudio($name); + + $studios[] = $studio; + + $this->ensureStudioHasResource($studio, ResourceSite::ANILIST(), $id); + } + + return $studios; + } + + /** + * Query Kitsu API for Anime Studios. + * + * @param ExternalResource $kitsuResource + * @return Studio[] + * + * @throws RequestException + */ + protected function getKitsuAnimeStudios(ExternalResource $kitsuResource): array + { + $studios = []; + + $query = ' + query ($id: ID!) { + findAnimeById(id: $id) { + productions(first:20) { + nodes { + role + company { + name + } + } + } + } + } + '; + + $variables = [ + 'id' => $kitsuResource->external_id, + ]; + + $response = Http::post('https://kitsu.io/api/graphql', [ + 'query' => $query, + 'variables' => $variables, + ]) + ->throw() + ->json(); + + $anilistStudios = Arr::get($response, 'data.findAnimeById.productions.nodes', []); + + foreach ($anilistStudios as $anilistStudio) { + $role = Arr::get($anilistStudio, 'role'); + $name = Arr::get($anilistStudio, 'company.name'); + if ($role !== 'STUDIO' || empty($name)) { + Log::info("Skipping production company of name '$name' and role '$role' for Anilist Resource '{$kitsuResource->getName()}'"); + continue; + } + + $studio = $this->getOrCreateStudio($name); + + $studios[] = $studio; + } + + return $studios; + } + + /** + * Query ANN API for Anime Studios. + * + * @param ExternalResource $annResource + * @return Studio[] + * + * @throws RequestException + */ + protected function getAnnAnimeStudios(ExternalResource $annResource): array + { + $studios = []; + + $response = Http::get("https://cdn.animenewsnetwork.com/encyclopedia/api.xml?anime=$annResource->external_id") + ->throw() + ->body(); + + $xml = XmlParser::extract($response); + + $annCredits = $xml->parse([ + 'credits' => [ + 'uses' => 'anime.credit[task,company>name,company::id>id]', + ], + ]); + + $annStudios = Arr::get($annCredits, 'credits', []); + foreach ($annStudios as $annStudio) { + $task = Arr::get($annStudio, 'task'); + $name = Arr::get($annStudio, 'name'); + $id = Arr::get($annStudio, 'id'); + if ($task !== 'Animation Production' || empty($name) || empty($id)) { + Log::info("Skipping production company of task '$task' and name '$name' and id '$id'"); + continue; + } + + $studio = $this->getOrCreateStudio($name); + + $studios[] = $studio; + + $this->ensureStudioHasResource($studio, ResourceSite::ANN(), intval($id)); + } + + return $studios; + } + + /** + * Get or create Studio from name (case-insensitive). + * + * @param string $name + * @return Studio + */ + protected function getOrCreateStudio(string $name): Studio + { + $column = Studio::ATTRIBUTE_NAME; + $studio = Studio::query() + ->where(DB::raw("lower($column)"), Str::lower($name)) + ->first(); + + if (! $studio instanceof Studio) { + Log::info("Creating studio '$name'"); + + $studio = Studio::query()->create([ + Studio::ATTRIBUTE_NAME => $name, + Studio::ATTRIBUTE_SLUG => Str::slug($name, '_'), + ]); + } + + return $studio; + } + + /** + * Ensure Studio has Resource. + * + * @param Studio $studio + * @param ResourceSite $site + * @param int $id + * @return void + */ + protected function ensureStudioHasResource(Studio $studio, ResourceSite $site, int $id): void + { + $studioResource = ExternalResource::query() + ->where(ExternalResource::ATTRIBUTE_SITE, $site->value) + ->where(ExternalResource::ATTRIBUTE_EXTERNAL_ID, $id) + ->first(); + + if (! $studioResource instanceof ExternalResource) { + Log::info("Creating studio resource with site '$site->value' and id '$id'"); + + $studioResource = ExternalResource::query()->create([ + ExternalResource::ATTRIBUTE_EXTERNAL_ID => $id, + ExternalResource::ATTRIBUTE_LINK => ResourceSite::formatStudioResourceLink($site, $id), + ExternalResource::ATTRIBUTE_SITE => $site->value, + ]); + } + + if (StudioResource::query() + ->where($studio->getKeyName(), $studio->getKey()) + ->where($studioResource->getKeyName(), $studioResource->getKey()) + ->doesntExist() + ) { + Log::info("Attaching resource '$studioResource->link' to studio '{$studio->getName()}'"); + $studioResource->studios()->attach($studio); + } + } + + /** + * Attach Studios to Anime. + * + * @param Studio[] $studios + * @return void + */ + protected function attachStudiosToAnime(array $studios): void + { + $results = $this->anime->studios()->sync(Arr::pluck($studios, Studio::ATTRIBUTE_ID)); + + if (count($results['attached'])) { + Log::info("Attached Studios to anime '{$this->anime->getName()}'", $results['attached']); + } + if (count($results['updated'])) { + Log::info("Updated Studios for anime '{$this->anime->getName()}'", $results['updated']); + } + if (count($results['detached'])) { + Log::info("Detached Studios from anime '{$this->anime->getName()}'", $results['detached']); + } + } +} diff --git a/app/Pivots/AnimeImage.php b/app/Pivots/AnimeImage.php new file mode 100644 index 000000000..15570d4d3 --- /dev/null +++ b/app/Pivots/AnimeImage.php @@ -0,0 +1,67 @@ + AnimeImageCreated::class, + 'deleted' => AnimeImageDeleted::class, + ]; + + /** + * Gets the anime that owns the anime image. + * + * @return BelongsTo + */ + public function anime(): BelongsTo + { + return $this->belongsTo(Anime::class, AnimeImage::ATTRIBUTE_ANIME); + } + + /** + * Gets the image that owns the anime image. + * + * @return BelongsTo + */ + public function image(): BelongsTo + { + return $this->belongsTo(Image::class, AnimeImage::ATTRIBUTE_IMAGE); + } +} diff --git a/app/Pivots/AnimeResource.php b/app/Pivots/AnimeResource.php new file mode 100644 index 000000000..c00bc6de9 --- /dev/null +++ b/app/Pivots/AnimeResource.php @@ -0,0 +1,80 @@ + AnimeResourceCreated::class, + 'deleted' => AnimeResourceDeleted::class, + 'updated' => AnimeResourceUpdated::class, + ]; + + /** + * Gets the anime that owns the anime resource. + * + * @return BelongsTo + */ + public function anime(): BelongsTo + { + return $this->belongsTo(Anime::class, AnimeResource::ATTRIBUTE_ANIME); + } + + /** + * Gets the resource that owns the anime resource. + * + * @return BelongsTo + */ + public function resource(): BelongsTo + { + return $this->belongsTo(ExternalResource::class, AnimeResource::ATTRIBUTE_RESOURCE); + } +} diff --git a/app/Pivots/AnimeSeries.php b/app/Pivots/AnimeSeries.php new file mode 100644 index 000000000..bdb1a5846 --- /dev/null +++ b/app/Pivots/AnimeSeries.php @@ -0,0 +1,67 @@ + AnimeSeriesCreated::class, + 'deleted' => AnimeSeriesDeleted::class, + ]; + + /** + * Gets the anime that owns the anime series. + * + * @return BelongsTo + */ + public function anime(): BelongsTo + { + return $this->belongsTo(Anime::class, AnimeSeries::ATTRIBUTE_ANIME); + } + + /** + * Gets the series that owns the anime series. + * + * @return BelongsTo + */ + public function series(): BelongsTo + { + return $this->belongsTo(Series::class, AnimeSeries::ATTRIBUTE_SERIES); + } +} diff --git a/app/Pivots/AnimeStudio.php b/app/Pivots/AnimeStudio.php new file mode 100644 index 000000000..e9e8c273a --- /dev/null +++ b/app/Pivots/AnimeStudio.php @@ -0,0 +1,67 @@ + AnimeStudioCreated::class, + 'deleted' => AnimeStudioDeleted::class, + ]; + + /** + * Gets the anime that owns the anime studio. + * + * @return BelongsTo + */ + public function anime(): BelongsTo + { + return $this->belongsTo(Anime::class, AnimeStudio::ATTRIBUTE_ANIME); + } + + /** + * Gets the studio that owns the anime studio. + * + * @return BelongsTo + */ + public function studio(): BelongsTo + { + return $this->belongsTo(Studio::class, AnimeStudio::ATTRIBUTE_STUDIO); + } +} diff --git a/app/Pivots/AnimeThemeEntryVideo.php b/app/Pivots/AnimeThemeEntryVideo.php new file mode 100644 index 000000000..d5abab6f0 --- /dev/null +++ b/app/Pivots/AnimeThemeEntryVideo.php @@ -0,0 +1,76 @@ + AnimeThemeEntryAnimeThemeCreatedVideo::class, + 'deleted' => AnimeThemeEntryAnimeThemeDeletedVideo::class, + ]; + + /** + * Gets the video that owns the video entry. + * + * @return BelongsTo + */ + public function video(): BelongsTo + { + return $this->belongsTo(Video::class, AnimeThemeEntryVideo::ATTRIBUTE_VIDEO); + } + + /** + * Gets the entry that owns the video entry. + * + * @return BelongsTo + */ + public function animethemeentry(): BelongsTo + { + return $this->belongsTo(AnimeThemeEntry::class, AnimeThemeEntryVideo::ATTRIBUTE_ENTRY); + } +} diff --git a/app/Pivots/ArtistImage.php b/app/Pivots/ArtistImage.php new file mode 100644 index 000000000..512418fbb --- /dev/null +++ b/app/Pivots/ArtistImage.php @@ -0,0 +1,67 @@ + ArtistImageCreated::class, + 'deleted' => ArtistImageDeleted::class, + ]; + + /** + * Gets the artist that owns the artist image. + * + * @return BelongsTo + */ + public function artist(): BelongsTo + { + return $this->belongsTo(Artist::class, ArtistImage::ATTRIBUTE_ARTIST); + } + + /** + * Gets the image that owns the artist image. + * + * @return BelongsTo + */ + public function image(): BelongsTo + { + return $this->belongsTo(Image::class, ArtistImage::ATTRIBUTE_IMAGE); + } +} diff --git a/app/Pivots/ArtistMember.php b/app/Pivots/ArtistMember.php new file mode 100644 index 000000000..804809b46 --- /dev/null +++ b/app/Pivots/ArtistMember.php @@ -0,0 +1,79 @@ + ArtistMemberCreated::class, + 'deleted' => ArtistMemberDeleted::class, + 'updated' => ArtistMemberUpdated::class, + ]; + + /** + * Gets the artist that owns the artist member. + * + * @return BelongsTo + */ + public function artist(): BelongsTo + { + return $this->belongsTo(Artist::class, ArtistMember::ATTRIBUTE_ARTIST); + } + + /** + * Gets the member that owns the artist member. + * + * @return BelongsTo + */ + public function member(): BelongsTo + { + return $this->belongsTo(Artist::class, ArtistMember::ATTRIBUTE_MEMBER); + } +} diff --git a/app/Pivots/ArtistResource.php b/app/Pivots/ArtistResource.php new file mode 100644 index 000000000..97b47b6f7 --- /dev/null +++ b/app/Pivots/ArtistResource.php @@ -0,0 +1,80 @@ + ArtistResourceCreated::class, + 'deleted' => ArtistResourceDeleted::class, + 'updated' => ArtistResourceUpdated::class, + ]; + + /** + * Gets the artist that owns the artist resource. + * + * @return BelongsTo + */ + public function artist(): BelongsTo + { + return $this->belongsTo(Artist::class, ArtistResource::ATTRIBUTE_ARTIST); + } + + /** + * Gets the resource that owns the artist resource. + * + * @return BelongsTo + */ + public function resource(): BelongsTo + { + return $this->belongsTo(ExternalResource::class, ArtistResource::ATTRIBUTE_RESOURCE); + } +} diff --git a/app/Pivots/ArtistSong.php b/app/Pivots/ArtistSong.php new file mode 100644 index 000000000..47cc4cc8f --- /dev/null +++ b/app/Pivots/ArtistSong.php @@ -0,0 +1,80 @@ + ArtistSongCreated::class, + 'deleted' => ArtistSongDeleted::class, + 'updated' => ArtistSongUpdated::class, + ]; + + /** + * Gets the artist that owns the artist song. + * + * @return BelongsTo + */ + public function artist(): BelongsTo + { + return $this->belongsTo(Artist::class, ArtistSong::ATTRIBUTE_ARTIST); + } + + /** + * Gets the song that owns the artist song. + * + * @return BelongsTo + */ + public function song(): BelongsTo + { + return $this->belongsTo(Song::class, ArtistSong::ATTRIBUTE_SONG); + } +} diff --git a/app/Pivots/BasePivot.php b/app/Pivots/BasePivot.php new file mode 100644 index 000000000..397da0755 --- /dev/null +++ b/app/Pivots/BasePivot.php @@ -0,0 +1,31 @@ + StudioResourceCreated::class, + 'deleted' => StudioResourceDeleted::class, + 'updated' => StudioResourceUpdated::class, + ]; + + /** + * Gets the studio that owns the studio resource. + * + * @return BelongsTo + */ + public function studio(): BelongsTo + { + return $this->belongsTo(Studio::class, StudioResource::ATTRIBUTE_STUDIO); + } + + /** + * Gets the resource that owns the studio resource. + * + * @return BelongsTo + */ + public function resource(): BelongsTo + { + return $this->belongsTo(ExternalResource::class, StudioResource::ATTRIBUTE_RESOURCE); + } +} diff --git a/app/Policies/Admin/AnnouncementPolicy.php b/app/Policies/Admin/AnnouncementPolicy.php new file mode 100644 index 000000000..021c8cd58 --- /dev/null +++ b/app/Policies/Admin/AnnouncementPolicy.php @@ -0,0 +1,93 @@ +can('view announcement'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view announcement'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create announcement'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update announcement'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete announcement'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore announcement'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete announcement'); + } +} diff --git a/app/Policies/Auth/InvitationPolicy.php b/app/Policies/Auth/InvitationPolicy.php new file mode 100644 index 000000000..b8d25fa8a --- /dev/null +++ b/app/Policies/Auth/InvitationPolicy.php @@ -0,0 +1,93 @@ +can('view invitation'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view invitation'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create invitation'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update invitation'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete invitation'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore invitation'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete invitation'); + } +} diff --git a/app/Policies/Auth/PermissionPolicy.php b/app/Policies/Auth/PermissionPolicy.php new file mode 100644 index 000000000..cbd69ac5d --- /dev/null +++ b/app/Policies/Auth/PermissionPolicy.php @@ -0,0 +1,153 @@ +can('view permission'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view permission'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create permission'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update permission'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete permission'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore permission'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete permission'); + } + + /** + * Determine whether the user can attach any role to the permission. + * + * @return bool + */ + public function attachAnyRole(): bool + { + return false; + } + + /** + * Determine whether the user can attach a role to the permission. + * + * @return bool + */ + public function attachRole(): bool + { + return false; + } + + /** + * Determine whether the user can detach a role from the permission. + * + * @return bool + */ + public function detachRole(): bool + { + return false; + } + + /** + * Determine whether the user can attach any user to the permission. + * + * @return bool + */ + public function attachAnyUser(): bool + { + return false; + } + + /** + * Determine whether the user can attach a user to the permission. + * + * @return bool + */ + public function attachUser(): bool + { + return false; + } + + /** + * Determine whether the user can detach a user from the permission. + * + * @return bool + */ + public function detachUser(): bool + { + return false; + } +} diff --git a/app/Policies/Auth/RolePolicy.php b/app/Policies/Auth/RolePolicy.php new file mode 100644 index 000000000..5d59f20cd --- /dev/null +++ b/app/Policies/Auth/RolePolicy.php @@ -0,0 +1,153 @@ +can('view role'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view role'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create role'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update role'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete role'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore role'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete role'); + } + + /** + * Determine whether the user can attach any permission to the role. + * + * @return bool + */ + public function attachAnyPermission(): bool + { + return false; + } + + /** + * Determine whether the user can attach a permission to the role. + * + * @return bool + */ + public function attachPermission(): bool + { + return false; + } + + /** + * Determine whether the user can detach a permission from the role. + * + * @return bool + */ + public function detachPermission(): bool + { + return false; + } + + /** + * Determine whether the user can attach any user to the role. + * + * @return bool + */ + public function attachAnyUser(): bool + { + return false; + } + + /** + * Determine whether the user can attach a user to the role. + * + * @return bool + */ + public function attachUser(): bool + { + return false; + } + + /** + * Determine whether the user can detach a user from the role. + * + * @return bool + */ + public function detachUser(): bool + { + return false; + } +} diff --git a/app/Policies/Auth/UserPolicy.php b/app/Policies/Auth/UserPolicy.php new file mode 100644 index 000000000..d443be59e --- /dev/null +++ b/app/Policies/Auth/UserPolicy.php @@ -0,0 +1,153 @@ +can('view user'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view user'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create user'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update user'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete user'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore user'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete user'); + } + + /** + * Determine whether the user can attach any role to the user. + * + * @return bool + */ + public function attachAnyRole(): bool + { + return false; + } + + /** + * Determine whether the user can attach a role to the user. + * + * @return bool + */ + public function attachRole(): bool + { + return false; + } + + /** + * Determine whether the user can detach a role from the user. + * + * @return bool + */ + public function detachRole(): bool + { + return false; + } + + /** + * Determine whether the user can attach any permission to the user. + * + * @return bool + */ + public function attachAnyPermission(): bool + { + return false; + } + + /** + * Determine whether the user can attach a permission to the user. + * + * @return bool + */ + public function attachPermission(): bool + { + return false; + } + + /** + * Determine whether the user can detach a permission from the user. + * + * @return bool + */ + public function detachPermission(): bool + { + return false; + } +} diff --git a/app/Policies/Billing/BalancePolicy.php b/app/Policies/Billing/BalancePolicy.php new file mode 100644 index 000000000..b52600168 --- /dev/null +++ b/app/Policies/Billing/BalancePolicy.php @@ -0,0 +1,93 @@ +can('view balance'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view balance'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create balance'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update balance'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete balance'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore balance'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete balance'); + } +} diff --git a/app/Policies/Billing/TransactionPolicy.php b/app/Policies/Billing/TransactionPolicy.php new file mode 100644 index 000000000..c9c5f2ef0 --- /dev/null +++ b/app/Policies/Billing/TransactionPolicy.php @@ -0,0 +1,93 @@ +can('view transaction'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view transaction'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create transaction'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update transaction'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete transaction'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore transaction'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete transaction'); + } +} diff --git a/app/Policies/Document/PagePolicy.php b/app/Policies/Document/PagePolicy.php new file mode 100644 index 000000000..1c427e2f5 --- /dev/null +++ b/app/Policies/Document/PagePolicy.php @@ -0,0 +1,93 @@ +can('view page'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view page'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create page'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update page'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete page'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore page'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete page'); + } +} diff --git a/app/Policies/Wiki/Anime/AnimeSynonymPolicy.php b/app/Policies/Wiki/Anime/AnimeSynonymPolicy.php new file mode 100644 index 000000000..09fcbf36b --- /dev/null +++ b/app/Policies/Wiki/Anime/AnimeSynonymPolicy.php @@ -0,0 +1,93 @@ +can('view anime synonym'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view anime synonym'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create anime synonym'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update anime synonym'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete anime synonym'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore anime synonym'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete anime synonym'); + } +} diff --git a/app/Policies/Wiki/Anime/AnimeThemePolicy.php b/app/Policies/Wiki/Anime/AnimeThemePolicy.php new file mode 100644 index 000000000..3c3b3ed0d --- /dev/null +++ b/app/Policies/Wiki/Anime/AnimeThemePolicy.php @@ -0,0 +1,93 @@ +can('view anime theme'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view anime theme'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create anime theme'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update anime theme'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete anime theme'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore anime theme'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete anime theme'); + } +} diff --git a/app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php b/app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php new file mode 100644 index 000000000..80ea78f9c --- /dev/null +++ b/app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php @@ -0,0 +1,136 @@ +can('view anime theme entry'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view anime theme entry'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create anime theme entry'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update anime theme entry'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete anime theme entry'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore anime theme entry'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete anime theme entry'); + } + + /** + * Determine whether the user can attach any video to the entry. + * + * @param User $user + * @return bool + */ + public function attachAnyVideo(User $user): bool + { + return $user->can('update anime theme entry'); + } + + /** + * Determine whether the user can attach a video to the entry. + * + * @param User $user + * @param AnimeThemeEntry $entry + * @param Video $video + * @return bool + */ + public function attachVideo(User $user, AnimeThemeEntry $entry, Video $video): bool + { + $attached = AnimeThemeEntryVideo::query() + ->where($entry->getKeyName(), $entry->getKey()) + ->where($video->getKeyName(), $video->getKey()) + ->exists(); + + return ! $attached && $user->can('update anime theme entry'); + } + + /** + * Determine whether the user can detach a video from the entry. + * + * @param User $user + * @return bool + */ + public function detachVideo(User $user): bool + { + return $user->can('update anime theme entry'); + } +} diff --git a/app/Policies/Wiki/AnimePolicy.php b/app/Policies/Wiki/AnimePolicy.php new file mode 100644 index 000000000..f341364e9 --- /dev/null +++ b/app/Policies/Wiki/AnimePolicy.php @@ -0,0 +1,253 @@ +can('view anime'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view anime'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create anime'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete anime'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore anime'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete anime'); + } + + /** + * Determine whether the user can attach any series to the anime. + * + * @param User $user + * @return bool + */ + public function attachAnySeries(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can attach a series to the anime. + * + * @param User $user + * @param Anime $anime + * @param Series $series + * @return bool + */ + public function attachSeries(User $user, Anime $anime, Series $series): bool + { + $attached = AnimeSeries::query() + ->where($anime->getKeyName(), $anime->getKey()) + ->where($series->getKeyName(), $series->getKey()) + ->exists(); + + return ! $attached && $user->can('update anime'); + } + + /** + * Determine whether the user can detach a series from the anime. + * + * @param User $user + * @return bool + */ + public function detachSeries(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can attach any resource to the anime. + * + * @param User $user + * @return bool + */ + public function attachAnyExternalResource(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can attach a resource to the anime. + * + * @param User $user + * @return bool + */ + public function attachExternalResource(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can detach a resource from the anime. + * + * @param User $user + * @return bool + */ + public function detachExternalResource(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can attach any image to the anime. + * + * @param User $user + * @return bool + */ + public function attachAnyImage(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can attach an image to the anime. + * + * @param User $user + * @param Anime $anime + * @param Image $image + * @return bool + */ + public function attachImage(User $user, Anime $anime, Image $image): bool + { + $attached = AnimeImage::query() + ->where($anime->getKeyName(), $anime->getKey()) + ->where($image->getKeyName(), $image->getKey()) + ->exists(); + + return ! $attached && $user->can('update anime'); + } + + /** + * Determine whether the user can detach an image from the anime. + * + * @param User $user + * @return bool + */ + public function detachImage(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can attach any studio to the anime. + * + * @param User $user + * @return bool + */ + public function attachAnyStudio(User $user): bool + { + return $user->can('update anime'); + } + + /** + * Determine whether the user can attach a studio to the anime. + * + * @param User $user + * @param Anime $anime + * @param Studio $studio + * @return bool + */ + public function attachStudio(User $user, Anime $anime, Studio $studio): bool + { + $attached = AnimeStudio::query() + ->where($anime->getKeyName(), $anime->getKey()) + ->where($studio->getKeyName(), $studio->getKey()) + ->exists(); + + return ! $attached && $user->can('update anime'); + } + + /** + * Determine whether the user can detach a studio from the anime. + * + * @param User $user + * @return bool + */ + public function detachStudio(User $user): bool + { + return $user->can('update anime'); + } +} diff --git a/app/Policies/Wiki/ArtistPolicy.php b/app/Policies/Wiki/ArtistPolicy.php new file mode 100644 index 000000000..828b7ce66 --- /dev/null +++ b/app/Policies/Wiki/ArtistPolicy.php @@ -0,0 +1,235 @@ +can('view artist'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view artist'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create artist'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete artist'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore artist'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete artist'); + } + + /** + * Determine whether the user can attach any resource to the artist. + * + * @param User $user + * @return bool + */ + public function attachAnyExternalResource(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can attach a resource to the artist. + * + * @param User $user + * @return bool + */ + public function attachExternalResource(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can detach a resource from the artist. + * + * @param User $user + * @return bool + */ + public function detachExternalResource(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can attach any song to the artist. + * + * @param User $user + * @return bool + */ + public function attachAnySong(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can attach a song to the artist. + * + * @param User $user + * @return bool + */ + public function attachSong(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can detach a song from the artist. + * + * @param User $user + * @return bool + */ + public function detachSong(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can attach any group/member to the artist. + * + * @param User $user + * @return bool + */ + public function attachAnyArtist(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can attach a group/member to the artist. + * + * @param User $user + * @return bool + */ + public function attachArtist(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can detach a group/member from the artist. + * + * @param User $user + * @return bool + */ + public function detachArtist(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can attach any image to the artist. + * + * @param User $user + * @return bool + */ + public function attachAnyImage(User $user): bool + { + return $user->can('update artist'); + } + + /** + * Determine whether the user can attach an image to the artist. + * + * @param User $user + * @param Artist $artist + * @param Image $image + * @return bool + */ + public function attachImage(User $user, Artist $artist, Image $image): bool + { + $attached = ArtistImage::query() + ->where($artist->getKeyName(), $artist->getKey()) + ->where($image->getKeyName(), $image->getKey()) + ->exists(); + + return ! $attached && $user->can('update artist'); + } + + /** + * Determine whether the user can detach an image from the artist. + * + * @param User $user + * @return bool + */ + public function detachImage(User $user): bool + { + return $user->can('update artist'); + } +} diff --git a/app/Policies/Wiki/ExternalResourcePolicy.php b/app/Policies/Wiki/ExternalResourcePolicy.php new file mode 100644 index 000000000..c91a0f963 --- /dev/null +++ b/app/Policies/Wiki/ExternalResourcePolicy.php @@ -0,0 +1,192 @@ +can('view external resource'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view external resource'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create external resource'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete external resource'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore external resource'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete external resource'); + } + + /** + * Determine whether the user can attach any artist to the resource. + * + * @param User $user + * @return bool + */ + public function attachAnyArtist(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can attach an artist to the resource. + * + * @param User $user + * @return bool + */ + public function attachArtist(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can detach an artist from the resource. + * + * @param User $user + * @return bool + */ + public function detachArtist(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can attach any anime to the resource. + * + * @param User $user + * @return bool + */ + public function attachAnyAnime(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can attach an anime to the resource. + * + * @param User $user + * @return bool + */ + public function attachAnime(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can detach an anime from the resource. + * + * @param User $user + * @return bool + */ + public function detachAnime(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can attach any studio to the resource. + * + * @param User $user + * @return bool + */ + public function attachAnyStudio(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can attach a studio to the resource. + * + * @param User $user + * @return bool + */ + public function attachStudio(User $user): bool + { + return $user->can('update external resource'); + } + + /** + * Determine whether the user can detach a studio from the resource. + * + * @param User $user + * @return bool + */ + public function detachStudio(User $user): bool + { + return $user->can('update external resource'); + } +} diff --git a/app/Policies/Wiki/ImagePolicy.php b/app/Policies/Wiki/ImagePolicy.php new file mode 100644 index 000000000..7375e8dc7 --- /dev/null +++ b/app/Policies/Wiki/ImagePolicy.php @@ -0,0 +1,178 @@ +can('view image'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view image'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create image'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update image'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete image'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore image'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete image'); + } + + /** + * Determine whether the user can attach any artist to the image. + * + * @param User $user + * @return bool + */ + public function attachAnyArtist(User $user): bool + { + return $user->can('update image'); + } + + /** + * Determine whether the user can attach an artist to the image. + * + * @param User $user + * @param Image $image + * @param Artist $artist + * @return bool + */ + public function attachArtist(User $user, Image $image, Artist $artist): bool + { + $attached = ArtistImage::query() + ->where($artist->getKeyName(), $artist->getKey()) + ->where($image->getKeyName(), $image->getKey()) + ->exists(); + + return ! $attached && $user->can('update image'); + } + + /** + * Determine whether the user can detach an artist from the image. + * + * @param User $user + * @return bool + */ + public function detachArtist(User $user): bool + { + return $user->can('update image'); + } + + /** + * Determine whether the user can attach any anime to the image. + * + * @param User $user + * @return bool + */ + public function attachAnyAnime(User $user): bool + { + return $user->can('update image'); + } + + /** + * Determine whether the user can attach an anime to the image. + * + * @param User $user + * @param Image $image + * @param Anime $anime + * @return bool + */ + public function attachAnime(User $user, Image $image, Anime $anime): bool + { + $attached = AnimeImage::query() + ->where($anime->getKeyName(), $anime->getKey()) + ->where($image->getKeyName(), $image->getKey()) + ->exists(); + + return ! $attached && $user->can('update image'); + } + + /** + * Determine whether the user can detach an anime from the image. + * + * @param User $user + * @return bool + */ + public function detachAnime(User $user): bool + { + return $user->can('update image'); + } +} diff --git a/app/Policies/Wiki/SeriesPolicy.php b/app/Policies/Wiki/SeriesPolicy.php new file mode 100644 index 000000000..cb9d28135 --- /dev/null +++ b/app/Policies/Wiki/SeriesPolicy.php @@ -0,0 +1,136 @@ +can('view series'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view series'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create series'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update series'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete series'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore series'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete series'); + } + + /** + * Determine whether the user can attach any anime to the series. + * + * @param User $user + * @return bool + */ + public function attachAnyAnime(User $user): bool + { + return $user->can('update series'); + } + + /** + * Determine whether the user can attach an anime to the series. + * + * @param User $user + * @param Series $series + * @param Anime $anime + * @return bool + */ + public function attachAnime(User $user, Series $series, Anime $anime): bool + { + $attached = AnimeSeries::query() + ->where($anime->getKeyName(), $anime->getKey()) + ->where($series->getKeyName(), $series->getKey()) + ->exists(); + + return ! $attached && $user->can('update series'); + } + + /** + * Determine whether the user can detach an anime from the series. + * + * @param User $user + * @return bool + */ + public function detachAnime(User $user): bool + { + return $user->can('update series'); + } +} diff --git a/app/Policies/Wiki/SongPolicy.php b/app/Policies/Wiki/SongPolicy.php new file mode 100644 index 000000000..65f74a274 --- /dev/null +++ b/app/Policies/Wiki/SongPolicy.php @@ -0,0 +1,137 @@ +can('view song'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view song'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create song'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update song'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete song'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore song'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete song'); + } + + /** + * Determine whether the user can add a theme to the song. + * + * @param User $user + * @return bool + */ + public function addAnimeTheme(User $user): bool + { + return $user->can('update song'); + } + + /** + * Determine whether the user can attach any artist to the song. + * + * @param User $user + * @return bool + */ + public function attachAnyArtist(User $user): bool + { + return $user->can('update song'); + } + + /** + * Determine whether the user can attach an artist to the song. + * + * @param User $user + * @return bool + */ + public function attachArtist(User $user): bool + { + return $user->can('update song'); + } + + /** + * Determine whether the user can detach an artist from the song. + * + * @param User $user + * @return bool + */ + public function detachArtist(User $user): bool + { + return $user->can('update song'); + } +} diff --git a/app/Policies/Wiki/StudioPolicy.php b/app/Policies/Wiki/StudioPolicy.php new file mode 100644 index 000000000..58cad7ff3 --- /dev/null +++ b/app/Policies/Wiki/StudioPolicy.php @@ -0,0 +1,169 @@ +can('view studio'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view studio'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create studio'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update studio'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete studio'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore studio'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete studio'); + } + + /** + * Determine whether the user can attach any anime to the studio. + * + * @param User $user + * @return bool + */ + public function attachAnyAnime(User $user): bool + { + return $user->can('update studio'); + } + + /** + * Determine whether the user can attach an anime to the studio. + * + * @param User $user + * @param Studio $studio + * @param Anime $anime + * @return bool + */ + public function attachAnime(User $user, Studio $studio, Anime $anime): bool + { + $attached = AnimeStudio::query() + ->where($anime->getKeyName(), $anime->getKey()) + ->where($studio->getKeyName(), $studio->getKey()) + ->exists(); + + return ! $attached && $user->can('update studio'); + } + + /** + * Determine whether the user can detach an anime from the studio. + * + * @param User $user + * @return bool + */ + public function detachAnime(User $user): bool + { + return $user->can('update studio'); + } + + /** + * Determine whether the user can attach any resource to the studio. + * + * @param User $user + * @return bool + */ + public function attachAnyExternalResource(User $user): bool + { + return $user->can('update studio'); + } + + /** + * Determine whether the user can attach a resource to the studio. + * + * @param User $user + * @return bool + */ + public function attachExternalResource(User $user): bool + { + return $user->can('update studio'); + } + + /** + * Determine whether the user can detach a resource from the studio. + * + * @param User $user + * @return bool + */ + public function detachExternalResource(User $user): bool + { + return $user->can('update studio'); + } +} diff --git a/app/Policies/Wiki/VideoPolicy.php b/app/Policies/Wiki/VideoPolicy.php new file mode 100644 index 000000000..53448224b --- /dev/null +++ b/app/Policies/Wiki/VideoPolicy.php @@ -0,0 +1,126 @@ +can('view video'); + } + + /** + * Determine whether the user can view the model. + * + * @param User $user + * @return bool + */ + public function view(User $user): bool + { + return $user->can('view video'); + } + + /** + * Determine whether the user can create models. + * + * @param User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create video'); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @return bool + */ + public function update(User $user): bool + { + return $user->can('update video'); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can('delete video'); + } + + /** + * Determine whether the user can restore the model. + * + * @param User $user + * @return bool + */ + public function restore(User $user): bool + { + return $user->can('restore video'); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can('force delete video'); + } + + /** + * Determine whether the user can attach any entry to a video. + * + * @param User $user + * @return bool + */ + public function attachAnyAnimeThemeEntry(User $user): bool + { + return $user->can('update video'); + } + + /** + * Determine whether the user can attach an entry to a video. + * + * @param User $user + * @return bool + */ + public function attachAnimeThemeEntry(User $user): bool + { + return $user->can('update video'); + } + + /** + * Determine whether the user can detach an entry from a video. + * + * @param User $user + * @return bool + */ + public function detachAnimeThemeEntry(User $user): bool + { + return $user->can('update video'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f5fa0f919..dea3c8cc9 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -1,34 +1,31 @@ environment($environment); + Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) { + $class = get_class($model); + + Log::info("Attempted to lazy load [$relation] on model [$class]"); }); } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 30490683b..0123a9859 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -1,30 +1,46 @@ 'App\Policies\ModelPolicy', - ]; - /** * Register any authentication / authorization services. * * @return void */ - public function boot() + public function boot(): void { $this->registerPolicies(); - // + Password::defaults( + fn () => Password::min(8) + ->uncompromised() + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->rules(['confirmed', 'zxcvbn_min:3', 'zxcvbn_dictionary']) + ); + + Gate::guessPolicyNamesUsing( + fn (string $modelClass) => Str::of($modelClass) + ->replace('Models', 'Policies') + ->append('Policy') + ->__toString() + ); + + Gate::define('viewNova', fn (User $user) => $user->can('view nova')); } } diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php index 395c518bc..5774a03cb 100644 --- a/app/Providers/BroadcastServiceProvider.php +++ b/app/Providers/BroadcastServiceProvider.php @@ -1,10 +1,15 @@ [ - SendEmailVerificationNotification::class, - ], - ]; - - /** - * Register any events for your application. + * @return bool * - * @return void + * @noinspection PhpMissingParentCallCommonInspection */ - public function boot() + public function shouldDiscoverEvents(): bool { - parent::boot(); - - // + return true; } } diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php new file mode 100644 index 000000000..0b365a8b4 --- /dev/null +++ b/app/Providers/FortifyServiceProvider.php @@ -0,0 +1,38 @@ + Limit::perMinute(5)->by($request->ip())); + + RateLimiter::for('two-factor', fn (Request $request) => Limit::perMinute(5)->by($request->session()->get('login.id'))); + } +} diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php new file mode 100644 index 000000000..8488314ce --- /dev/null +++ b/app/Providers/HorizonServiceProvider.php @@ -0,0 +1,45 @@ + $user->can('view horizon')); + } +} diff --git a/app/Providers/JetstreamServiceProvider.php b/app/Providers/JetstreamServiceProvider.php new file mode 100644 index 000000000..8015ca3b0 --- /dev/null +++ b/app/Providers/JetstreamServiceProvider.php @@ -0,0 +1,27 @@ +append($profile); + + return $menu; + }); + + // Disable the footer + Nova::footer(fn () => ''); + } + + /** + * Register the Nova routes. + * + * @return void + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function routes(): void + { + Nova::routes()->register(); + } + + /** + * Register the Nova gate. + * + * This gate determines who can access Nova in non-local environments. + * + * @return void + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function gate(): void + { + Gate::define('viewNova', fn (User $user) => $user->can('view nova')); + } + + /** + * Get the cards that should be displayed on the Nova dashboard. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function dashboards(): array + { + return [ + new Main(), + ]; + } + + /** + * Get the tools that should be listed in the Nova sidebar. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public function tools(): array + { + return [ + (new LogViewer()) + ->canSee(fn (Request $request) => $request->user()->can('view logs')), + ]; + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 527eee349..76818d0d5 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -1,80 +1,85 @@ configureRateLimiting(); - parent::boot(); + // Anime Resources + Route::model('animesynonym', AnimeSynonym::class); + Route::model('animetheme', AnimeTheme::class); + + // Anime Theme Resources + Route::model('animethemeentry', AnimeThemeEntry::class); + + $this->routes(function () { + Route::middleware('web') + ->group(base_path('routes/web.php')); + + Route::middleware('web') + ->domain(Config::get('video.url')) + ->prefix(Config::get('video.path')) + ->group(base_path('routes/video.php')); + + Route::middleware('api') + ->domain(Config::get('api.url')) + ->prefix(Config::get('api.path')) + ->as('api.') + ->group(base_path('routes/api.php')); + }); } /** - * Define the routes for the application. + * Configure the rate limiters for the application. * * @return void */ - public function map() + protected function configureRateLimiting(): void { - $this->mapApiRoutes(); + RateLimiter::for('api', function (Request $request) { + // Allow the client to bypass API rate limiting + $user = $request->user('sanctum'); + if ($user instanceof User && $user->can('bypass api rate limiter')) { + return Limit::none(); + } - $this->mapWebRoutes(); - - // - } - - /** - * Define the "web" routes for the application. - * - * These routes all receive session state, CSRF protection, etc. - * - * @return void - */ - protected function mapWebRoutes() - { - Route::middleware('web') - ->namespace($this->namespace) - ->group(base_path('routes/web.php')); - } - - /** - * Define the "api" routes for the application. - * - * These routes are typically stateless. - * - * @return void - */ - protected function mapApiRoutes() - { - Route::prefix('api') - ->middleware('api') - ->namespace($this->namespace) - ->group(base_path('routes/api.php')); + return Limit::perMinute(90)->by(Auth::check() ? Auth::id() : $request->ip()); + }); } } diff --git a/app/Providers/TelescopeServiceProvider.php b/app/Providers/TelescopeServiceProvider.php new file mode 100644 index 000000000..71daacd53 --- /dev/null +++ b/app/Providers/TelescopeServiceProvider.php @@ -0,0 +1,77 @@ +hideSensitiveRequestDetails(); + + Telescope::filter(function (IncomingEntry $entry) { + if ($this->app->environment('local') || $this->app->environment('staging')) { + return true; + } + + return $entry->isReportableException() || + $entry->isFailedRequest() || + $entry->isFailedJob() || + $entry->isScheduledTask() || + $entry->hasMonitoredTag(); + }); + } + + /** + * Prevent sensitive request details from being logged by Telescope. + * + * @return void + */ + protected function hideSensitiveRequestDetails(): void + { + if ($this->app->environment('local')) { + return; + } + + Telescope::hideRequestParameters(['_token']); + + Telescope::hideRequestHeaders([ + 'cookie', + 'x-csrf-token', + 'x-xsrf-token', + ]); + } + + /** + * Register the Telescope gate. + * + * This gate determines who can access Telescope in non-local environments. + * + * @return void + * + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function gate(): void + { + Gate::define('viewTelescope', fn (User $user) => $user->can('view telescope')); + } +} diff --git a/app/Repositories/Eloquent/Billing/DigitalOceanBalanceRepository.php b/app/Repositories/Eloquent/Billing/DigitalOceanBalanceRepository.php new file mode 100644 index 000000000..ec9e339c5 --- /dev/null +++ b/app/Repositories/Eloquent/Billing/DigitalOceanBalanceRepository.php @@ -0,0 +1,58 @@ +where(Balance::ATTRIBUTE_SERVICE, Service::DIGITALOCEAN) + ->whereMonth(Balance::ATTRIBUTE_DATE, ComparisonOperator::EQ, $now) + ->whereYear(Balance::ATTRIBUTE_DATE, ComparisonOperator::EQ, $now); + } + + /** + * Validate repository filter. + * + * @param string $filter + * @param mixed $value + * @return bool + */ + public function validateFilter(string $filter, mixed $value = null): bool + { + // not supported + return false; + } + + /** + * Filter repository models. + * + * @param string $filter + * @param mixed $value + * @return void + */ + public function handleFilter(string $filter, mixed $value = null): void + { + // not supported + } +} diff --git a/app/Repositories/Eloquent/Billing/DigitalOceanTransactionRepository.php b/app/Repositories/Eloquent/Billing/DigitalOceanTransactionRepository.php new file mode 100644 index 000000000..914ea9d07 --- /dev/null +++ b/app/Repositories/Eloquent/Billing/DigitalOceanTransactionRepository.php @@ -0,0 +1,51 @@ +where(Transaction::ATTRIBUTE_SERVICE, Service::DIGITALOCEAN); + } + + /** + * Validate repository filter. + * + * @param string $filter + * @param mixed $value + * @return bool + */ + public function validateFilter(string $filter, mixed $value = null): bool + { + // not supported + return false; + } + + /** + * Filter repository models. + * + * @param string $filter + * @param mixed $value + * @return void + */ + public function handleFilter(string $filter, mixed $value = null): void + { + // not supported + } +} diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php new file mode 100644 index 000000000..71d51b6cf --- /dev/null +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -0,0 +1,83 @@ +query = $this->builder(); + } + + /** + * Get models from the repository. + * + * @param array $columns + * @return Collection + */ + public function get(array $columns = ['*']): Collection + { + return $this->query->get($columns); + } + + /** + * Save model to the repository. + * + * @param Model $model + * @return bool + */ + public function save(Model $model): bool + { + return $model->save(); + } + + /** + * Delete model from the repository. + * + * @param Model $model + * @return bool + */ + public function delete(Model $model): bool + { + return $model->delete(); + } + + /** + * Update model in the repository. + * + * @param Model $model + * @param array $attributes + * @return bool + */ + public function update(Model $model, array $attributes): bool + { + return $model->update($attributes); + } + + /** + * Get the underlying query builder. + * + * @return Builder + */ + abstract protected function builder(): Builder; +} diff --git a/app/Repositories/Eloquent/Wiki/VideoRepository.php b/app/Repositories/Eloquent/Wiki/VideoRepository.php new file mode 100644 index 000000000..7fc873d72 --- /dev/null +++ b/app/Repositories/Eloquent/Wiki/VideoRepository.php @@ -0,0 +1,57 @@ +query->where(Video::ATTRIBUTE_PATH, ComparisonOperator::LIKE, "$value%"); + } + } +} diff --git a/app/Repositories/Service/DigitalOcean/Billing/DigitalOceanBalanceRepository.php b/app/Repositories/Service/DigitalOcean/Billing/DigitalOceanBalanceRepository.php new file mode 100644 index 000000000..8f727ab01 --- /dev/null +++ b/app/Repositories/Service/DigitalOcean/Billing/DigitalOceanBalanceRepository.php @@ -0,0 +1,120 @@ +contentType('application/json') + ->get('https://api.digitalocean.com/v2/customers/my/balance') + ->throw() + ->json(); + + $balance = Balance::factory()->makeOne([ + Balance::ATTRIBUTE_BALANCE => -1.0 * floatval(Arr::get($response, 'month_to_date_balance')), + Balance::ATTRIBUTE_DATE => Date::now()->firstOfMonth()->format(AllowedDateFormat::YMD), + Balance::ATTRIBUTE_FREQUENCY => BalanceFrequency::MONTHLY, + Balance::ATTRIBUTE_SERVICE => Service::DIGITALOCEAN, + Balance::ATTRIBUTE_USAGE => Arr::get($response, 'month_to_date_usage'), + ]); + + return collect([$balance]); + } + + /** + * Save model to the repository. + * + * @param Model $model + * @return bool + */ + public function save(Model $model): bool + { + // Billing API is not writable + return false; + } + + /** + * Delete model from the repository. + * + * @param Model $model + * @return bool + */ + public function delete(Model $model): bool + { + // Billing API is not writable + return false; + } + + /** + * Update model in the repository. + * + * @param Model $model + * @param array $attributes + * @return bool + */ + public function update(Model $model, array $attributes): bool + { + // Billing API is not writable + return false; + } + + /** + * Validate repository filter. + * + * @param string $filter + * @param mixed $value + * @return bool + */ + public function validateFilter(string $filter, mixed $value = null): bool + { + // not supported + return false; + } + + /** + * Filter repository models. + * + * @param string $filter + * @param mixed $value + * @return void + */ + public function handleFilter(string $filter, mixed $value = null): void + { + // not supported + } +} diff --git a/app/Repositories/Service/DigitalOcean/Billing/DigitalOceanTransactionRepository.php b/app/Repositories/Service/DigitalOcean/Billing/DigitalOceanTransactionRepository.php new file mode 100644 index 000000000..63969070e --- /dev/null +++ b/app/Repositories/Service/DigitalOcean/Billing/DigitalOceanTransactionRepository.php @@ -0,0 +1,136 @@ +contentType('application/json'); + + $nextBillingHistory = 'https://api.digitalocean.com/v2/customers/my/billing_history?per_page=200'; + while (! empty($nextBillingHistory)) { + // Try not to upset DO + sleep(rand(2, 5)); + + $response = $request->get($nextBillingHistory)->throw()->json(); + + $billingHistory = Arr::get($response, 'billing_history', []); + foreach ($billingHistory as $sourceTransaction) { + $date = DateTime::createFromFormat( + '!'.DateTimeInterface::RFC3339, + Arr::get($sourceTransaction, 'date') + ); + + $sourceTransactions[] = Transaction::factory()->makeOne([ + Transaction::ATTRIBUTE_AMOUNT => Arr::get($sourceTransaction, 'amount'), + Transaction::ATTRIBUTE_DATE => $date->format(AllowedDateFormat::YMD), + Transaction::ATTRIBUTE_DESCRIPTION => Arr::get($sourceTransaction, 'description'), + Transaction::ATTRIBUTE_EXTERNAL_ID => Arr::get($sourceTransaction, 'invoice_id'), + Transaction::ATTRIBUTE_SERVICE => Service::DIGITALOCEAN, + ]); + } + + $nextBillingHistory = Arr::get($response, 'links.pages.next'); + } + + return Collection::make($sourceTransactions); + } + + /** + * Save model to the repository. + * + * @param Model $model + * @return bool + */ + public function save(Model $model): bool + { + // Billing API is not writable + return false; + } + + /** + * Delete model from the repository. + * + * @param Model $model + * @return bool + */ + public function delete(Model $model): bool + { + // Billing API is not writable + return false; + } + + /** + * Update model in the repository. + * + * @param Model $model + * @param array $attributes + * @return bool + */ + public function update(Model $model, array $attributes): bool + { + // Billing API is not writable + return false; + } + + /** + * Validate repository filter. + * + * @param string $filter + * @param mixed $value + * @return bool + */ + public function validateFilter(string $filter, mixed $value = null): bool + { + // not supported + return false; + } + + /** + * Filter repository models. + * + * @param string $filter + * @param mixed $value + * @return void + */ + public function handleFilter(string $filter, mixed $value = null): void + { + // not supported + } +} diff --git a/app/Repositories/Service/DigitalOcean/VideoRepository.php b/app/Repositories/Service/DigitalOcean/VideoRepository.php new file mode 100644 index 000000000..a1a3dba1d --- /dev/null +++ b/app/Repositories/Service/DigitalOcean/VideoRepository.php @@ -0,0 +1,149 @@ +listContents($this->location, FilesystemReader::LIST_DEEP)); + + // Filter all objects for WebM metadata + // We don't want to filter on the remote filesystem for performance concerns + $fsVideos = $fsVideos->filter( + fn (StorageAttributes $fsFile) => $fsFile->isFile() && File::extension($fsFile->path()) === 'webm' + ); + + // Create videos from metadata that we can later save if needed + return $fsVideos->map( + fn (StorageAttributes $fsFile) => Video::factory()->makeOne([ + Video::ATTRIBUTE_BASENAME => File::basename($fsFile->path()), + Video::ATTRIBUTE_FILENAME => File::name($fsFile->path()), + Video::ATTRIBUTE_LYRICS => false, + Video::ATTRIBUTE_MIMETYPE => MimeType::from($fsFile->path()), + Video::ATTRIBUTE_NC => false, + Video::ATTRIBUTE_OVERLAP => VideoOverlap::NONE, + Video::ATTRIBUTE_PATH => $fsFile->path(), + Video::ATTRIBUTE_RESOLUTION => null, + Video::ATTRIBUTE_SIZE => $fsFile->offsetGet(StorageAttributes::ATTRIBUTE_FILE_SIZE), + Video::ATTRIBUTE_SOURCE => null, + Video::ATTRIBUTE_SUBBED => false, + Video::ATTRIBUTE_UNCEN => false, + ]) + ); + } + + /** + * Save model to the repository. + * + * @param Model $model + * @return bool + */ + public function save(Model $model): bool + { + // Do not write serialized models to object storage + return false; + } + + /** + * Delete model from the repository. + * + * @param Model $model + * @return bool + */ + public function delete(Model $model): bool + { + // Do not write serialized models to object storage + return false; + } + + /** + * Update model in the repository. + * + * @param Model $model + * @param array $attributes + * @return bool + */ + public function update(Model $model, array $attributes): bool + { + // Do not write serialized models to object storage + return false; + } + + /** + * Validate repository filter. + * + * @param string $filter + * @param mixed $value + * @return bool + */ + public function validateFilter(string $filter, mixed $value = null): bool + { + if ($filter === 'path') { + $fs = Storage::disk('videos'); + if ($fs instanceof FilesystemAdapter) { + return ! Str::startsWith($value, '/') && $fs->directoryExists($value); + } + } + + return false; + } + + /** + * Filter repository models. + * + * @param string $filter + * @param mixed $value + * @return void + */ + public function handleFilter(string $filter, mixed $value = null): void + { + if ($filter === 'path') { + $this->location = $value; + } + } +} diff --git a/app/Rules/Api/DistinctIgnoringDirectionRule.php b/app/Rules/Api/DistinctIgnoringDirectionRule.php new file mode 100644 index 000000000..0364cb2a2 --- /dev/null +++ b/app/Rules/Api/DistinctIgnoringDirectionRule.php @@ -0,0 +1,46 @@ +explode(','); + + $duplicateValues = $values->duplicates(function (mixed $sort) { + if (is_string($sort) && Str::startsWith($sort, '-')) { + return Str::replaceFirst('-', '', $sort); + } + + return $sort; + }); + + return $duplicateValues->isEmpty(); + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return __('validation.distinct'); + } +} diff --git a/app/Rules/Api/EnumDescriptionRule.php b/app/Rules/Api/EnumDescriptionRule.php new file mode 100644 index 000000000..c0556aa4c --- /dev/null +++ b/app/Rules/Api/EnumDescriptionRule.php @@ -0,0 +1,46 @@ + $enumClass + * @return void + */ + public function __construct(protected readonly string $enumClass) + { + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value): bool + { + return is_string($value) && $this->enumClass::fromDescription($value) !== null; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return __('validation.enum'); + } +} diff --git a/app/Rules/Api/IsValidBoolean.php b/app/Rules/Api/IsValidBoolean.php new file mode 100644 index 000000000..a6eca16a1 --- /dev/null +++ b/app/Rules/Api/IsValidBoolean.php @@ -0,0 +1,35 @@ +explode(','); + + return ! $values->contains(RandomCriteria::PARAM_VALUE) || $values->containsOneItem(); + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return __('validation.random_sole'); + } +} diff --git a/app/Rules/Billing/TransparencyDateRule.php b/app/Rules/Billing/TransparencyDateRule.php new file mode 100644 index 000000000..3b4605b4c --- /dev/null +++ b/app/Rules/Billing/TransparencyDateRule.php @@ -0,0 +1,50 @@ + $validDates + * @return void + */ + public function __construct(protected readonly Collection $validDates) + { + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value): bool + { + return $this->validDates->contains( + fn (Carbon $validDate) => $validDate->format(AllowedDateFormat::YM) === $value + ); + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return __('The selected month is not valid.'); + } +} diff --git a/app/Rules/Wiki/ResourceLinkMatchesSiteRule.php b/app/Rules/Wiki/ResourceLinkMatchesSiteRule.php new file mode 100644 index 000000000..086d1054c --- /dev/null +++ b/app/Rules/Wiki/ResourceLinkMatchesSiteRule.php @@ -0,0 +1,48 @@ +site); + + return empty($domain) || $domain === parse_url($value, PHP_URL_HOST); + } + + /** + * Get the validation error message. + * + * @return string|array + */ + public function message(): string|array + { + return __('validation.resource_link_site_mismatch'); + } +} diff --git a/app/Rules/Wiki/ResourceSiteMatchesLinkRule.php b/app/Rules/Wiki/ResourceSiteMatchesLinkRule.php new file mode 100644 index 000000000..bdf508b17 --- /dev/null +++ b/app/Rules/Wiki/ResourceSiteMatchesLinkRule.php @@ -0,0 +1,48 @@ +link, PHP_URL_HOST); + } + + /** + * Get the validation error message. + * + * @return string|array + */ + public function message(): string|array + { + return __('validation.resource_link_site_mismatch'); + } +} diff --git a/app/Scout/Elasticsearch/Api/Criteria/Filter/Criteria.php b/app/Scout/Elasticsearch/Api/Criteria/Filter/Criteria.php new file mode 100644 index 000000000..cbabaace6 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Criteria/Filter/Criteria.php @@ -0,0 +1,35 @@ +getElasticsearchClause($filter); + + if (BinaryLogicalOperator::OR()->is($this->criteria->getLogicalOperator())) { + if (ComparisonOperator::NE()->is($this->criteria->getComparisonOperator())) { + return $builder->should((new BoolQueryBuilder())->mustNot($clause)); + } + + return $builder->should($clause); + } + + if (ComparisonOperator::NE()->is($this->criteria->getComparisonOperator())) { + return $builder->mustNot($clause); + } + + return $builder->must($clause); + } + + /** + * Build clause for Elasticsearch filter based on comparison operator. + * + * @param Filter $filter + * @return QueryBuilderInterface + */ + protected function getElasticsearchClause(Filter $filter): QueryBuilderInterface + { + $filterValue = $this->coerceFilterValue($filter); + + return match ($this->criteria->getComparisonOperator()?->value) { + ComparisonOperator::LT => ElasticQuery::range()->field($filter->getColumn())->lt($filterValue), + ComparisonOperator::GT => ElasticQuery::range()->field($filter->getColumn())->gt($filterValue), + ComparisonOperator::LTE => ElasticQuery::range()->field($filter->getColumn())->lte($filterValue), + ComparisonOperator::GTE => ElasticQuery::range()->field($filter->getColumn())->gte($filterValue), + ComparisonOperator::LIKE => ElasticQuery::wildcard() + ->field($filter->getColumn()) + ->value(Str::replace(['%', '_'], ['*', '?'], $filterValue)), + ComparisonOperator::NOTLIKE => ElasticQuery::bool()->mustNot( + ElasticQuery::wildcard()->field($filter->getColumn())->value(Str::replace(['%', '_'], ['*', '?'], $filterValue)) + ), + default => ElasticQuery::term()->field($filter->getColumn())->value($filterValue), + }; + } + + /** + * Coerce filter value for elasticsearch range and term queries. + * + * @param Filter $filter + * @return string + */ + protected function coerceFilterValue(Filter $filter): string + { + $filterValue = Arr::first($filter->getFilterValues($this->criteria->getFilterValues())); + + // Elasticsearch wants 'true' or 'false' for boolean fields + if (is_bool($filterValue)) { + return $filterValue ? 'true' : 'false'; + } + + // The Elasticsearch driver wants a string for wildcard & term queries + return strval($filterValue); + } +} diff --git a/app/Scout/Elasticsearch/Api/Criteria/Filter/WhereInCriteria.php b/app/Scout/Elasticsearch/Api/Criteria/Filter/WhereInCriteria.php new file mode 100644 index 000000000..be13b828a --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Criteria/Filter/WhereInCriteria.php @@ -0,0 +1,60 @@ +not = $criteria->not(); + } + + /** + * Apply criteria to builder. + * + * @param BoolQueryBuilder $builder + * @param Filter $filter + * @param ReadQuery $query + * @return BoolQueryBuilder + */ + public function filter(BoolQueryBuilder $builder, Filter $filter, ReadQuery $query): BoolQueryBuilder + { + $filterValues = $filter->getFilterValues($this->criteria->getFilterValues()); + + $clause = ElasticQuery::terms()->field($filter->getColumn())->values($filterValues); + + if (BinaryLogicalOperator::OR()->is($this->criteria->getLogicalOperator())) { + if ($this->not) { + return $builder->should((new BoolQueryBuilder())->mustNot($clause)); + } + + return $builder->should($clause); + } + + if ($this->not) { + return $builder->mustNot($clause); + } + + return $builder->must($clause); + } +} diff --git a/app/Scout/Elasticsearch/Api/Criteria/Paging/Criteria.php b/app/Scout/Elasticsearch/Api/Criteria/Paging/Criteria.php new file mode 100644 index 000000000..a1b80afb4 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Criteria/Paging/Criteria.php @@ -0,0 +1,44 @@ +criteria->getStrategy(); + } + + /** + * Paginate the search query. + * + * @param SearchRequestBuilder $builder + * @return Collection|Paginator + */ + abstract public function paginate(SearchRequestBuilder $builder): Collection|Paginator; +} diff --git a/app/Scout/Elasticsearch/Api/Criteria/Paging/LimitCriteria.php b/app/Scout/Elasticsearch/Api/Criteria/Paging/LimitCriteria.php new file mode 100644 index 000000000..9e6acba1a --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Criteria/Paging/LimitCriteria.php @@ -0,0 +1,40 @@ +size($this->criteria->getResultSize()) + ->execute() + ->models(); + } +} diff --git a/app/Scout/Elasticsearch/Api/Criteria/Paging/OffsetCriteria.php b/app/Scout/Elasticsearch/Api/Criteria/Paging/OffsetCriteria.php new file mode 100644 index 000000000..43672c05b --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Criteria/Paging/OffsetCriteria.php @@ -0,0 +1,57 @@ +append('.') + ->append(BaseCriteria::NUMBER_PARAM) + ->__toString(); + + $pageNameLink = Str::of(PagingParser::param()) + ->append('[') + ->append(BaseCriteria::NUMBER_PARAM) + ->append(']') + ->__toString(); + + $paginator = $builder->paginate($this->criteria->getResultSize(), $pageNameQuery) + ->setPageName($pageNameLink) + ->appends(Request::except($pageNameQuery)); + + $paginator->setCollection($paginator->models()); + + return $paginator; + } +} diff --git a/app/Scout/Elasticsearch/Api/Criteria/Sort/Criteria.php b/app/Scout/Elasticsearch/Api/Criteria/Sort/Criteria.php new file mode 100644 index 000000000..a9724e5d9 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Criteria/Sort/Criteria.php @@ -0,0 +1,31 @@ +direction = $criteria->getDirection(); + } + + /** + * Apply criteria to builder. + * + * @param Sort $sort + * @return array + */ + public function sort(Sort $sort): array + { + return [$sort->getColumn() => $this->direction->value]; + } +} diff --git a/app/Scout/Elasticsearch/Api/Criteria/Sort/RelationCriteria.php b/app/Scout/Elasticsearch/Api/Criteria/Sort/RelationCriteria.php new file mode 100644 index 000000000..2bcf21626 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Criteria/Sort/RelationCriteria.php @@ -0,0 +1,34 @@ +getColumn() => [ + 'order' => $this->direction->value, + 'nested' => [ + 'path' => Str::beforeLast($sort->getColumn(), '.'), + ], + ], + ]; + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/Base/CreatedAtField.php b/app/Scout/Elasticsearch/Api/Field/Base/CreatedAtField.php new file mode 100644 index 000000000..de96b7cc8 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/Base/CreatedAtField.php @@ -0,0 +1,22 @@ +getKey(), $this->getSearchField()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getSortField()); + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/DateField.php b/app/Scout/Elasticsearch/Api/Field/DateField.php new file mode 100644 index 000000000..9ece6d809 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/DateField.php @@ -0,0 +1,37 @@ +getKey(), $this->getSearchField()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getSortField()); + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/EnumField.php b/app/Scout/Elasticsearch/Api/Field/EnumField.php new file mode 100644 index 000000000..46b93ea73 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/EnumField.php @@ -0,0 +1,53 @@ + $enumClass + * @param string|null $column + */ + public function __construct( + string $key, + protected readonly string $enumClass, + ?string $column = null + ) { + parent::__construct($key, $column); + } + + /** + * Get the filter that can be applied to the field. + * + * @return Filter + */ + public function getFilter(): Filter + { + return new EnumFilter($this->getKey(), $this->enumClass, $this->getSearchField()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getSortField()); + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/Field.php b/app/Scout/Elasticsearch/Api/Field/Field.php new file mode 100644 index 000000000..6e5ff8bb2 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/Field.php @@ -0,0 +1,57 @@ +key; + } + + /** + * Get the search field. + * + * @return string|null + */ + public function getSearchField(): ?string + { + return $this->searchField ?? $this->key; + } + + /** + * Get the sort field. + * + * @return string|null + */ + public function getSortField(): ?string + { + return $this->sortField ?? $this->getSearchField(); + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/FloatField.php b/app/Scout/Elasticsearch/Api/Field/FloatField.php new file mode 100644 index 000000000..53710a698 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/FloatField.php @@ -0,0 +1,37 @@ +getKey(), $this->getSearchField()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getSortField()); + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/IntField.php b/app/Scout/Elasticsearch/Api/Field/IntField.php new file mode 100644 index 000000000..550626466 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/IntField.php @@ -0,0 +1,37 @@ +getKey(), $this->getSearchField()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getSortField()); + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/StringField.php b/app/Scout/Elasticsearch/Api/Field/StringField.php new file mode 100644 index 000000000..11a3bf5cb --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/StringField.php @@ -0,0 +1,60 @@ +getKey(), $this->getSearchField()); + } + + /** + * Get the sort that can be applied to the field. + * + * @return Sort + */ + public function getSort(): Sort + { + return new Sort($this->getKey(), $this->getSortField()); + } +} diff --git a/app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeNameField.php b/app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeNameField.php new file mode 100644 index 000000000..422bfaee9 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Field/Wiki/Anime/AnimeNameField.php @@ -0,0 +1,22 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('text') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->minimumShouldMatch(1); + + return AnimeSynonym::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/Anime/Theme/EntryQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/Anime/Theme/EntryQueryPayload.php new file mode 100644 index 000000000..0a73eac26 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/Anime/Theme/EntryQueryPayload.php @@ -0,0 +1,271 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('version') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('version') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('version') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('version_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('version_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('version_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('theme.anime.name') + ->query($this->criteria->getTerm()) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime') + ->query( + (new MatchQueryBuilder()) + ->field('theme.anime.name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime') + ->query( + (new MatchQueryBuilder()) + ->field('theme.anime.name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime.synonyms') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('theme.anime.synonyms.text') + ->query($this->criteria->getTerm()) + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime.synonyms') + ->query( + (new MatchQueryBuilder()) + ->field('theme.anime.synonyms.text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime') + ->query( + (new NestedQueryBuilder()) + ->path('theme.anime.synonyms') + ->query( + (new MatchQueryBuilder()) + ->field('theme.anime.synonyms.text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.song') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('theme.song.title') + ->query($this->criteria->getTerm()) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.song') + ->query( + (new MatchQueryBuilder()) + ->field('theme.song.title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('theme') + ->query( + (new NestedQueryBuilder()) + ->path('theme.song') + ->query( + (new MatchQueryBuilder()) + ->field('theme.song.title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ->minimumShouldMatch(1); + + return AnimeThemeEntry::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/Anime/ThemeQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/Anime/ThemeQueryPayload.php new file mode 100644 index 000000000..4741d1fc6 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/Anime/ThemeQueryPayload.php @@ -0,0 +1,216 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new NestedQueryBuilder()) + ->path('anime') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('anime.name') + ->query($this->criteria->getTerm()) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('anime') + ->query( + (new MatchQueryBuilder()) + ->field('anime.name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('anime') + ->query( + (new MatchQueryBuilder()) + ->field('anime.name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('anime') + ->query( + (new NestedQueryBuilder()) + ->path('anime.synonyms') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('anime.synonyms.text') + ->query($this->criteria->getTerm()) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('anime') + ->query( + (new NestedQueryBuilder()) + ->path('anime.synonyms') + ->query( + (new MatchQueryBuilder()) + ->field('anime.synonyms.text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('anime') + ->query( + (new NestedQueryBuilder()) + ->path('anime.synonyms') + ->query( + (new MatchQueryBuilder()) + ->field('anime.synonyms.text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('song') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('song.title') + ->query($this->criteria->getTerm()) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('song') + ->query( + (new MatchQueryBuilder()) + ->field('song.title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('song') + ->query( + (new MatchQueryBuilder()) + ->field('song.title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ->minimumShouldMatch(1); + + return AnimeTheme::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/AnimeQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/AnimeQueryPayload.php new file mode 100644 index 000000000..0432b0ce5 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/AnimeQueryPayload.php @@ -0,0 +1,152 @@ +mustRaw([ + // Pick the score of the best performing sub-query. + 'dis_max' => [ + 'queries' => [ + [ + // The more sub-queries match the better the score will be. + 'bool' => [ + 'should' => [ + [ + 'match_phrase' => [ + 'name' => [ + 'query' => $this->criteria->getTerm(), + ], + ], + ], + [ + 'match' => [ + 'name' => [ + 'query' => $this->criteria->getTerm(), + 'operator' => 'AND', + ], + ], + ], + [ + 'match' => [ + 'name' => [ + 'query' => $this->criteria->getTerm(), + 'boost' => 0.6, + ], + ], + ], + [ + 'fuzzy' => [ + 'name' => [ + 'value' => $this->criteria->getTerm(), + 'boost' => 0.4, + ], + ], + ], + ], + ], + ], + [ + 'bool' => [ + 'boost' => 0.85, + 'should' => [ + [ + 'nested' => [ + 'path' => 'synonyms', + 'query' => [ + 'match_phrase' => [ + 'synonyms.text' => [ + 'query' => $this->criteria->getTerm(), + ], + ], + ], + ], + ], + [ + 'nested' => [ + 'path' => 'synonyms', + 'query' => [ + 'match' => [ + 'synonyms.text' => [ + 'query' => $this->criteria->getTerm(), + 'operator' => 'AND', + ], + ], + ], + ], + ], + [ + 'nested' => [ + 'path' => 'synonyms', + 'query' => [ + 'match' => [ + 'synonyms.text' => [ + 'query' => $this->criteria->getTerm(), + 'boost' => 0.6, + ], + ], + ], + ], + ], + [ + 'nested' => [ + 'path' => 'synonyms', + 'query' => [ + 'fuzzy' => [ + 'synonyms.text' => [ + 'value' => $this->criteria->getTerm(), + 'boost' => 0.4, + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ]); + + return Anime::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/ArtistQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/ArtistQueryPayload.php new file mode 100644 index 000000000..3832ed02b --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/ArtistQueryPayload.php @@ -0,0 +1,115 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new NestedQueryBuilder()) + ->path('songs') + ->query( + (new NestedQueryBuilder()) + ->path('songs.pivot') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('songs.pivot.as') + ->query($this->criteria->getTerm()) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('songs') + ->query( + (new NestedQueryBuilder()) + ->path('songs.pivot') + ->query( + (new MatchQueryBuilder()) + ->field('songs.pivot.as') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('songs') + ->query( + (new NestedQueryBuilder()) + ->path('songs.pivot') + ->query( + (new MatchQueryBuilder()) + ->field('songs.pivot.as') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ->minimumShouldMatch(1); + + return Artist::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/ElasticQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/ElasticQueryPayload.php new file mode 100644 index 000000000..17ca7fd09 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/ElasticQueryPayload.php @@ -0,0 +1,45 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->minimumShouldMatch(1); + + return Series::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/SongQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/SongQueryPayload.php new file mode 100644 index 000000000..bde62c3a5 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/SongQueryPayload.php @@ -0,0 +1,71 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('title') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->minimumShouldMatch(1); + + return Song::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/StudioQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/StudioQueryPayload.php new file mode 100644 index 000000000..8a8acbe33 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/StudioQueryPayload.php @@ -0,0 +1,71 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->minimumShouldMatch(1); + + return Studio::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Query/VideoQueryPayload.php b/app/Scout/Elasticsearch/Api/Query/VideoQueryPayload.php new file mode 100644 index 000000000..8589b382c --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Query/VideoQueryPayload.php @@ -0,0 +1,344 @@ +should( + (new MatchPhraseQueryBuilder()) + ->field('filename') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('filename') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('filename') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('tags') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('tags') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('tags') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('tags_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('tags_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('tags_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('version_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('version_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('version_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('anime_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new MatchPhraseQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ) + ->should( + (new MatchQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ->should( + (new MatchQueryBuilder()) + ->field('synonym_slug') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('entries.theme.anime.name') + ->query($this->criteria->getTerm()) + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime') + ->query( + (new MatchQueryBuilder()) + ->field('entries.theme.anime.name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime') + ->query( + (new MatchQueryBuilder()) + ->field('entries.theme.anime.name') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime.synonyms') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('entries.theme.anime.synonyms.text') + ->query($this->criteria->getTerm()) + ) + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime.synonyms') + ->query( + (new MatchQueryBuilder()) + ->field('entries.theme.anime.synonyms.text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.anime.synonyms') + ->query( + (new MatchQueryBuilder()) + ->field('entries.theme.anime.synonyms.text') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.song') + ->query( + (new MatchPhraseQueryBuilder()) + ->field('entries.theme.song.title') + ->query($this->criteria->getTerm()) + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.song') + ->query( + (new MatchQueryBuilder()) + ->field('entries.theme.song.title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ) + ) + ) + ) + ->should( + (new NestedQueryBuilder()) + ->path('entries') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme') + ->query( + (new NestedQueryBuilder()) + ->path('entries.theme.song') + ->query( + (new MatchQueryBuilder()) + ->field('entries.theme.song.title') + ->query($this->criteria->getTerm()) + ->operator('AND') + ->lenient(true) + ->fuzziness('AUTO') + ) + ) + ) + ) + ->minimumShouldMatch(1); + + return Video::searchQuery($query); + } +} diff --git a/app/Scout/Elasticsearch/Api/Schema/Schema.php b/app/Scout/Elasticsearch/Api/Schema/Schema.php new file mode 100644 index 000000000..7755b4352 --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Schema/Schema.php @@ -0,0 +1,76 @@ +fields()) + ->filter(fn (Field $field) => $field instanceof FilterableField) + ->map(fn (FilterableField $field) => $field->getFilter()) + ->all(); + } + + /** + * Get the sorts of the resource. + * + * @return Sort[] + */ + public function sorts(): array + { + return collect($this->fields()) + ->filter(fn (Field $field) => $field instanceof SortableField) + ->map(fn (SortableField $field) => $field->getSort()) + ->all(); + } +} diff --git a/app/Scout/Elasticsearch/Api/Schema/Wiki/Anime/SynonymSchema.php b/app/Scout/Elasticsearch/Api/Schema/Wiki/Anime/SynonymSchema.php new file mode 100644 index 000000000..be605db6f --- /dev/null +++ b/app/Scout/Elasticsearch/Api/Schema/Wiki/Anime/SynonymSchema.php @@ -0,0 +1,68 @@ +files()->in(static::getPath()); + + foreach ($payloads as $payload) { + try { + $payload = new ReflectionClass(static::classFromFile($payload)); + } catch (ReflectionException $e) { + Log::error($e->getMessage()); + continue; + } + + if (! $payload->isInstantiable()) { + continue; + } + + if (! $payload->isSubclassOf(ElasticQueryPayload::class)) { + continue; + } + + if ($payload->hasMethod('model')) { + try { + $method = $payload->getMethod('model'); + if ($modelClass === $method->invoke(null)) { + return $payload->getName(); + } + } catch (ReflectionException $e) { + Log::error($e->getMessage()); + continue; + } + } + } + + return null; + } + + /** + * Extract the class name from the given file path. + * + * @param SplFileInfo $file + * @return string + */ + protected static function classFromFile(SplFileInfo $file): string + { + $class = trim(Str::replaceFirst(base_path(), '', $file->getRealPath()), DIRECTORY_SEPARATOR); + + return str_replace( + [DIRECTORY_SEPARATOR, ucfirst(basename(app()->path())).'\\'], + ['\\', app()->getNamespace()], + ucfirst(Str::replaceLast('.php', '', $class)) + ); + } + + /** + * The path to the Elasticsearch query payload classes. + * + * @return string + */ + protected static function getPath(): string + { + return app()->path(Str::of('Scout') + ->append(DIRECTORY_SEPARATOR) + ->append('Elasticsearch') + ->append(DIRECTORY_SEPARATOR) + ->append('Api') + ->append(DIRECTORY_SEPARATOR) + ->append('Query') + ->__toString()); + } +} diff --git a/app/Scout/Elasticsearch/Elasticsearch.php b/app/Scout/Elasticsearch/Elasticsearch.php new file mode 100644 index 000000000..a01e2a620 --- /dev/null +++ b/app/Scout/Elasticsearch/Elasticsearch.php @@ -0,0 +1,161 @@ +alive = $client->ping(); + } catch (Exception $exception) { + Log::error($exception->getMessage()); + $this->alive = false; + } + } + + /** + * Is the ES instance reachable? + * + * @return bool + */ + public function isAlive(): bool + { + return $this->alive; + } + + /** + * Should the search be performed? + * + * @param EloquentReadQuery $query + * @return bool + */ + public function shouldSearch(EloquentReadQuery $query): bool + { + return $query->hasSearchCriteria() && $this->isAlive(); + } + + /** + * Perform the search. + * + * @param EloquentReadQuery $query + * @param PaginationStrategy $paginationStrategy + * @return BaseCollection + */ + public function search( + EloquentReadQuery $query, + PaginationStrategy $paginationStrategy + ): BaseCollection { + $elasticQueryPayload = $this->elasticQueryPayload($query); + if ($elasticQueryPayload === null) { + $model = $query->schema()->model(); + throw new RuntimeException("ElasticQueryPayload not configured for model '$model'"); + } + + $schema = $elasticQueryPayload->schema(); + + // initialize builder with payload for matches + $builder = $elasticQueryPayload->buildQuery(); + + // eager load relations with constraints + $constrainedEagerLoads = $query->constrainEagerLoads(); + $builder = $builder->load($constrainedEagerLoads); + + // apply filters + $filterBuilder = new BoolQueryBuilder(); + $scope = ScopeParser::parse($schema->type()); + foreach ($query->getFilterCriteria() as $filterCriterion) { + $elasticFilterCriteria = FilterParser::parse($filterCriterion); + if ($elasticFilterCriteria !== null) { + foreach ($schema->filters() as $filter) { + if ($filterCriterion->shouldFilter($filter, $scope)) { + $filterBuilder = $elasticFilterCriteria->filter($filterBuilder, $filter, $query); + } + } + } + } + try { + $builder->postFilter($filterBuilder); + } catch (QueryBuilderException) { + // There doesn't appear to be a way to check if any filters have been set in the filter builder + } + + // apply sorts + $sorts = []; + foreach ($query->getSortCriteria() as $sortCriterion) { + $elasticSortCriteria = SortParser::parse($sortCriterion); + if ($elasticSortCriteria !== null) { + foreach ($schema->sorts() as $sort) { + if ($sortCriterion->shouldSort($sort, $scope)) { + $sorts[] = $elasticSortCriteria->sort($sort); + } + } + } + } + if (! empty($sorts)) { + $builder->sortRaw($sorts); + } + + // paginate + $paginationCriteria = $query->getPagingCriteria($paginationStrategy); + $elasticPaginationCriteria = PagingParser::parse($paginationCriteria); + + $collection = $elasticPaginationCriteria !== null + ? $elasticPaginationCriteria->paginate($builder) + : $builder->execute()->models(); + + return $query->collection($collection); + } + + /** + * Resolve Elasticsearch query builder from schema. + * + * @param EloquentReadQuery $query + * @return ElasticQueryPayload|null + */ + protected function elasticQueryPayload(EloquentReadQuery $query): ?ElasticQueryPayload + { + $schema = $query->schema(); + + $elasticQueryPayload = DiscoverElasticQueryPayload::byModelClass($schema->model()); + + if ($elasticQueryPayload !== null) { + return new $elasticQueryPayload($query->getSearchCriteria()); + } + + return null; + } +} diff --git a/app/Scout/Search.php b/app/Scout/Search.php new file mode 100644 index 000000000..2f5e6528f --- /dev/null +++ b/app/Scout/Search.php @@ -0,0 +1,35 @@ +=5.5" }, "require-dev": { - "phpunit/phpunit": "^6|^7|^8|^9", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "^4.8.35|^5.4.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, "autoload": { - "psr-4": { - "Asm89\\Stack\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Alexander", - "email": "iam.asm89@gmail.com" + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" } ], - "description": "Cross-origin resource sharing library and stack middleware", - "homepage": "https://github.com/asm89/stack-cors", + "description": "AWS Common Runtime for PHP", + "homepage": "http://aws.amazon.com/sdkforphp", "keywords": [ - "cors", - "stack" + "amazon", + "aws", + "crt", + "sdk" ], "support": { - "issues": "https://github.com/asm89/stack-cors/issues", - "source": "https://github.com/asm89/stack-cors/tree/v2.0.3" + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2" }, - "time": "2021-03-11T06:42:03+00:00" + "time": "2021-09-03T22:57:30+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.185.6", + "version": "3.224.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "35310302912fdc3b4a0e829b84424c41e3fd9727" + "reference": "bc5eb18414ef703c5f39a5a009a437c74c228306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/35310302912fdc3b4a0e829b84424c41e3fd9727", - "reference": "35310302912fdc3b4a0e829b84424c41e3fd9727", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bc5eb18414ef703c5f39a5a009a437c74c228306", + "reference": "bc5eb18414ef703c5f39a5a009a437c74c228306", "shasum": "" }, "require": { + "aws/aws-crt-php": "^1.0.2", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/guzzle": "^5.3.3 || ^6.2.1 || ^7.0", "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.7.0", + "guzzlehttp/psr7": "^1.7.0 || ^2.1.1", "mtdowling/jmespath.php": "^2.6", "php": ">=5.5" }, @@ -97,7 +92,7 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35|^5.4.3", + "phpunit/phpunit": "^4.8.35 || ^5.6.3", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3" @@ -116,12 +111,12 @@ } }, "autoload": { - "psr-4": { - "Aws\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Aws\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -148,22 +143,731 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.185.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.224.0" }, - "time": "2021-07-02T18:13:18+00:00" + "time": "2022-05-27T20:23:28+00:00" }, { - "name": "brick/math", - "version": "0.9.2", + "name": "babenkoivan/elastic-adapter", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0" + "url": "https://github.com/babenkoivan/elastic-adapter.git", + "reference": "16179d073785070984122eeaaa3f57651c673dec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", + "url": "https://api.github.com/repos/babenkoivan/elastic-adapter/zipball/16179d073785070984122eeaaa3f57651c673dec", + "reference": "16179d073785070984122eeaaa3f57651c673dec", + "shasum": "" + }, + "require": { + "elasticsearch/elasticsearch": "^7.3", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "orchestra/testbench": "^6.20", + "phpstan/phpstan": "^0.12.32", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ElasticAdapter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "Adapter for official PHP Elasticsearch client", + "keywords": [ + "adapter", + "client", + "elastic", + "elasticsearch", + "php" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-adapter/issues", + "source": "https://github.com/babenkoivan/elastic-adapter/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://ko-fi.com/ivanbabenko", + "type": "ko-fi" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2022-02-20T11:46:46+00:00" + }, + { + "name": "babenkoivan/elastic-client", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/babenkoivan/elastic-client.git", + "reference": "a1e818b444c5e64afd33a578aa4a009c54aff065" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/babenkoivan/elastic-client/zipball/a1e818b444c5e64afd33a578aa4a009c54aff065", + "reference": "a1e818b444c5e64afd33a578aa4a009c54aff065", + "shasum": "" + }, + "require": { + "elasticsearch/elasticsearch": "^7.3", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "orchestra/testbench": "^6.12", + "phpstan/phpstan": "^0.12.32", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "ElasticClient\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "ElasticClient\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "The official PHP Elasticsearch client integrated with Laravel", + "keywords": [ + "client", + "elastic", + "elasticsearch", + "laravel", + "php" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-client/issues", + "source": "https://github.com/babenkoivan/elastic-client/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://www.buymeacoffee.com/ivanbabenko", + "type": "buymeacoffee" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2021-02-16T07:28:08+00:00" + }, + { + "name": "babenkoivan/elastic-migrations", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/babenkoivan/elastic-migrations.git", + "reference": "cddfd3232a24a6b5f4f5bdc4bd5a21689759f1f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/babenkoivan/elastic-migrations/zipball/cddfd3232a24a6b5f4f5bdc4bd5a21689759f1f5", + "reference": "cddfd3232a24a6b5f4f5bdc4bd5a21689759f1f5", + "shasum": "" + }, + "require": { + "babenkoivan/elastic-adapter": "^2.0", + "babenkoivan/elastic-client": "^1.2", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "orchestra/testbench": "^6.12", + "phpstan/phpstan": "^0.12.32", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "ElasticMigrations\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "ElasticMigrations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "Elasticsearch migrations for Laravel", + "keywords": [ + "elastic", + "elasticsearch", + "laravel", + "migrations", + "php" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-migrations/issues", + "source": "https://github.com/babenkoivan/elastic-migrations/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://ko-fi.com/ivanbabenko", + "type": "ko-fi" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2022-01-16T11:01:35+00:00" + }, + { + "name": "babenkoivan/elastic-scout-driver", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/babenkoivan/elastic-scout-driver.git", + "reference": "db324d805bdf6a5b61f1c0c1fde2c9cc9d614320" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/babenkoivan/elastic-scout-driver/zipball/db324d805bdf6a5b61f1c0c1fde2c9cc9d614320", + "reference": "db324d805bdf6a5b61f1c0c1fde2c9cc9d614320", + "shasum": "" + }, + "require": { + "babenkoivan/elastic-adapter": "^2.0", + "babenkoivan/elastic-client": "^1.2", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "babenkoivan/elastic-migrations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "laravel/legacy-factories": "^1.1", + "laravel/scout": "^9.0", + "orchestra/testbench": "^6.12", + "phpstan/phpstan": "^0.12.32", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "ElasticScoutDriver\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "ElasticScoutDriver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "Elasticsearch driver for Laravel Scout", + "keywords": [ + "driver", + "elastic", + "elasticsearch", + "laravel", + "php", + "scout" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-scout-driver/issues", + "source": "https://github.com/babenkoivan/elastic-scout-driver/tree/v2.0.0" + }, + "funding": [ + { + "url": "https://www.buymeacoffee.com/ivanbabenko", + "type": "buymeacoffee" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2021-10-05T17:13:51+00:00" + }, + { + "name": "babenkoivan/elastic-scout-driver-plus", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/babenkoivan/elastic-scout-driver-plus.git", + "reference": "b3a39ddfd4c57624b0aeab4626c7251cc535a14d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/babenkoivan/elastic-scout-driver-plus/zipball/b3a39ddfd4c57624b0aeab4626c7251cc535a14d", + "reference": "b3a39ddfd4c57624b0aeab4626c7251cc535a14d", + "shasum": "" + }, + "require": { + "babenkoivan/elastic-adapter": "^2.4", + "babenkoivan/elastic-scout-driver": "^2.0", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "babenkoivan/elastic-migrations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "laravel/legacy-factories": "^1.1", + "laravel/scout": "^9.0", + "orchestra/testbench": "^6.12", + "phpstan/phpstan": "^0.12.32", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "ElasticScoutDriverPlus\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "ElasticScoutDriverPlus\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "Extension for Elastic Scout Driver", + "keywords": [ + "driver", + "elastic", + "elasticsearch", + "laravel", + "php", + "scout" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-scout-driver-plus/issues", + "source": "https://github.com/babenkoivan/elastic-scout-driver-plus/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://ko-fi.com/ivanbabenko", + "type": "ko-fi" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2022-02-20T16:59:55+00:00" + }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/d70c840f68657ce49094b8d91f9ee0cc07fbf66c", + "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.7" + }, + "time": "2022-03-14T02:02:36+00:00" + }, + { + "name": "bensampo/laravel-enum", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/BenSampo/laravel-enum.git", + "reference": "0440f7a7fd70d77716a6e6c7fed2a3e932d09b49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/BenSampo/laravel-enum/zipball/0440f7a7fd70d77716a6e6c7fed2a3e932d09b49", + "reference": "0440f7a7fd70d77716a6e6c7fed2a3e932d09b49", + "shasum": "" + }, + "require": { + "composer/composer": "^2.2", + "illuminate/contracts": "^9.0", + "illuminate/support": "^9.0", + "laminas/laminas-code": "^3.4 || ^4.0", + "nikic/php-parser": "^4.13", + "php": "^8.0" + }, + "require-dev": { + "doctrine/dbal": "^3.3", + "ergebnis/composer-normalize": "^2.24", + "mockery/mockery": "^1.4.4", + "orchestra/testbench": "^7.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5.10", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "BenSampo\\Enum\\EnumServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "BenSampo\\Enum\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Sampson", + "homepage": "https://sampo.co.uk", + "role": "Developer" + } + ], + "description": "Simple, extensible and powerful enumeration implementation for Laravel.", + "homepage": "https://github.com/bensampo/laravel-enum", + "keywords": [ + "bensampo", + "enum", + "laravel", + "package", + "validation" + ], + "support": { + "issues": "https://github.com/BenSampo/laravel-enum/issues", + "source": "https://github.com/BenSampo/laravel-enum/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://github.com/bensampo", + "type": "github" + } + ], + "time": "2022-04-08T07:46:13+00:00" + }, + { + "name": "bepsvpt/secure-headers", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/bepsvpt/secure-headers.git", + "reference": "e02d80065aae06a66220693e9c321787657fb20b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bepsvpt/secure-headers/zipball/e02d80065aae06a66220693e9c321787657fb20b", + "reference": "e02d80065aae06a66220693e9c321787657fb20b", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.1 || ~6.0 || ~7.0 || ~8.0 || ~9.0", + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "ext-json": "*", + "ext-xdebug": "*", + "orchestra/testbench": "~3.1 || ~4.0 || ~5.0 || ~6.0 || ~7.0" + }, + "suggest": { + "friendsofphp/php-cs-fixer": "Coding style fixer", + "phpstan/phpstan": "Static analysis tool", + "phpunit/phpunit": "PHP unit testing framework" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Bepsvpt\\SecureHeaders\\SecureHeadersServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Bepsvpt\\SecureHeaders\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "bepsvpt", + "email": "og7lsrszah6y3lz@infinitefa.email" + } + ], + "description": "Add security related headers to HTTP response. The package includes Service Providers for easy Laravel integration.", + "homepage": "https://github.com/bepsvpt/secure-headers", + "keywords": [ + "clear-site-data", + "content-security-policy", + "csp", + "except-ct", + "feature-policy", + "header", + "hsts", + "https", + "laravel", + "referrer-policy" + ], + "support": { + "issues": "https://github.com/bepsvpt/secure-headers/issues", + "source": "https://github.com/bepsvpt/secure-headers/tree/7.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/secure-headers", + "type": "open_collective" + } + ], + "time": "2022-02-10T01:11:21+00:00" + }, + { + "name": "bjeavons/zxcvbn-php", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/bjeavons/zxcvbn-php.git", + "reference": "994928ae5b17ecff8baa2406832d37bdf01116c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bjeavons/zxcvbn-php/zipball/994928ae5b17ecff8baa2406832d37bdf01116c0", + "reference": "994928ae5b17ecff8baa2406832d37bdf01116c0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2 | ^8.0 | ^8.1", + "symfony/polyfill-mbstring": ">=1.3.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "*", + "phpunit/phpunit": "^8.5", + "squizlabs/php_codesniffer": "3.*" + }, + "suggest": { + "ext-gmp": "Required for optimized binomial calculations (also requires PHP >= 7.3)" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZxcvbnPhp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "See contributors", + "homepage": "https://github.com/bjeavons/zxcvbn-php" + } + ], + "description": "Realistic password strength estimation PHP library based on Zxcvbn JS", + "homepage": "https://github.com/bjeavons/zxcvbn-php", + "keywords": [ + "password", + "zxcvbn" + ], + "support": { + "issues": "https://github.com/bjeavons/zxcvbn-php/issues", + "source": "https://github.com/bjeavons/zxcvbn-php/tree/1.3.1" + }, + "time": "2021-12-21T18:37:02+00:00" + }, + { + "name": "box/spout", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/box/spout.git", + "reference": "9bdb027d312b732515b884a341c0ad70372c6295" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/box/spout/zipball/9bdb027d312b732515b884a341c0ad70372c6295", + "reference": "9bdb027d312b732515b884a341c0ad70372c6295", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlreader": "*", + "ext-zip": "*", + "php": ">=7.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2", + "phpunit/phpunit": "^8" + }, + "suggest": { + "ext-iconv": "To handle non UTF-8 CSV files (if \"php-intl\" is not already installed or is too limited)", + "ext-intl": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Box\\Spout\\": "src/Spout" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Adrien Loison", + "email": "adrien@box.com" + } + ], + "description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way", + "homepage": "https://www.github.com/box/spout", + "keywords": [ + "OOXML", + "csv", + "excel", + "memory", + "odf", + "ods", + "office", + "open", + "php", + "read", + "scale", + "spreadsheet", + "stream", + "write", + "xlsx" + ], + "support": { + "issues": "https://github.com/box/spout/issues", + "source": "https://github.com/box/spout/tree/v3.3.0" + }, + "time": "2021-05-14T21:18:09+00:00" + }, + { + "name": "brick/math", + "version": "0.9.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", "shasum": "" }, "require": { @@ -173,7 +877,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", - "vimeo/psalm": "4.3.2" + "vimeo/psalm": "4.9.2" }, "type": "library", "autoload": { @@ -198,52 +902,115 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.9.2" + "source": "https://github.com/brick/math/tree/0.9.3" }, "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/brick/math", "type": "tidelift" } ], - "time": "2021-01-20T22:51:39+00:00" + "time": "2021-08-15T20:50:18+00:00" }, { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.2", + "name": "brick/money", + "version": "0.5.3", "source": { "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" + "url": "https://github.com/brick/money.git", + "reference": "49e6597470da74f6a9f1dd7d5286ea3b4756b7e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", + "url": "https://api.github.com/repos/brick/money/zipball/49e6597470da74f6a9f1dd7d5286ea3b4756b7e0", + "reference": "49e6597470da74f6a9f1dd7d5286ea3b4756b7e0", "shasum": "" }, "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" + "brick/math": "~0.7.3 || ~0.8.0 || ~0.9.0", + "php": "^7.1 || ^8.0" }, "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" + "brick/varexporter": "~0.2.1", + "ext-dom": "*", + "ext-pdo": "*", + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15 || ^8.0 || ^9.0", + "vimeo/psalm": "4.9.2" }, - "type": "composer-plugin", + "suggest": { + "ext-intl": "Required to format Money objects" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Money\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Money and currency library", + "keywords": [ + "brick", + "currency", + "money" + ], + "support": { + "issues": "https://github.com/brick/money/issues", + "source": "https://github.com/brick/money/tree/0.5.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/brick/money", + "type": "tidelift" + } + ], + "time": "2021-10-10T11:59:43+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/fd5dd441932a7e10ca6e5b490e272d34c8430640", + "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "psr/log": "^1.0", + "symfony/phpunit-bridge": "^4.2 || ^5", + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "type": "library", "extra": { - "class": "PackageVersions\\Installer", "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "PackageVersions\\": "src/PackageVersions" + "Composer\\CaBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -251,19 +1018,24 @@ "MIT" ], "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, { "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.3.2" }, "funding": [ { @@ -279,41 +1051,707 @@ "type": "tidelift" } ], - "time": "2021-05-24T07:46:03+00:00" + "time": "2022-05-24T11:56:16+00:00" }, { - "name": "doctrine/cache", - "version": "2.0.3", + "name": "composer/composer", + "version": "2.3.5", "source": { "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "c9622c6820d3ede1e2315a6a377ea1076e421d88" + "url": "https://github.com/composer/composer.git", + "reference": "50c47b1f907cfcdb8f072b88164d22b527557ae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/c9622c6820d3ede1e2315a6a377ea1076e421d88", - "reference": "c9622c6820d3ede1e2315a6a377ea1076e421d88", + "url": "https://api.github.com/repos/composer/composer/zipball/50c47b1f907cfcdb8f072b88164d22b527557ae1", + "reference": "50c47b1f907cfcdb8f072b88164d22b527557ae1", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2 || ^3", + "composer/semver": "^3.0", + "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "justinrainbow/json-schema": "^5.2.11", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^2.8", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "symfony/console": "^5.4.1 || ^6.0", + "symfony/filesystem": "^5.4 || ^6.0", + "symfony/finder": "^5.4 || ^6.0", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/process": "^5.4 || ^6.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1", + "phpstan/phpstan-symfony": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "source": "https://github.com/composer/composer/tree/2.3.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-13T14:43:00+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T20:21:48+00:00" + }, + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.7", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "c848241796da2abf65837d51dce1fae55a960149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", + "reference": "c848241796da2abf65837d51dce1fae55a960149", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-05-23T07:37:50+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "cyrildewit/eloquent-viewable", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/cyrildewit/eloquent-viewable.git", + "reference": "b661b456386d5dd515812adc437d7ed9711d5053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cyrildewit/eloquent-viewable/zipball/b661b456386d5dd515812adc437d7ed9711d5053", + "reference": "b661b456386d5dd515812adc437d7ed9711d5053", + "shasum": "" + }, + "require": { + "illuminate/cache": "^6.0|^7.0|^8.0|^9.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0", + "illuminate/cookie": "^6.0|^7.0|^8.0|^9.0", + "illuminate/database": "^6.0|^7.0|^8.0|^9.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0", + "jaybizzle/crawler-detect": "^1.0", + "nesbot/carbon": "^2.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "illuminate/config": "^6.0|^7.0|^8.0|^9.0", + "laravel/legacy-factories": "^1.1|^1.3", + "mockery/mockery": "^1.2.4", + "orchestra/testbench": "^4.9.1|^5.9.1|^6.6.1|^7.0.0", + "phpunit/phpunit": "^9.3.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "CyrildeWit\\EloquentViewable\\EloquentViewableServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "CyrildeWit\\EloquentViewable\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cyril de Wit", + "email": "info@cyrildewit.nl", + "homepage": "http://cyrildewit.nl", + "role": "Developer" + } + ], + "description": "Laravel package that allows you to associate views with Eloquent models", + "homepage": "https://github.com/cyrildewit/eloquent-viewable", + "keywords": [ + "counter", + "eloquent", + "hits", + "laravel", + "viewable", + "views", + "visitable", + "visits" + ], + "support": { + "issues": "https://github.com/cyrildewit/eloquent-viewable/issues", + "source": "https://github.com/cyrildewit/eloquent-viewable/tree/v6.1.0" + }, + "time": "2022-02-28T15:14:45+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.3" + }, + "time": "2020-10-02T16:03:48+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "doctrine/cache", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", "shasum": "" }, "require": { "php": "~7.1 || ^8.0" }, "conflict": { - "doctrine/common": ">2.2,<2.4", - "psr/cache": ">=3" + "doctrine/common": ">2.2,<2.4" }, "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0", - "symfony/cache": "^4.4 || ^5.2" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" }, "type": "library", "autoload": { @@ -362,7 +1800,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.0.3" + "source": "https://github.com/doctrine/cache/tree/2.2.0" }, "funding": [ { @@ -378,40 +1816,42 @@ "type": "tidelift" } ], - "time": "2021-05-25T09:43:04+00:00" + "time": "2022-05-20T20:07:39+00:00" }, { "name": "doctrine/dbal", - "version": "3.1.1", + "version": "3.3.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0" + "reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/8e0fde2b90e3f61361013d1e928621beeea07bc0", - "reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/9e7f76dd1cde81c62574fdffa5a9c655c847ad21", + "reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "^1.11.99", - "doctrine/cache": "^1.0|^2.0", - "doctrine/deprecations": "^0.5.3", + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1.0", - "php": "^7.3 || ^8.0" + "php": "^7.3 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" }, "require-dev": { "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2020.2", - "phpstan/phpstan": "0.12.81", - "phpstan/phpstan-strict-rules": "^0.12.2", - "phpunit/phpunit": "9.5.5", - "psalm/plugin-phpunit": "0.13.0", - "squizlabs/php_codesniffer": "3.6.0", + "jetbrains/phpstorm-stubs": "2022.1", + "phpstan/phpstan": "1.6.3", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "9.5.20", + "psalm/plugin-phpunit": "0.16.1", + "squizlabs/php_codesniffer": "3.6.2", "symfony/cache": "^5.2|^6.0", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0", - "vimeo/psalm": "4.6.4" + "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.23.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -471,7 +1911,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.1.1" + "source": "https://github.com/doctrine/dbal/tree/3.3.6" }, "funding": [ { @@ -487,29 +1927,29 @@ "type": "tidelift" } ], - "time": "2021-06-19T17:59:55+00:00" + "time": "2022-05-02T17:21:01+00:00" }, { "name": "doctrine/deprecations", - "version": "v0.5.3", + "version": "v1.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", "shasum": "" }, "require": { "php": "^7.1|^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -528,9 +1968,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" }, - "time": "2021-03-21T12:59:47+00:00" + "time": "2022-05-02T15:47:09+00:00" }, { "name": "doctrine/event-manager", @@ -628,34 +2068,30 @@ }, { "name": "doctrine/inflector", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210" + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/9cf661f4eb38f7c881cac67c75ea9b00bf97b210", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^7.0", - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-strict-rules": "^0.11", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" @@ -703,7 +2139,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.x" + "source": "https://github.com/doctrine/inflector/tree/2.0.4" }, "funding": [ { @@ -719,36 +2155,32 @@ "type": "tidelift" } ], - "time": "2020-05-29T15:13:26+00:00" + "time": "2021-10-22T20:16:43+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" @@ -783,7 +2215,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.1" + "source": "https://github.com/doctrine/lexer/tree/1.2.3" }, "funding": [ { @@ -799,33 +2231,33 @@ "type": "tidelift" } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2022-02-28T11:07:21+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v3.1.0", + "version": "v3.3.1", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c" + "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", - "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/be85b3f05b46c39bbc0d95f6c071ddff669510fa", + "reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "webmozart/assert": "^1.7.0" + "webmozart/assert": "^1.0" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-webmozart-assert": "^0.12.7", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", @@ -852,7 +2284,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.1" }, "funding": [ { @@ -860,31 +2292,31 @@ "type": "github" } ], - "time": "2020-11-24T19:55:57+00:00" + "time": "2022-01-18T15:43:28+00:00" }, { "name": "egulias/email-validator", - "version": "2.1.25", + "version": "3.2", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4" + "reference": "a5ed8d58ed0c340a7c2109f587951b1c84cf6286" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4", - "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/a5ed8d58ed0c340a7c2109f587951b1c84cf6286", + "reference": "a5ed8d58ed0c340a7c2109f587951b1c84cf6286", "shasum": "" }, "require": { - "doctrine/lexer": "^1.0.1", - "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.10" + "doctrine/lexer": "^1.2", + "php": ">=7.2", + "symfony/polyfill-intl-idn": "^1.15" }, "require-dev": { - "dominicsayers/isemail": "^3.0.7", - "phpunit/phpunit": "^4.8.36|^7.5.15", - "satooshi/php-coveralls": "^1.0.1" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^8.5.8|^9.3.3", + "vimeo/psalm": "^4" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -892,7 +2324,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -920,7 +2352,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + "source": "https://github.com/egulias/EmailValidator/tree/3.2" }, "funding": [ { @@ -928,42 +2360,104 @@ "type": "github" } ], - "time": "2020-12-29T14:50:06+00:00" + "time": "2022-05-28T22:19:18+00:00" }, { - "name": "fideloper/proxy", - "version": "4.4.1", + "name": "elasticsearch/elasticsearch", + "version": "v7.17.0", "source": { "type": "git", - "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0" + "url": "https://github.com/elastic/elasticsearch-php.git", + "reference": "1890f9d7fde076b5a3ddcf579a802af05b2e781b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/c073b2bd04d1c90e04dc1b787662b558dd65ade0", - "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/1890f9d7fde076b5a3ddcf579a802af05b2e781b", + "reference": "1890f9d7fde076b5a3ddcf579a802af05b2e781b", + "shasum": "" + }, + "require": { + "ext-json": ">=1.3.7", + "ezimuel/ringphp": "^1.1.2", + "php": "^7.3 || ^8.0", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "ext-yaml": "*", + "ext-zip": "*", + "mockery/mockery": "^1.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.4", + "symfony/finder": "~4.0" + }, + "suggest": { + "ext-curl": "*", + "monolog/monolog": "Allows for client-level logging and tracing" + }, + "type": "library", + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Elasticsearch\\": "src/Elasticsearch/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0", + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Zachary Tong" + }, + { + "name": "Enrico Zimuel" + } + ], + "description": "PHP Client for Elasticsearch", + "keywords": [ + "client", + "elasticsearch", + "search" + ], + "support": { + "issues": "https://github.com/elastic/elasticsearch-php/issues", + "source": "https://github.com/elastic/elasticsearch-php/tree/v7.17.0" + }, + "time": "2022-02-03T13:40:04+00:00" + }, + { + "name": "ezimuel/guzzlestreams", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/ezimuel/guzzlestreams.git", + "reference": "abe3791d231167f14eb80d413420d1eab91163a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/abe3791d231167f14eb80d413420d1eab91163a8", + "reference": "abe3791d231167f14eb80d413420d1eab91163a8", "shasum": "" }, "require": { - "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0", "php": ">=5.4.0" }, "require-dev": { - "illuminate/http": "^5.0|^6.0|^7.0|^8.0|^9.0", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "~4.0" }, "type": "library", "extra": { - "laravel": { - "providers": [ - "Fideloper\\Proxy\\TrustedProxyServiceProvider" - ] + "branch-alias": { + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { - "Fideloper\\Proxy\\": "src/" + "GuzzleHttp\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -972,59 +2466,173 @@ ], "authors": [ { - "name": "Chris Fidao", - "email": "fideloper@gmail.com" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" } ], - "description": "Set trusted proxies for Laravel", + "description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php", + "homepage": "http://guzzlephp.org/", "keywords": [ - "load balancing", - "proxy", - "trusted proxy" + "Guzzle", + "stream" ], "support": { - "issues": "https://github.com/fideloper/TrustedProxy/issues", - "source": "https://github.com/fideloper/TrustedProxy/tree/4.4.1" + "source": "https://github.com/ezimuel/guzzlestreams/tree/3.0.1" }, - "time": "2020-10-22T13:48:01+00:00" + "time": "2020-02-14T23:11:50+00:00" }, { - "name": "fruitcake/laravel-cors", - "version": "v2.0.4", + "name": "ezimuel/ringphp", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/fruitcake/laravel-cors.git", - "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a" + "url": "https://github.com/ezimuel/ringphp.git", + "reference": "92b8161404ab1ad84059ebed41d9f757e897ce74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/a8ccedc7ca95189ead0e407c43b530dc17791d6a", - "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a", + "url": "https://api.github.com/repos/ezimuel/ringphp/zipball/92b8161404ab1ad84059ebed41d9f757e897ce74", + "reference": "92b8161404ab1ad84059ebed41d9f757e897ce74", "shasum": "" }, "require": { - "asm89/stack-cors": "^2.0.1", - "illuminate/contracts": "^6|^7|^8|^9", - "illuminate/support": "^6|^7|^8|^9", - "php": ">=7.2", - "symfony/http-foundation": "^4|^5", - "symfony/http-kernel": "^4.3.4|^5" + "ezimuel/guzzlestreams": "^3.0.1", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "replace": { + "guzzlehttp/ringphp": "self.version" }, "require-dev": { - "laravel/framework": "^6|^7|^8", - "orchestra/testbench-dusk": "^4|^5|^6|^7", - "phpunit/phpunit": "^6|^7|^8|^9", + "ext-curl": "*", + "phpunit/phpunit": "~9.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php", + "support": { + "source": "https://github.com/ezimuel/ringphp/tree/1.2.0" + }, + "time": "2021-11-16T11:51:30+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.19.0", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "d7f08a622b3346766325488aa32ddc93ccdecc75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/d7f08a622b3346766325488aa32ddc93ccdecc75", + "reference": "d7f08a622b3346766325488aa32ddc93ccdecc75", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "symfony/phpunit-bridge": "^4.4 || ^5.2" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "v1.19-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.19.0" + }, + "time": "2022-02-02T17:38:57+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" - }, - "laravel": { - "providers": [ - "Fruitcake\\Cors\\CorsServiceProvider" - ] + "dev-main": "1.1-dev" } }, "autoload": { @@ -1042,56 +2650,55 @@ "homepage": "https://fruitcake.nl" }, { - "name": "Barry vd. Heuvel", + "name": "Barryvdh", "email": "barryvdh@gmail.com" } ], - "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application", + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", "keywords": [ - "api", "cors", - "crossdomain", - "laravel" + "laravel", + "symfony" ], "support": { - "issues": "https://github.com/fruitcake/laravel-cors/issues", - "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.4" + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" }, "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, { "url": "https://github.com/barryvdh", "type": "github" } ], - "time": "2021-04-26T11:24:25+00:00" + "time": "2022-02-20T15:07:15+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.0.1", + "version": "v1.0.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb" + "reference": "0690bde05318336c7221785f2a932467f98b64ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb", - "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/0690bde05318336c7221785f2a932467f98b64ca", + "reference": "0690bde05318336c7221785f2a932467f98b64ca", "shasum": "" }, "require": { - "php": "^7.0|^8.0", - "phpoption/phpoption": "^1.7.3" + "php": "^7.0 || ^8.0", + "phpoption/phpoption": "^1.8" }, "require-dev": { - "phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0" + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { "GrahamCampbell\\ResultType\\": "src/" @@ -1104,7 +2711,8 @@ "authors": [ { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "An Implementation Of The Result Type", @@ -1117,7 +2725,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.4" }, "funding": [ { @@ -1129,28 +2737,29 @@ "type": "tidelift" } ], - "time": "2020-04-13T13:17:36+00:00" + "time": "2021-11-21T21:41:47+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.3.0", + "version": "7.4.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7008573787b430c1c1f650e3722d9bba59967628" + "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", - "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", + "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.7 || ^2.0", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0" + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, "provide": { "psr/http-client-implementation": "1.0" @@ -1160,7 +2769,7 @@ "ext-curl": "*", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^8.5.5 || ^9.3.5", - "psr/log": "^1.1" + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { "ext-curl": "Required for CURL handler support", @@ -1170,35 +2779,59 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, { "name": "Márk Sági-Kazár", "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", "keywords": [ "client", "curl", @@ -1212,7 +2845,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + "source": "https://github.com/guzzle/guzzle/tree/7.4.3" }, "funding": [ { @@ -1224,28 +2857,24 @@ "type": "github" }, { - "url": "https://github.com/alexeyshockov", - "type": "github" - }, - { - "url": "https://github.com/gmponos", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" } ], - "time": "2021-03-23T11:33:13+00:00" + "time": "2022-05-25T13:24:33+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.4.1", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "shasum": "" }, "require": { @@ -1257,26 +2886,41 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle promises library", @@ -1285,35 +2929,52 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.1" + "source": "https://github.com/guzzle/promises/tree/1.5.1" }, - "time": "2021-03-07T09:25:29+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.8.2", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1321,30 +2982,53 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "2.2-dev" } }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, { "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -1360,59 +3044,546 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.2" + "source": "https://github.com/guzzle/psr7/tree/2.2.1" }, - "time": "2021-04-26T09:17:50+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-03-20T21:55:58+00:00" }, { - "name": "laravel/framework", - "version": "v8.49.1", + "name": "inertiajs/inertia-laravel", + "version": "v0.6.2", "source": { "type": "git", - "url": "https://github.com/laravel/framework.git", - "reference": "62aee1bfeefd82f160c7aa3b4c63cb2f053215c0" + "url": "https://github.com/inertiajs/inertia-laravel.git", + "reference": "75859fb1586c3d37ed8705500b5f43e87eee3cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/62aee1bfeefd82f160c7aa3b4c63cb2f053215c0", - "reference": "62aee1bfeefd82f160c7aa3b4c63cb2f053215c0", + "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/75859fb1586c3d37ed8705500b5f43e87eee3cb2", + "reference": "75859fb1586c3d37ed8705500b5f43e87eee3cb2", "shasum": "" }, "require": { - "doctrine/inflector": "^1.4|^2.0", - "dragonmantank/cron-expression": "^3.0.2", - "egulias/email-validator": "^2.1.10", "ext-json": "*", + "laravel/framework": "^6.0|^7.0|^8.74|^9.0", + "php": "^7.2|~8.0.0|~8.1.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.3", + "orchestra/testbench": "^4.0|^5.0|^6.4|^7.0", + "phpunit/phpunit": "^8.0|^9.5.8", + "roave/security-advisories": "dev-master" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Inertia\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "./helpers.php" + ], + "psr-4": { + "Inertia\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "homepage": "https://reinink.ca" + } + ], + "description": "The Laravel adapter for Inertia.js.", + "keywords": [ + "inertia", + "laravel" + ], + "support": { + "issues": "https://github.com/inertiajs/inertia-laravel/issues", + "source": "https://github.com/inertiajs/inertia-laravel/tree/v0.6.2" + }, + "funding": [ + { + "url": "https://github.com/reinink", + "type": "github" + } + ], + "time": "2022-05-25T02:00:06+00:00" + }, + { + "name": "jaybizzle/crawler-detect", + "version": "v1.2.111", + "source": { + "type": "git", + "url": "https://github.com/JayBizzle/Crawler-Detect.git", + "reference": "d572ed4a65a70a2d2871dc5137c9c5b7e69745ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/d572ed4a65a70a2d2871dc5137c9c5b7e69745ab", + "reference": "d572ed4a65a70a2d2871dc5137c9c5b7e69745ab", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jaybizzle\\CrawlerDetect\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Beech", + "email": "m@rkbee.ch", + "role": "Developer" + } + ], + "description": "CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent", + "homepage": "https://github.com/JayBizzle/Crawler-Detect/", + "keywords": [ + "crawler", + "crawler detect", + "crawler detector", + "crawlerdetect", + "php crawler detect" + ], + "support": { + "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.111" + }, + "time": "2022-03-15T22:19:01+00:00" + }, + { + "name": "jenssegers/agent", + "version": "v2.6.4", + "source": { + "type": "git", + "url": "https://github.com/jenssegers/agent.git", + "reference": "daa11c43729510b3700bc34d414664966b03bffe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jenssegers/agent/zipball/daa11c43729510b3700bc34d414664966b03bffe", + "reference": "daa11c43729510b3700bc34d414664966b03bffe", + "shasum": "" + }, + "require": { + "jaybizzle/crawler-detect": "^1.2", + "mobiledetect/mobiledetectlib": "^2.7.6", + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5.0|^6.0|^7.0" + }, + "suggest": { + "illuminate/support": "Required for laravel service providers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + }, + "laravel": { + "providers": [ + "Jenssegers\\Agent\\AgentServiceProvider" + ], + "aliases": { + "Agent": "Jenssegers\\Agent\\Facades\\Agent" + } + } + }, + "autoload": { + "psr-4": { + "Jenssegers\\Agent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jens Segers", + "homepage": "https://jenssegers.com" + } + ], + "description": "Desktop/mobile user agent parser with support for Laravel, based on Mobiledetect", + "homepage": "https://github.com/jenssegers/agent", + "keywords": [ + "Agent", + "browser", + "desktop", + "laravel", + "mobile", + "platform", + "user agent", + "useragent" + ], + "support": { + "issues": "https://github.com/jenssegers/agent/issues", + "source": "https://github.com/jenssegers/agent/tree/v2.6.4" + }, + "funding": [ + { + "url": "https://github.com/jenssegers", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/jenssegers/agent", + "type": "tidelift" + } + ], + "time": "2020-06-13T08:05:20+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.12", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/justinrainbow/json-schema/issues", + "source": "https://github.com/justinrainbow/json-schema/tree/5.2.12" + }, + "time": "2022-04-13T08:02:27+00:00" + }, + { + "name": "laminas/laminas-code", + "version": "4.5.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-code.git", + "reference": "6fd96d4d913571a2cd056a27b123fa28cb90ac4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/6fd96d4d913571a2cd056a27b123fa28cb90ac4e", + "reference": "6fd96d4d913571a2cd056a27b123fa28cb90ac4e", + "shasum": "" + }, + "require": { + "php": ">=7.4, <8.2" + }, + "require-dev": { + "doctrine/annotations": "^1.13.2", + "ext-phar": "*", + "laminas/laminas-coding-standard": "^2.3.0", + "laminas/laminas-stdlib": "^3.6.1", + "phpunit/phpunit": "^9.5.10", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.13.1" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "laminas/laminas-stdlib": "Laminas\\Stdlib component" + }, + "type": "library", + "autoload": { + "files": [ + "polyfill/ReflectionEnumPolyfill.php" + ], + "psr-4": { + "Laminas\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "homepage": "https://laminas.dev", + "keywords": [ + "code", + "laminas", + "laminasframework" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-code/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-code/issues", + "rss": "https://github.com/laminas/laminas-code/releases.atom", + "source": "https://github.com/laminas/laminas-code" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-12-19T18:06:55+00:00" + }, + { + "name": "laravel-notification-channels/discord", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/laravel-notification-channels/discord.git", + "reference": "e6526cd0903b51abe39d3a80b6b1f60c391598e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel-notification-channels/discord/zipball/e6526cd0903b51abe39d3a80b6b1f60c391598e1", + "reference": "e6526cd0903b51abe39d3a80b6b1f60c391598e1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.3 || ^7.0", + "illuminate/console": "^6.0 || ^7.0 || ^8.0 || ^9.0", + "illuminate/notifications": "^6.0 || ^7.0 || ^8.0 || ^9.0", + "illuminate/queue": "^6.0 || ^7.0 || ^8.0 || ^9.0", + "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0", + "php": "^7.2|^8.0", + "textalk/websocket": "^1.2" + }, + "require-dev": { + "mockery/mockery": "^1.3.3", + "orchestra/testbench": "^5.0 || ^6.0 || ^7.0", + "phpunit/phpunit": "^8.5 || ^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NotificationChannels\\Discord\\DiscordServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "NotificationChannels\\Discord\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cody Scott", + "email": "cs475x@icloud.com", + "role": "Developer" + } + ], + "description": "Laravel notification driver for Discord.", + "homepage": "https://github.com/laravel-notification-channels/discord", + "keywords": [ + "channel", + "discord", + "driver", + "laravel", + "notification" + ], + "support": { + "issues": "https://github.com/laravel-notification-channels/discord/issues", + "source": "https://github.com/laravel-notification-channels/discord/tree/v1.3.0" + }, + "time": "2022-02-13T23:55:52+00:00" + }, + { + "name": "laravel/fortify", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/fortify.git", + "reference": "0b8e7a860f0aa4868846555f5d095d7b546ffb9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/fortify/zipball/0b8e7a860f0aa4868846555f5d095d7b546ffb9c", + "reference": "0b8e7a860f0aa4868846555f5d095d7b546ffb9c", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "ext-json": "*", + "illuminate/support": "^8.82|^9.0", + "php": "^7.3|^8.0", + "pragmarx/google2fa": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^6.0|^7.0", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Fortify\\FortifyServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Fortify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Backend controllers and scaffolding for Laravel authentication.", + "keywords": [ + "auth", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/fortify/issues", + "source": "https://github.com/laravel/fortify" + }, + "time": "2022-05-05T14:52:14+00:00" + }, + { + "name": "laravel/framework", + "version": "v9.14.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "2ca86f96118635a79ee3389013ccab110ae14bea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/2ca86f96118635a79ee3389013ccab110ae14bea", + "reference": "2ca86f96118635a79ee3389013ccab110ae14bea", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "dragonmantank/cron-expression": "^3.1", + "egulias/email-validator": "^3.1", "ext-mbstring": "*", "ext-openssl": "*", - "league/commonmark": "^1.3", - "league/flysystem": "^1.1", + "fruitcake/php-cors": "^1.2", + "laravel/serializable-closure": "^1.0", + "league/commonmark": "^2.2", + "league/flysystem": "^3.0", "monolog/monolog": "^2.0", - "nesbot/carbon": "^2.31", - "opis/closure": "^3.6", - "php": "^7.3|^8.0", - "psr/container": "^1.0", - "psr/simple-cache": "^1.0", - "ramsey/uuid": "^4.0", - "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^5.1.4", - "symfony/error-handler": "^5.1.4", - "symfony/finder": "^5.1.4", - "symfony/http-foundation": "^5.1.4", - "symfony/http-kernel": "^5.1.4", - "symfony/mime": "^5.1.4", - "symfony/process": "^5.1.4", - "symfony/routing": "^5.1.4", - "symfony/var-dumper": "^5.1.4", + "nesbot/carbon": "^2.53.1", + "php": "^8.0.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.2.2", + "symfony/console": "^6.0", + "symfony/error-handler": "^6.0", + "symfony/finder": "^6.0", + "symfony/http-foundation": "^6.0", + "symfony/http-kernel": "^6.0", + "symfony/mailer": "^6.0", + "symfony/mime": "^6.0", + "symfony/process": "^6.0", + "symfony/routing": "^6.0", + "symfony/var-dumper": "^6.0", "tijsverkoyen/css-to-inline-styles": "^2.2.2", - "vlucas/phpdotenv": "^5.2", - "voku/portable-ascii": "^1.4.8" + "vlucas/phpdotenv": "^5.4.1", + "voku/portable-ascii": "^2.0" }, "conflict": { "tightenco/collect": "<5.5.33" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.1|2.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "replace": { "illuminate/auth": "self.version", @@ -1420,6 +3591,7 @@ "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", + "illuminate/conditionable": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", "illuminate/container": "self.version", @@ -1448,22 +3620,27 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "^3.155", - "doctrine/dbal": "^2.6|^3.0", - "filp/whoops": "^2.8", - "guzzlehttp/guzzle": "^6.5.5|^7.0.1", - "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.4.2", - "orchestra/testbench-core": "^6.23", + "aws/aws-sdk-php": "^3.198.1", + "doctrine/dbal": "^2.13.3|^3.1.4", + "fakerphp/faker": "^1.9.2", + "guzzlehttp/guzzle": "^7.2", + "league/flysystem-aws-s3-v3": "^3.0", + "league/flysystem-ftp": "^3.0", + "league/flysystem-sftp-v3": "^3.0", + "mockery/mockery": "^1.4.4", + "orchestra/testbench-core": "^7.1", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.5.8|^9.3.3", - "predis/predis": "^1.1.2", - "symfony/cache": "^5.1.4" + "phpstan/phpstan": "^1.4.7", + "phpunit/phpunit": "^9.5.8", + "predis/predis": "^1.1.9", + "symfony/cache": "^6.0" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.198.1).", "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "ext-bcmath": "Required to use the multiple_of validation rule.", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", @@ -1471,28 +3648,30 @@ "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", - "filp/whoops": "Required for friendly error pages in development (^2.8).", - "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.2).", "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", - "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", - "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", - "mockery/mockery": "Required to use mocking (^1.4.2).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "mockery/mockery": "Required to use mocking (^1.4.4).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", - "predis/predis": "Required to use the predis connector (^1.1.2).", + "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8).", + "predis/predis": "Required to use the predis connector (^1.1.9).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", - "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^6.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "8.x-dev" + "dev-master": "9.x-dev" } }, "autoload": { @@ -1506,7 +3685,8 @@ "Illuminate\\": "src/Illuminate/", "Illuminate\\Support\\": [ "src/Illuminate/Macroable/", - "src/Illuminate/Collections/" + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" ] } }, @@ -1530,36 +3710,596 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-07-02T16:50:12+00:00" + "time": "2022-05-25T18:18:07+00:00" }, { - "name": "laravel/tinker", - "version": "v2.6.1", + "name": "laravel/horizon", + "version": "v5.9.7", "source": { "type": "git", - "url": "https://github.com/laravel/tinker.git", - "reference": "04ad32c1a3328081097a181875733fa51f402083" + "url": "https://github.com/laravel/horizon.git", + "reference": "a98150769dc970972c30219662fe0e64259b733f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/04ad32c1a3328081097a181875733fa51f402083", - "reference": "04ad32c1a3328081097a181875733fa51f402083", + "url": "https://api.github.com/repos/laravel/horizon/zipball/a98150769dc970972c30219662fe0e64259b733f", + "reference": "a98150769dc970972c30219662fe0e64259b733f", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0", - "illuminate/contracts": "^6.0|^7.0|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0", + "ext-json": "*", + "ext-pcntl": "*", + "ext-posix": "*", + "illuminate/contracts": "^8.17|^9.0", + "illuminate/queue": "^8.17|^9.0", + "illuminate/support": "^8.17|^9.0", + "nesbot/carbon": "^2.17", + "php": "^7.3|^8.0", + "ramsey/uuid": "^4.0", + "symfony/error-handler": "^5.0|^6.0", + "symfony/process": "^5.0|^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^6.0|^7.0", + "phpunit/phpunit": "^9.0", + "predis/predis": "^1.1" + }, + "suggest": { + "ext-redis": "Required to use the Redis PHP driver.", + "predis/predis": "Required when not using the Redis PHP driver (^1.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Horizon\\HorizonServiceProvider" + ], + "aliases": { + "Horizon": "Laravel\\Horizon\\Horizon" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Horizon\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Dashboard and code-driven configuration for Laravel queues.", + "keywords": [ + "laravel", + "queue" + ], + "support": { + "issues": "https://github.com/laravel/horizon/issues", + "source": "https://github.com/laravel/horizon/tree/v5.9.7" + }, + "time": "2022-04-20T18:07:08+00:00" + }, + { + "name": "laravel/jetstream", + "version": "v2.8.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/jetstream.git", + "reference": "10a7914447c24f8ac9c6565c41f9f10711807401" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/jetstream/zipball/10a7914447c24f8ac9c6565c41f9f10711807401", + "reference": "10a7914447c24f8ac9c6565c41f9f10711807401", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/support": "^8.37|^9.0", + "jenssegers/agent": "^2.6", + "laravel/fortify": "^1.12", + "php": "^7.3|^8.0" + }, + "require-dev": { + "inertiajs/inertia-laravel": "^0.5.2", + "laravel/sanctum": "^2.7", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^6.0|^7.0", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Jetstream\\JetstreamServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Jetstream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Tailwind scaffolding for the Laravel framework.", + "keywords": [ + "auth", + "laravel", + "tailwind" + ], + "support": { + "issues": "https://github.com/laravel/jetstream/issues", + "source": "https://github.com/laravel/jetstream" + }, + "time": "2022-05-24T13:49:23+00:00" + }, + { + "name": "laravel/nova", + "version": "4.7.0", + "source": { + "type": "git", + "url": "git@github.com:laravel/nova.git", + "reference": "a07d9b466dca14810e7db14c4438312c075c5e7a" + }, + "dist": { + "type": "zip", + "url": "https://nova.laravel.com/dist/laravel/nova/laravel-nova-a07d9b466dca14810e7db14c4438312c075c5e7a-zip-9e7c08.zip", + "reference": "a07d9b466dca14810e7db14c4438312c075c5e7a", + "shasum": "244d7f8634b4160032bd8666ed402dbe2f198be5" + }, + "require": { + "brick/money": "^0.5.0", + "doctrine/dbal": "^2.13.3|^3.1.2", + "illuminate/support": "^8.83.4|^9.3.1", + "inertiajs/inertia-laravel": "^0.4.5|^0.5.2|^0.6.0", + "laravel/ui": "^3.3", + "nesbot/carbon": "^2.53.1", + "php": "^7.3|^8.0", + "rap2hpoutre/fast-excel": "^3.2", + "spatie/once": "^1.1|^2.0|^3.0", + "symfony/console": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "require-dev": { + "laravel/nova-dusk-suite": "8.4.x-dev|9.4.x-dev", + "laravel/scout": "^9.4", + "mockery/mockery": "^1.4.4", + "nunomaduro/larastan": "^1.0.1|^2.0", + "orchestra/testbench-dusk": "^6.24|^7.0", + "phpunit/phpunit": "^9.5.10", + "predis/predis": "^1.1.9" + }, + "suggest": { + "ext-intl": "Required to format Currency field" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Nova\\NovaCoreServiceProvider" + ], + "aliases": { + "Nova": "Laravel\\Nova\\Nova" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Nova\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Laravel\\Nova\\Tests\\": "tests/" + } + }, + "scripts": { + "dusk:prepare": [ + "./vendor/bin/dusk-updater detect --auto-update", + "@php -r \"file_exists('phpunit.dusk.xml') || copy('phpunit.dusk.xml.dist', 'phpunit.dusk.xml'); \"", + "@php -r \"if (file_exists('.env.dusk')) { copy('.env.dusk', 'vendor/laravel/nova-dusk-suite/.env'); } else { copy('.env.dusk.example', 'vendor/laravel/nova-dusk-suite/.env'); }\"", + "./vendor/bin/testbench-dusk package:discover" + ], + "dusk:dev-assets": [ + "npm ci", + "TAILWIND_MODE=build npm run dev", + "./vendor/bin/testbench-dusk nova:publish --force" + ], + "dusk:assets": [ + "npm ci", + "TAILWIND_MODE=build npm run prod", + "./vendor/bin/testbench-dusk nova:publish --force" + ], + "dusk:test": [ + "./vendor/bin/testbench-dusk package:dusk-purge", + "./vendor/bin/phpunit -c phpunit.dusk.xml" + ], + "dusk:filter": [ + "./vendor/bin/testbench-dusk package:dusk-purge && ./vendor/bin/phpunit -c phpunit.dusk.xml --filter" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "A wonderful administration interface for Laravel.", + "keywords": [ + "admin", + "laravel" + ], + "support": { + "source": "https://github.com/laravel/nova/tree/v4.7.0" + }, + "time": "2022-05-25T17:03:24+00:00" + }, + { + "name": "laravel/nova-log-viewer", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/nova-log-viewer.git", + "reference": "07e88d1e0aa24f8781c9fbb28ba80c214b731a58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/nova-log-viewer/zipball/07e88d1e0aa24f8781c9fbb28ba80c214b731a58", + "reference": "07e88d1e0aa24f8781c9fbb28ba80c214b731a58", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Nova\\LogViewer\\ToolServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Nova\\LogViewer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A Laravel Nova tool for viewing your application logs.", + "keywords": [ + "laravel", + "nova" + ], + "support": { + "source": "https://github.com/laravel/nova-log-viewer/tree/v0.1.2" + }, + "time": "2022-05-05T14:06:16+00:00" + }, + { + "name": "laravel/sanctum", + "version": "v2.15.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^6.9|^7.0|^8.0|^9.0", + "illuminate/contracts": "^6.9|^7.0|^8.0|^9.0", + "illuminate/database": "^6.9|^7.0|^8.0|^9.0", + "illuminate/support": "^6.9|^7.0|^8.0|^9.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2022-04-08T13:39:49+00:00" + }, + { + "name": "laravel/scout", + "version": "v9.4.9", + "source": { + "type": "git", + "url": "https://github.com/laravel/scout.git", + "reference": "61ce79ce87fbebb28dcc0dd8f95776aa0dec00c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/scout/zipball/61ce79ce87fbebb28dcc0dd8f95776aa0dec00c8", + "reference": "61ce79ce87fbebb28dcc0dd8f95776aa0dec00c8", + "shasum": "" + }, + "require": { + "illuminate/bus": "^8.0|^9.0", + "illuminate/contracts": "^8.0|^9.0", + "illuminate/database": "^8.0|^9.0", + "illuminate/http": "^8.0|^9.0", + "illuminate/pagination": "^8.0|^9.0", + "illuminate/queue": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "meilisearch/meilisearch-php": "^0.19", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^6.17|^7.0", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).", + "meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.23)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Scout\\ScoutServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Scout\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Scout provides a driver based solution to searching your Eloquent models.", + "keywords": [ + "algolia", + "laravel", + "search" + ], + "support": { + "issues": "https://github.com/laravel/scout/issues", + "source": "https://github.com/laravel/scout" + }, + "time": "2022-05-05T14:24:18+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, + { + "name": "laravel/telescope", + "version": "v4.9.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/telescope.git", + "reference": "d0cf8d6a54a1831dbe189a1f194e8271a4a5435a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/telescope/zipball/d0cf8d6a54a1831dbe189a1f194e8271a4a5435a", + "reference": "d0cf8d6a54a1831dbe189a1f194e8271a4a5435a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^8.37|^9.0", + "php": "^7.3|^8.0", + "symfony/var-dumper": "^5.0|^6.0" + }, + "require-dev": { + "ext-gd": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "orchestra/testbench": "^6.0|^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Telescope\\TelescopeServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Telescope\\": "src/", + "Laravel\\Telescope\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Mohamed Said", + "email": "mohamed@laravel.com" + } + ], + "description": "An elegant debug assistant for the Laravel framework.", + "keywords": [ + "debugging", + "laravel", + "monitoring" + ], + "support": { + "issues": "https://github.com/laravel/telescope/issues", + "source": "https://github.com/laravel/telescope/tree/v4.9.0" + }, + "time": "2022-04-19T15:48:51+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "dff39b661e827dae6e092412f976658df82dbac5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/dff39b661e827dae6e092412f976658df82dbac5", + "reference": "dff39b661e827dae6e092412f976658df82dbac5", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0", "php": "^7.2.5|^8.0", - "psy/psysh": "^0.10.4", - "symfony/var-dumper": "^4.3.4|^5.0" + "psy/psysh": "^0.10.4|^0.11.1", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0" }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0)." }, "type": "library", "extra": { @@ -1596,48 +4336,189 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.6.1" + "source": "https://github.com/laravel/tinker/tree/v2.7.2" }, - "time": "2021-03-02T16:53:12+00:00" + "time": "2022-03-23T12:38:24+00:00" }, { - "name": "league/commonmark", - "version": "1.6.5", + "name": "laravel/ui", + "version": "v3.4.6", "source": { "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "44ffd8d3c4a9133e4bd0548622b09c55af39db5f" + "url": "https://github.com/laravel/ui.git", + "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/44ffd8d3c4a9133e4bd0548622b09c55af39db5f", - "reference": "44ffd8d3c4a9133e4bd0548622b09c55af39db5f", + "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c", + "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c", + "shasum": "" + }, + "require": { + "illuminate/console": "^8.42|^9.0", + "illuminate/filesystem": "^8.42|^9.0", + "illuminate/support": "^8.82|^9.0", + "illuminate/validation": "^8.42|^9.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^6.23|^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Ui\\": "src/", + "Illuminate\\Foundation\\Auth\\": "auth-backend/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel UI utilities and presets.", + "keywords": [ + "laravel", + "ui" + ], + "support": { + "source": "https://github.com/laravel/ui/tree/v3.4.6" + }, + "time": "2022-05-20T13:38:08+00:00" + }, + { + "name": "laravie/parser", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/laravie/parser.git", + "reference": "c11aa5a3feeea54115a4eebce475535e5ee2d16a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravie/parser/zipball/c11aa5a3feeea54115a4eebce475535e5ee2d16a", + "reference": "c11aa5a3feeea54115a4eebce475535e5ee2d16a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": ">=7.1", + "tightenco/collect": "^5.6.16 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.1", + "phpunit/phpunit": "^7.5 || ^8.4 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravie\\Parser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "XML Document Parser for PHP", + "keywords": [ + "parser", + "xml" + ], + "support": { + "source": "https://github.com/laravie/parser/tree/v2.1.3" + }, + "funding": [ + { + "url": "https://paypal.me/crynobone", + "type": "custom" + }, + { + "url": "https://liberapay.com/crynobone", + "type": "liberapay" + } + ], + "time": "2021-12-07T14:02:00+00:00" + }, + { + "name": "league/commonmark", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "cb36fee279f7fca01d5d9399ddd1b37e48e2eca1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/cb36fee279f7fca01d5d9399ddd1b37e48e2eca1", + "reference": "cb36fee279f7fca01d5d9399ddd1b37e48e2eca1", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.1 || ^8.0" - }, - "conflict": { - "scrutinizer/ocular": "1.7.*" + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "cebe/markdown": "~1.0", - "commonmark/commonmark.js": "0.29.2", - "erusev/parsedown": "~1.0", + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", "ext-json": "*", "github/gfm": "0.29.0", - "michelf/php-markdown": "~1.4", - "mikehaertl/php-shellcommand": "^1.4", - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", - "scrutinizer/ocular": "^1.5", - "symfony/finder": "^4.2" + "michelf/php-markdown": "^1.4", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" }, - "bin": [ - "bin/commonmark" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + } + }, "autoload": { "psr-4": { "League\\CommonMark\\": "src" @@ -1655,7 +4536,7 @@ "role": "Lead Developer" } ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", "homepage": "https://commonmark.thephpleague.com", "keywords": [ "commonmark", @@ -1669,15 +4550,12 @@ ], "support": { "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", "issues": "https://github.com/thephpleague/commonmark/issues", "rss": "https://github.com/thephpleague/commonmark/releases.atom", "source": "https://github.com/thephpleague/commonmark" }, "funding": [ - { - "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", - "type": "custom" - }, { "url": "https://www.colinodell.com/sponsor", "type": "custom" @@ -1690,67 +4568,139 @@ "url": "https://github.com/colinodell", "type": "github" }, - { - "url": "https://www.patreon.com/colinodell", - "type": "patreon" - }, { "url": "https://tidelift.com/funding/github/packagist/league/commonmark", "type": "tidelift" } ], - "time": "2021-06-26T11:57:13+00:00" + "time": "2022-05-14T15:37:39+00:00" }, { - "name": "league/flysystem", - "version": "1.1.4", + "name": "league/config", + "version": "v1.1.1", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32" + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32", - "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "league/mime-type-detection": "^1.3", - "php": "^7.2.5 || ^8.0" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpspec/prophecy": "^1.11.1", - "phpunit/phpunit": "^8.5.8" - }, - "suggest": { - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-main": "1.2-dev" } }, "autoload": { "psr-4": { - "League\\Flysystem\\": "src/" + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "league/flysystem", + "version": "3.0.20", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "42a2f47dcf39944e2aee1b660ee55ab6ef69b535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/42a2f47dcf39944e2aee1b660ee55ab6ef69b535", + "reference": "42a2f47dcf39944e2aee1b660ee55ab6ef69b535", + "shasum": "" + }, + "require": { + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5", + "async-aws/simple-s3": "^1.0", + "aws/aws-sdk-php": "^3.198.1", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "microsoft/azure-storage-blob": "^1.1", + "phpseclib/phpseclib": "^2.0", + "phpstan/phpstan": "^0.12.26", + "phpunit/phpunit": "^9.5.11", + "sabre/dav": "^4.3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1760,73 +4710,71 @@ "authors": [ { "name": "Frank de Jonge", - "email": "info@frenky.net" + "email": "info@frankdejonge.nl" } ], - "description": "Filesystem abstraction: Many filesystems, one API.", + "description": "File storage abstraction for PHP", "keywords": [ - "Cloud Files", "WebDAV", - "abstraction", "aws", "cloud", - "copy.com", - "dropbox", - "file systems", + "file", "files", "filesystem", "filesystems", "ftp", - "rackspace", - "remote", "s3", "sftp", "storage" ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.1.4" + "source": "https://github.com/thephpleague/flysystem/tree/3.0.20" }, "funding": [ { "url": "https://offset.earth/frankdejonge", - "type": "other" + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" } ], - "time": "2021-06-23T21:56:05+00:00" + "time": "2022-05-25T19:18:39+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "1.0.29", + "version": "3.0.13", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "4e25cc0582a36a786c31115e419c6e40498f6972" + "reference": "0074cf016e21a6d1eb99b6db70acdd23743fc371" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4e25cc0582a36a786c31115e419c6e40498f6972", - "reference": "4e25cc0582a36a786c31115e419c6e40498f6972", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/0074cf016e21a6d1eb99b6db70acdd23743fc371", + "reference": "0074cf016e21a6d1eb99b6db70acdd23743fc371", "shasum": "" }, "require": { - "aws/aws-sdk-php": "^3.20.0", - "league/flysystem": "^1.0.40", - "php": ">=5.5.0" + "aws/aws-sdk-php": "^3.132.4", + "league/flysystem": "^2.0.0 || ^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.0.0" + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { - "League\\Flysystem\\AwsS3v3\\": "src/" + "League\\Flysystem\\AwsS3V3\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -1836,28 +4784,37 @@ "authors": [ { "name": "Frank de Jonge", - "email": "info@frenky.net" + "email": "info@frankdejonge.nl" } ], - "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.29" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.0.13" }, - "time": "2020-10-08T18:58:37+00:00" + "time": "2022-04-01T22:05:11+00:00" }, { "name": "league/mime-type-detection", - "version": "1.7.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", "shasum": "" }, "require": { @@ -1865,7 +4822,7 @@ "php": "^7.2 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.18", + "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", "phpunit/phpunit": "^8.5.8 || ^9.3" }, @@ -1888,7 +4845,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" }, "funding": [ { @@ -1900,52 +4857,189 @@ "type": "tidelift" } ], - "time": "2021-01-18T20:58:21+00:00" + "time": "2022-04-17T13:12:02+00:00" }, { - "name": "monolog/monolog", - "version": "2.3.0", + "name": "livewire/livewire", + "version": "v2.10.5", "source": { "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "df991fd88693ab703aa403413d83e15f688dae33" + "url": "https://github.com/livewire/livewire.git", + "reference": "9ea6237760f627b3b6a05d15137880780ac843b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/df991fd88693ab703aa403413d83e15f688dae33", - "reference": "df991fd88693ab703aa403413d83e15f688dae33", + "url": "https://api.github.com/repos/livewire/livewire/zipball/9ea6237760f627b3b6a05d15137880780ac843b5", + "reference": "9ea6237760f627b3b6a05d15137880780ac843b5", + "shasum": "" + }, + "require": { + "illuminate/database": "^7.0|^8.0|^9.0", + "illuminate/support": "^7.0|^8.0|^9.0", + "illuminate/validation": "^7.0|^8.0|^9.0", + "league/mime-type-detection": "^1.9", + "php": "^7.2.5|^8.0", + "symfony/http-kernel": "^5.0|^6.0" + }, + "require-dev": { + "calebporzio/sushi": "^2.1", + "laravel/framework": "^7.0|^8.0|^9.0", + "mockery/mockery": "^1.3.1", + "orchestra/testbench": "^5.0|^6.0|^7.0", + "orchestra/testbench-dusk": "^5.2|^6.0|^7.0", + "phpunit/phpunit": "^8.4|^9.0", + "psy/psysh": "@stable" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Livewire\\LivewireServiceProvider" + ], + "aliases": { + "Livewire": "Livewire\\Livewire" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Livewire\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "A front-end framework for Laravel.", + "support": { + "issues": "https://github.com/livewire/livewire/issues", + "source": "https://github.com/livewire/livewire/tree/v2.10.5" + }, + "funding": [ + { + "url": "https://github.com/livewire", + "type": "github" + } + ], + "time": "2022-04-07T21:38:12+00:00" + }, + { + "name": "mobiledetect/mobiledetectlib", + "version": "2.8.39", + "source": { + "type": "git", + "url": "https://github.com/serbanghita/Mobile-Detect.git", + "reference": "0fd6753003fc870f6e229bae869cc1337c99bc45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/0fd6753003fc870f6e229bae869cc1337c99bc45", + "reference": "0fd6753003fc870f6e229bae869cc1337c99bc45", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35||~5.7" + }, + "type": "library", + "autoload": { + "psr-0": { + "Detection": "namespaced/" + }, + "classmap": [ + "Mobile_Detect.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Serban Ghita", + "email": "serbanghita@gmail.com", + "homepage": "http://mobiledetect.net", + "role": "Developer" + } + ], + "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.", + "homepage": "https://github.com/serbanghita/Mobile-Detect", + "keywords": [ + "detect mobile devices", + "mobile", + "mobile detect", + "mobile detector", + "php mobile detect" + ], + "support": { + "issues": "https://github.com/serbanghita/Mobile-Detect/issues", + "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.39" + }, + "time": "2022-02-17T19:24:25+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "247918972acd74356b0a91dfaa5adcaec069b6c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/247918972acd74356b0a91dfaa5adcaec069b6c0", + "reference": "247918972acd74356b0a91dfaa5adcaec069b6c0", "shasum": "" }, "require": { "php": ">=7.2", - "psr/log": "^1.0.1" + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", "graylog2/gelf-php": "^1.4.2", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", - "php-amqplib/php-amqplib": "~2.4", + "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.3", - "phpspec/prophecy": "^1.6.1", + "phpspec/prophecy": "^1.15", "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5", + "phpunit/phpunit": "^8.5.14", "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90 <7.0.1", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", "ext-mbstring": "Allow to work properly with unicode symbols", "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", @@ -1984,7 +5078,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.3.0" + "source": "https://github.com/Seldaek/monolog/tree/2.6.0" }, "funding": [ { @@ -1996,7 +5090,7 @@ "type": "tidelift" } ], - "time": "2021-07-05T11:34:13+00:00" + "time": "2022-05-10T09:36:00+00:00" }, { "name": "mtdowling/jmespath.php", @@ -2030,12 +5124,12 @@ } }, "autoload": { - "psr-4": { - "JmesPath\\": "src/" - }, "files": [ "src/JmesPath.php" - ] + ], + "psr-4": { + "JmesPath\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2061,32 +5155,35 @@ }, { "name": "nesbot/carbon", - "version": "2.50.0", + "version": "2.58.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "f47f17d17602b2243414a44ad53d9f8b9ada5fdb" + "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f47f17d17602b2243414a44ad53d9f8b9ada5fdb", - "reference": "f47f17d17602b2243414a44ad53d9f8b9ada5fdb", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055", + "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^3.4 || ^4.0 || ^5.0" + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", "doctrine/orm": "^2.7", - "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", "phpmd/phpmd": "^2.9", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.54", - "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "phpstan/phpstan": "^0.12.54 || ^1.0", + "phpunit/php-file-iterator": "^2.0.5", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", "squizlabs/php_codesniffer": "^3.4" }, "bin": [ @@ -2095,8 +5192,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev", - "dev-3.x": "3.x-dev" + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" }, "laravel": { "providers": [ @@ -2137,6 +5234,7 @@ "time" ], "support": { + "docs": "https://carbon.nesbot.com/docs", "issues": "https://github.com/briannesbitt/Carbon/issues", "source": "https://github.com/briannesbitt/Carbon" }, @@ -2150,20 +5248,167 @@ "type": "tidelift" } ], - "time": "2021-06-28T22:38:45+00:00" + "time": "2022-04-25T19:31:17+00:00" }, { - "name": "nikic/php-parser", - "version": "v4.11.0", + "name": "nette/schema", + "version": "v1.2.2", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94" + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/fe14cf3672a149364fb66dfe11bf6549af899f94", - "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.13.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", "shasum": "" }, "require": { @@ -2204,44 +5449,48 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.11.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" }, - "time": "2021-07-03T13:36:55+00:00" + "time": "2021-11-30T19:35:32+00:00" }, { - "name": "opis/closure", - "version": "3.6.2", + "name": "olssonm/l5-zxcvbn", + "version": "v4.5", "source": { "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" + "url": "https://github.com/olssonm/laravel-zxcvbn.git", + "reference": "1ea671c7fea1a1fbfb10e03b7e1d971d3c856143" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", - "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", + "url": "https://api.github.com/repos/olssonm/laravel-zxcvbn/zipball/1ea671c7fea1a1fbfb10e03b7e1d971d3c856143", + "reference": "1ea671c7fea1a1fbfb10e03b7e1d971d3c856143", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0" + "bjeavons/zxcvbn-php": "^1.2", + "illuminate/support": "^7.0|^8.0|^9.0", + "php": "^7.3|^8.0" }, "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "orchestra/testbench": ">=4.0", + "phpunit/phpunit": "^8.0|^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.6.x-dev" + "dev-master": "4.x-dev" + }, + "laravel": { + "providers": [ + "Olssonm\\Zxcvbn\\ZxcvbnServiceProvider" + ] } }, "autoload": { "psr-4": { - "Opis\\Closure\\": "src/" - }, - "files": [ - "functions.php" - ] + "Olssonm\\Zxcvbn\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2249,55 +5498,280 @@ ], "authors": [ { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "Marcus Olsson", + "email": "contact@marcusolsson.me", + "homepage": "https://marcusolsson.me" } ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", + "description": "Implementation of the zxcvbn project by @dropbox for Laravel. Uses zxcvbn-php by @bjeavons.", + "homepage": "https://github.com/olssonm/laravel-zxcvbn", "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" + "laravel", + "olssonm", + "passwords", + "security", + "staple horse battery", + "validation", + "zxcvbn" ], "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.2" + "issues": "https://github.com/olssonm/laravel-zxcvbn/issues", + "source": "https://github.com/olssonm/laravel-zxcvbn/tree/v4.5" }, - "time": "2021-04-09T13:42:10+00:00" + "time": "2022-02-08T16:33:29+00:00" }, { - "name": "phpoption/phpoption", - "version": "1.7.5", + "name": "orchestra/parser", + "version": "v7.0.1", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + "url": "https://github.com/orchestral/parser.git", + "reference": "7963a827c853a7d1a79a3b03226a3191fa445dba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "url": "https://api.github.com/repos/orchestral/parser/zipball/7963a827c853a7d1a79a3b03226a3191fa445dba", + "reference": "7963a827c853a7d1a79a3b03226a3191fa445dba", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0" + "illuminate/container": "^9.0", + "laravie/parser": "^2.1.3", + "php": "^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + "orchestra/testbench": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "8.0-dev" + }, + "laravel": { + "providers": [ + "Orchestra\\Parser\\XmlServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Orchestra\\Parser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "XML Document Parser for Laravel and PHP", + "keywords": [ + "laravel", + "orchestra-platform", + "orchestral", + "parser", + "xml" + ], + "support": { + "source": "https://github.com/orchestral/parser/tree/v7.0.1" + }, + "funding": [ + { + "url": "https://paypal.me/crynobone", + "type": "custom" + }, + { + "url": "https://liberapay.com/crynobone", + "type": "liberapay" + } + ], + "time": "2022-02-26T22:04:11+00:00" + }, + { + "name": "owen-it/laravel-auditing", + "version": "v13.0.3", + "source": { + "type": "git", + "url": "https://github.com/owen-it/laravel-auditing.git", + "reference": "332d136a7d67a2c06ee85aa17efeafb3ba25c972" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/owen-it/laravel-auditing/zipball/332d136a7d67a2c06ee85aa17efeafb3ba25c972", + "reference": "332d136a7d67a2c06ee85aa17efeafb3ba25c972", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^7.0|^8.0|^9.0", + "illuminate/database": "^7.0|^8.0|^9.0", + "illuminate/filesystem": "^7.0|^8.0|^9.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "laravel/legacy-factories": "*", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^5.0|^6.0|^7.0", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "laravelista/lumen-vendor-publish": "Needed to publish the package configuration in Lumen" + }, + "type": "package", + "extra": { + "branch-alias": { + "dev-master": "v13-dev" + }, + "laravel": { + "providers": [ + "OwenIt\\Auditing\\AuditingServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "OwenIt\\Auditing\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antério Vieira", + "email": "anteriovieira@gmail.com" + }, + { + "name": "Raphael França", + "email": "raphaelfrancabsb@gmail.com" + }, + { + "name": "Morten D. Hansen", + "email": "morten@visia.dk" + } + ], + "description": "Audit changes of your Eloquent models in Laravel/Lumen", + "homepage": "http://laravel-auditing.com", + "keywords": [ + "Accountability", + "Audit", + "auditing", + "changes", + "eloquent", + "history", + "laravel", + "log", + "logging", + "lumen", + "observer", + "record", + "revision", + "tracking" + ], + "support": { + "issues": "https://github.com/owen-it/laravel-auditing/issues", + "source": "https://github.com/owen-it/laravel-auditing" + }, + "time": "2022-04-20T14:28:57+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9229e15f2e6ba772f0c55dd6986c563b937170a8", + "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2022-01-17T05:32:27+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" } }, "autoload": { @@ -2312,11 +5786,13 @@ "authors": [ { "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" }, { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "Option Type for PHP", @@ -2328,7 +5804,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + "source": "https://github.com/schmittjoh/php-option/tree/1.8.1" }, "funding": [ { @@ -2340,26 +5816,132 @@ "type": "tidelift" } ], - "time": "2020-07-20T17:29:33+00:00" + "time": "2021-12-04T23:24:31+00:00" }, { - "name": "psr/container", - "version": "1.1.1", + "name": "pragmarx/google2fa", + "version": "8.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/26c4c5cf30a2844ba121760fd7301f8ad240100b", + "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b", "shasum": "" }, "require": { - "php": ">=7.2.0" + "paragonie/constant_time_encoding": "^1.0|^2.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.18", + "phpunit/phpunit": "^7.5.15|^8.5|^9.0" }, "type": "library", + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "source": "https://github.com/antonioribeiro/google2fa/tree/8.0.0" + }, + "time": "2020-04-05T10:47:18+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -2386,9 +5968,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/event-dispatcher", @@ -2492,6 +6074,61 @@ }, "time": "2020-06-29T06:28:15+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -2547,30 +6184,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2591,31 +6228,31 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:46:02+00:00" }, { "name": "psr/simple-cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -2630,7 +6267,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for simple caching", @@ -2642,42 +6279,43 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/master" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2017-10-23T01:57:42+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { "name": "psy/psysh", - "version": "v0.10.8", + "version": "v0.11.5", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3" + "reference": "c23686f9c48ca202710dbb967df8385a952a2daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3", - "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/c23686f9c48ca202710dbb967df8385a952a2daf", + "reference": "c23686f9c48ca202710dbb967df8385a952a2daf", "shasum": "" }, "require": { "ext-json": "*", "ext-tokenizer": "*", - "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", - "php": "^8.0 || ^7.0 || ^5.5.9", - "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", - "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" + "nikic/php-parser": "^4.0 || ^3.1", + "php": "^8.0 || ^7.0.8", + "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "3.17.*" + "bamarni/composer-bin-plugin": "^1.2" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", "ext-pdo-sqlite": "The doc command requires SQLite to work.", "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", - "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." }, "bin": [ "bin/psysh" @@ -2685,7 +6323,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.10.x-dev" + "dev-main": "0.11.x-dev" } }, "autoload": { @@ -2717,9 +6355,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.10.8" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.5" }, - "time": "2021-04-10T16:23:39+00:00" + "time": "2022-05-27T18:03:49+00:00" }, { "name": "ralouphie/getallheaders", @@ -2767,20 +6405,21 @@ }, { "name": "ramsey/collection", - "version": "1.1.3", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", "shasum": "" }, "require": { - "php": "^7.2 || ^8" + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" }, "require-dev": { "captainhook/captainhook": "^5.3", @@ -2790,6 +6429,7 @@ "hamcrest/hamcrest-php": "^2", "jangregor/phpstan-prophecy": "^0.8", "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", "phpstan/extension-installer": "^1", "phpstan/phpstan": "^0.12.32", "phpstan/phpstan-mockery": "^0.12.5", @@ -2817,7 +6457,7 @@ "homepage": "https://benramsey.com" } ], - "description": "A PHP 7.2+ library for representing and manipulating collections.", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ "array", "collection", @@ -2828,7 +6468,7 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.1.3" + "source": "https://github.com/ramsey/collection/tree/1.2.2" }, "funding": [ { @@ -2840,53 +6480,53 @@ "type": "tidelift" } ], - "time": "2021-01-21T17:40:04+00:00" + "time": "2021-10-10T03:01:02+00:00" }, { "name": "ramsey/uuid", - "version": "4.1.1", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "cd4032040a750077205918c86049aa0f43d22947" + "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947", - "reference": "cd4032040a750077205918c86049aa0f43d22947", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", + "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", "shasum": "" }, "require": { "brick/math": "^0.8 || ^0.9", + "ext-ctype": "*", "ext-json": "*", - "php": "^7.2 || ^8", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8" + "php": "^8.0", + "ramsey/collection": "^1.0" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0", + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", "doctrine/annotations": "^1.8", - "goaop/framework": "^2", + "ergebnis/composer-normalize": "^2.15", "mockery/mockery": "^1.3", "moontoast/math": "^1.1", "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", "php-mock/php-mock-mockery": "^1.3", - "php-mock/php-mock-phpunit": "^2.5", "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^0.17.1", + "phpbench/phpbench": "^1.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12", "phpstan/phpstan-mockery": "^0.12", "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "psy/psysh": "^0.10.0", - "slevomat/coding-standard": "^6.0", + "phpunit/phpunit": "^8.5 || ^9", + "slevomat/coding-standard": "^7.0", "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "3.9.4" + "vimeo/psalm": "^4.9" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -2898,24 +6538,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.x-dev" + "captainhook": { + "force-install": true } }, "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "homepage": "https://github.com/ramsey/uuid", "keywords": [ "guid", "identifier", @@ -2923,55 +6562,58 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "rss": "https://github.com/ramsey/uuid/releases.atom", - "source": "https://github.com/ramsey/uuid" + "source": "https://github.com/ramsey/uuid/tree/4.3.1" }, "funding": [ { "url": "https://github.com/ramsey", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" } ], - "time": "2020-08-18T17:17:46+00:00" + "time": "2022-03-27T21:42:02+00:00" }, { - "name": "swiftmailer/swiftmailer", - "version": "v6.2.7", + "name": "rap2hpoutre/fast-excel", + "version": "v3.2.0", "source": { "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "15f7faf8508e04471f666633addacf54c0ab5933" + "url": "https://github.com/rap2hpoutre/fast-excel.git", + "reference": "28183f3a90179386bfadcd0083129c247ce49fbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/15f7faf8508e04471f666633addacf54c0ab5933", - "reference": "15f7faf8508e04471f666633addacf54c0ab5933", + "url": "https://api.github.com/repos/rap2hpoutre/fast-excel/zipball/28183f3a90179386bfadcd0083129c247ce49fbe", + "reference": "28183f3a90179386bfadcd0083129c247ce49fbe", "shasum": "" }, "require": { - "egulias/email-validator": "^2.0|^3.1", - "php": ">=7.0.0", - "symfony/polyfill-iconv": "^1.0", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" + "box/spout": "^3", + "illuminate/support": "5.3.* || 5.4.* || 5.5.* || 5.6.* || 5.7.* || 5.8.* || ^6.0 || ^7.0 || ^8.0|^9.0", + "php": "^7.1|^8.0" }, "require-dev": { - "mockery/mockery": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.0" - }, - "suggest": { - "ext-intl": "Needed to support internationalized email addresses" + "illuminate/database": "^6.20.12 || ^7.30.4 || ^8.24.0|^9.0", + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "6.2-dev" + "laravel": { + "providers": [ + "Rap2hpoutre\\FastExcel\\Providers\\FastExcelServiceProvider" + ] } }, "autoload": { "files": [ - "lib/swift_required.php" - ] + "src/functions/fastexcel.php" + ], + "psr-4": { + "Rap2hpoutre\\FastExcel\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2979,77 +6621,722 @@ ], "authors": [ { - "name": "Chris Corbyn" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "rap2h", + "email": "raphaelht@gmail.com" } ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", + "description": "Fast Excel import/export for Laravel", "keywords": [ - "email", - "mail", - "mailer" + "csv", + "excel", + "laravel", + "xls", + "xlsx" ], "support": { - "issues": "https://github.com/swiftmailer/swiftmailer/issues", - "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.7" + "issues": "https://github.com/rap2hpoutre/fast-excel/issues", + "source": "https://github.com/rap2hpoutre/fast-excel/tree/v3.2.0" }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", - "type": "tidelift" - } - ], - "time": "2021-03-09T12:30:35+00:00" + "time": "2022-02-09T16:04:44+00:00" }, { - "name": "symfony/console", - "version": "v5.3.2", + "name": "react/promise", + "version": "v2.9.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" + "url": "https://github.com/reactphp/promise.git", + "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910", + "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" - }, - "conflict": { - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0" + "php": ">=5.4.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.9.0" + }, + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-02-11T10:27:51+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "4211420d25eba80712bff236a98960ef68b866b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7", + "reference": "4211420d25eba80712bff236a98960ef68b866b7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2022-04-01T13:37:23+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "9f3452c93ff423469c0d56450431562ca423dcee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/9f3452c93ff423469c0d56450431562ca423dcee", + "reference": "9f3452c93ff423469c0d56450431562ca423dcee", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.0" + }, + "time": "2021-12-10T11:20:11+00:00" + }, + { + "name": "spatie/db-dumper", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/db-dumper.git", + "reference": "17152c3fd799fb55b34f35885f8f129678faed73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/17152c3fd799fb55b34f35885f8f129678faed73", + "reference": "17152c3fd799fb55b34f35885f8f129678faed73", + "shasum": "" + }, + "require": { + "php": "^8.0", + "symfony/process": "^5.0|^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\DbDumper\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Dump databases", + "homepage": "https://github.com/spatie/db-dumper", + "keywords": [ + "database", + "db-dumper", + "dump", + "mysqldump", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/db-dumper/issues", + "source": "https://github.com/spatie/db-dumper/tree/3.2.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-03-10T16:01:42+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.11.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "baeb3df0ebb3a541394fdaf8cbe6115bf4034a59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/baeb3df0ebb3a541394fdaf8cbe6115bf4034a59", + "reference": "baeb3df0ebb3a541394fdaf8cbe6115bf4034a59", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^7.0|^8.0|^9.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.4", + "orchestra/testbench": "^5.0|^6.23|^7.0", + "phpunit/phpunit": "^9.4", + "spatie/test-time": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.11.3" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-03-15T20:01:36+00:00" + }, + { + "name": "spatie/laravel-permission", + "version": "5.5.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "cb86fd87b43fcfc493c3f2b1de6fad100c078146" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/cb86fd87b43fcfc493c3f2b1de6fad100c078146", + "reference": "cb86fd87b43fcfc493c3f2b1de6fad100c078146", + "shasum": "" + }, + "require": { + "illuminate/auth": "^7.0|^8.0|^9.0", + "illuminate/container": "^7.0|^8.0|^9.0", + "illuminate/contracts": "^7.0|^8.0|^9.0", + "illuminate/database": "^7.0|^8.0|^9.0", + "php": "^7.3|^8.0|^8.1" + }, + "require-dev": { + "orchestra/testbench": "^5.0|^6.0|^7.0", + "phpunit/phpunit": "^9.4", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "5.x-dev", + "dev-master": "5.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 6.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/5.5.4" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-05-16T12:09:59+00:00" + }, + { + "name": "spatie/laravel-route-discovery", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-route-discovery.git", + "reference": "5f1938f64b60e6128f494d6ddee1faa818d8935d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-route-discovery/zipball/5f1938f64b60e6128f494d6ddee1faa818d8935d", + "reference": "5f1938f64b60e6128f494d6ddee1faa818d8935d", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.67|^9.0", + "illuminate/support": "^8.77|^9.0", + "php": "^8.0", + "spatie/laravel-package-tools": "^1.10", + "symfony/finder": "^5.4.2|^6.0" + }, + "require-dev": { + "nunomaduro/larastan": "^1.0.2", + "orchestra/testbench": "^6.23.2|^7.0", + "pestphp/pest": "^1.21", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/laravel-ray": "^1.27" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\RouteDiscovery\\RouteDiscoveryServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\RouteDiscovery\\": "src", + "Spatie\\RouteDiscovery\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Auto register routes using PHP attributes", + "homepage": "https://github.com/spatie/laravel-route-discovery", + "keywords": [ + "laravel-route-discovery", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-route-discovery/issues", + "source": "https://github.com/spatie/laravel-route-discovery/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2022-03-08T14:54:07+00:00" + }, + { + "name": "spatie/laravel-validation-rules", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-validation-rules.git", + "reference": "40affd1d4eb4c084f041a79822b9fe8d17b5e877" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-validation-rules/zipball/40affd1d4eb4c084f041a79822b9fe8d17b5e877", + "reference": "40affd1d4eb4c084f041a79822b9fe8d17b5e877", + "shasum": "" + }, + "require": { + "illuminate/support": "^8.0|^9.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "league/iso3166": "^3.0", + "myclabs/php-enum": "^1.6", + "orchestra/testbench": "^6.23|^7.0", + "phpunit/phpunit": "^9.4", + "spatie/enum": "^2.2|^3.0" + }, + "suggest": { + "league/iso3166": "Needed for the CountryCode rule and Currency rule" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\ValidationRules\\ValidationRulesServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\ValidationRules\\": "src", + "Spatie\\ValidationRules\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A set of useful Laravel validation rules", + "homepage": "https://github.com/spatie/laravel-validation-rules", + "keywords": [ + "laravel-validation-rules", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-validation-rules/issues", + "source": "https://github.com/spatie/laravel-validation-rules/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-01-12T08:33:31+00:00" + }, + { + "name": "spatie/once", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/once.git", + "reference": "aab3fc8694e7caec98b2d330c4a8420391c7559d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/once/zipball/aab3fc8694e7caec98b2d330c4a8420391c7559d", + "reference": "aab3fc8694e7caec98b2d330c4a8420391c7559d", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.21", + "symfony/var-dumper": "^5.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Spatie\\Once\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A magic memoization function", + "homepage": "https://github.com/spatie/once", + "keywords": [ + "cache", + "callable", + "memoization", + "once", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/once/tree/3.1.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2022-04-21T12:23:20+00:00" + }, + { + "name": "staudenmeir/belongs-to-through", + "version": "v2.12.1", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/belongs-to-through.git", + "reference": "8316d274db603f63b16bb1c67379b0fa73209d98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/8316d274db603f63b16bb1c67379b0fa73209d98", + "reference": "8316d274db603f63b16bb1c67379b0fa73209d98", + "shasum": "" + }, + "require": { + "illuminate/database": "^9.0", + "php": "^8.0.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Znck\\Eloquent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rahul Kadyan", + "email": "hi@znck.me" + }, + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Laravel Eloquent BelongsToThrough relationships", + "support": { + "issues": "https://github.com/staudenmeir/belongs-to-through/issues", + "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.12.1" + }, + "funding": [ + { + "url": "https://paypal.me/JonasStaudenmeir", + "type": "custom" + } + ], + "time": "2022-03-10T21:14:19+00:00" + }, + { + "name": "symfony/console", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c9646197ef43b0e2ff44af61e7f0571526fd4170", + "reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -3089,7 +7376,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.2" + "source": "https://github.com/symfony/console/tree/v6.1.0" }, "funding": [ { @@ -3105,24 +7392,24 @@ "type": "tidelift" } ], - "time": "2021-06-12T09:42:48+00:00" + "time": "2022-05-27T06:34:22+00:00" }, { "name": "symfony/css-selector", - "version": "v5.3.0", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814" + "reference": "05c40f02f621609404b8820ff8bc39acb46e19cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/05c40f02f621609404b8820ff8bc39acb46e19cf", + "reference": "05c40f02f621609404b8820ff8bc39acb46e19cf", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -3154,7 +7441,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.3.0" + "source": "https://github.com/symfony/css-selector/tree/v6.1.0" }, "funding": [ { @@ -3170,29 +7457,29 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:40:38+00:00" + "time": "2022-02-25T11:15:52+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.4.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -3221,7 +7508,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0" }, "funding": [ { @@ -3237,33 +7524,35 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2022-02-25T11:15:52+00:00" }, { "name": "symfony/error-handler", - "version": "v5.3.3", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "43323e79c80719e8a4674e33484bca98270d223f" + "reference": "d02c662651e5de760bb7d5e94437113309e8f8a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/43323e79c80719e8a4674e33484bca98270d223f", - "reference": "43323e79c80719e8a4674e33484bca98270d223f", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d02c662651e5de760bb7d5e94437113309e8f8a0", + "reference": "d02c662651e5de760bb7d5e94437113309e8f8a0", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/var-dumper": "^4.4|^5.0" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], "type": "library", "autoload": { "psr-4": { @@ -3290,7 +7579,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.3.3" + "source": "https://github.com/symfony/error-handler/tree/v6.1.0" }, "funding": [ { @@ -3306,44 +7595,42 @@ "type": "tidelift" } ], - "time": "2021-06-24T08:13:00+00:00" + "time": "2022-05-23T10:32:57+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.3.0", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce" + "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a0449a7ad7daa0f7c0acd508259f80544ab5a347", + "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<5.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^5.4|^6.0" }, "suggest": { "symfony/dependency-injection": "", @@ -3375,7 +7662,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.1.0" }, "funding": [ { @@ -3391,24 +7678,24 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-05-05T16:51:07+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.4.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/02ff5eea2f453731cfbc6bc215e456b781480448", + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, "suggest": { @@ -3417,7 +7704,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -3454,7 +7741,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.0" }, "funding": [ { @@ -3470,24 +7757,90 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2022-02-25T11:15:52+00:00" }, { - "name": "symfony/finder", - "version": "v5.3.0", + "name": "symfony/filesystem", + "version": "v6.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6" + "url": "https://github.com/symfony/filesystem.git", + "reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3132d2f43ca799c2aa099f9738d98228c56baa5d", + "reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-21T13:34:40+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/45b8beb69d6eb3b05a65689ebfd4222326773f8f", + "reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" }, "type": "library", "autoload": { @@ -3515,7 +7868,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.3.0" + "source": "https://github.com/symfony/finder/tree/v6.1.0" }, "funding": [ { @@ -3531,24 +7884,108 @@ "type": "tidelift" } ], - "time": "2021-05-26T12:52:38+00:00" + "time": "2022-04-15T08:08:08+00:00" }, { - "name": "symfony/http-client-contracts", - "version": "v2.4.0", + "name": "symfony/http-client", + "version": "v6.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4" + "url": "https://github.com/symfony/http-client.git", + "reference": "aa0f9bae4e9f0328373f2cdf93996fb2278b0dd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "url": "https://api.github.com/repos/symfony/http-client/zipball/aa0f9bae4e9f0328373f2cdf93996fb2278b0dd6", + "reference": "aa0f9bae4e9f0328373f2cdf93996fb2278b0dd6", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-21T13:34:40+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "fd038f08c623ab5d22b26e9ba35afe8c79071800" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/fd038f08c623ab5d22b26e9ba35afe8c79071800", + "reference": "fd038f08c623ab5d22b26e9ba35afe8c79071800", + "shasum": "" + }, + "require": { + "php": ">=8.1" }, "suggest": { "symfony/http-client-implementation": "" @@ -3556,7 +7993,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -3566,7 +8003,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3593,7 +8033,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.1.0" }, "funding": [ { @@ -3609,33 +8049,32 @@ "type": "tidelift" } ], - "time": "2021-04-11T23:07:08+00:00" + "time": "2022-04-22T07:30:54+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.3.3", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "0e45ab1574caa0460d9190871a8ce47539e40ccf" + "reference": "18f8a2a3ab703428143c27c055fd743ab7e7dcb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0e45ab1574caa0460d9190871a8ce47539e40ccf", - "reference": "0e45ab1574caa0460d9190871a8ce47539e40ccf", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/18f8a2a3ab703428143c27c055fd743ab7e7dcb1", + "reference": "18f8a2a3ab703428143c27c055fd743ab7e7dcb1", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0" + "symfony/cache": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0" }, "suggest": { "symfony/mime": "To use the file extension guesser" @@ -3666,7 +8105,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.3.3" + "source": "https://github.com/symfony/http-foundation/tree/v6.1.0" }, "funding": [ { @@ -3682,68 +8121,66 @@ "type": "tidelift" } ], - "time": "2021-06-27T09:19:40+00:00" + "time": "2022-05-21T13:34:40+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.3.3", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8" + "reference": "a57c7084bd0604d80d70c89c6662512c698352d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8", - "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a57c7084bd0604d80d70c89c6662512c698352d1", + "reference": "a57c7084bd0604d80d70c89c6662512c698352d1", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "~1.0", - "symfony/deprecation-contracts": "^2.1", - "symfony/error-handler": "^4.4|^5.0", - "symfony/event-dispatcher": "^5.0", - "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^5.3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<4.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.3", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.1", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/config": "^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dependency-injection": "^5.3", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/routing": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^6.1", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "suggest": { @@ -3778,7 +8215,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.3.3" + "source": "https://github.com/symfony/http-kernel/tree/v6.1.0" }, "funding": [ { @@ -3794,42 +8231,259 @@ "type": "tidelift" } ], - "time": "2021-06-30T08:27:49+00:00" + "time": "2022-05-27T07:21:03+00:00" }, { - "name": "symfony/mime", - "version": "v5.3.2", + "name": "symfony/intl", + "version": "v6.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a" + "url": "https://github.com/symfony/intl.git", + "reference": "9fc07ba57eb33d44c6b3b86b4e1ef927620d1a39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/47dd7912152b82d0d4c8d9040dbc93d6232d472a", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a", + "url": "https://api.github.com/repos/symfony/intl/zipball/9fc07ba57eb33d44c6b3b86b4e1ef927620d1a39", + "reference": "9fc07ba57eb33d44c6b3b86b4e1ef927620d1a39", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", + "homepage": "https://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "support": { + "source": "https://github.com/symfony/intl/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-12T16:22:53+00:00" + }, + { + "name": "symfony/mailer", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "bc4338d7729aafaaac8559e1a4680ee97b8bfedb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/bc4338d7729aafaaac8559e1a4680ee97b8bfedb", + "reference": "bc4338d7729aafaaac8559e1a4680ee97b8bfedb", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3", + "php": ">=8.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3" + }, + "conflict": { + "symfony/http-kernel": "<5.4" + }, + "require-dev": { + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/messenger": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-27T17:11:01+00:00" + }, + { + "name": "symfony/mailgun-mailer", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailgun-mailer.git", + "reference": "44d3c15049d84f5165917a6190f06adbe64d71dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/44d3c15049d84f5165917a6190f06adbe64d71dd", + "reference": "44d3c15049d84f5165917a6190f06adbe64d71dd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/mailer": "^5.4|^6.0" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0" + }, + "type": "symfony-mailer-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\Bridge\\Mailgun\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Mailgun Mailer Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-01T07:15:35+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "032e796edbf842bc4b4c81c42598b144b95ce56a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/032e796edbf842bc4b4c81c42598b144b95ce56a", + "reference": "032e796edbf842bc4b4c81c42598b144b95ce56a", + "shasum": "" + }, + "require": { + "php": ">=8.1", "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<4.4" + "symfony/mailer": "<5.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.1", - "symfony/property-info": "^4.4|^5.1", - "symfony/serializer": "^5.2" + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -3861,7 +8515,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.3.2" + "source": "https://github.com/symfony/mime/tree/v6.1.0" }, "funding": [ { @@ -3877,25 +8531,28 @@ "type": "tidelift" } ], - "time": "2021-06-09T10:58:01+00:00" + "time": "2022-05-21T13:34:40+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "reference": "30885182c981ab175d4d034db0f6f469898070ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-ctype": "*" + }, "suggest": { "ext-ctype": "For best performance" }, @@ -3910,12 +8567,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3940,7 +8597,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" }, "funding": [ { @@ -3956,100 +8613,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" - }, - { - "name": "symfony/polyfill-iconv", - "version": "v1.23.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-iconv": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Iconv extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "iconv", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2021-10-20T20:35:02+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", "shasum": "" }, "require": { @@ -4069,12 +8646,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4101,7 +8678,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" }, "funding": [ { @@ -4117,20 +8694,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2021-11-23T21:10:46+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65" + "reference": "749045c69efb97c70d25d7463abba812e91f3a44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65", - "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", + "reference": "749045c69efb97c70d25d7463abba812e91f3a44", "shasum": "" }, "require": { @@ -4152,12 +8729,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4188,7 +8765,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" }, "funding": [ { @@ -4204,11 +8781,11 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2021-09-14T14:02:44+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -4237,12 +8814,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -4272,7 +8849,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" }, "funding": [ { @@ -4292,21 +8869,24 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-mbstring": "*" + }, "suggest": { "ext-mbstring": "For best performance" }, @@ -4321,12 +8901,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4352,7 +8932,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" }, "funding": [ { @@ -4368,11 +8948,11 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2021-11-30T18:21:41+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", @@ -4398,12 +8978,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4428,7 +9008,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" }, "funding": [ { @@ -4448,16 +9028,16 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", "shasum": "" }, "require": { @@ -4474,12 +9054,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -4507,7 +9087,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" }, "funding": [ { @@ -4523,20 +9103,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-06-05T21:20:04+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", "shasum": "" }, "require": { @@ -4553,12 +9133,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -4590,7 +9170,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" }, "funding": [ { @@ -4606,25 +9186,103 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-03-04T08:16:47+00:00" }, { - "name": "symfony/process", - "version": "v5.3.2", + "name": "symfony/polyfill-php81", + "version": "v1.25.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/714b47f9196de61a196d86c4bad5f09201b307df", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-09-13T13:58:11+00:00" + }, + { + "name": "symfony/process", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "318718453c2be58266f1a9e74063d13cb8dd4165" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/318718453c2be58266f1a9e74063d13cb8dd4165", + "reference": "318718453c2be58266f1a9e74063d13cb8dd4165", + "shasum": "" + }, + "require": { + "php": ">=8.1" }, "type": "library", "autoload": { @@ -4652,7 +9310,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.3.2" + "source": "https://github.com/symfony/process/tree/v6.1.0" }, "funding": [ { @@ -4668,41 +9326,39 @@ "type": "tidelift" } ], - "time": "2021-06-12T10:15:01+00:00" + "time": "2022-05-11T12:12:29+00:00" }, { "name": "symfony/routing", - "version": "v5.3.0", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199" + "reference": "03c13cbaefdec9fbb7a62ed18235d487686540dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/368e81376a8e049c37cb80ae87dbfbf411279199", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199", + "url": "https://api.github.com/repos/symfony/routing/zipball/03c13cbaefdec9fbb7a62ed18235d487686540dd", + "reference": "03c13cbaefdec9fbb7a62ed18235d487686540dd", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1" }, "conflict": { "doctrine/annotations": "<1.12", - "symfony/config": "<5.3", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/config": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.12", - "psr/log": "~1.0", - "symfony/config": "^5.3", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "suggest": { "symfony/config": "For using the all-in-one router or any loader", @@ -4742,7 +9398,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.3.0" + "source": "https://github.com/symfony/routing/tree/v6.1.0" }, "funding": [ { @@ -4758,25 +9414,28 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2022-04-27T17:06:58+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.4.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + "reference": "d66cd8ab656780f62c4215b903a420eb86358957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957", + "reference": "d66cd8ab656780f62c4215b903a420eb86358957", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1" + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "suggest": { "symfony/service-implementation": "" @@ -4784,7 +9443,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -4794,7 +9453,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4821,7 +9483,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.1.0" }, "funding": [ { @@ -4837,44 +9499,46 @@ "type": "tidelift" } ], - "time": "2021-04-01T10:43:52+00:00" + "time": "2022-05-07T08:07:09+00:00" }, { "name": "symfony/string", - "version": "v5.3.3", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" + "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529", + "reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -4904,7 +9568,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.3" + "source": "https://github.com/symfony/string/tree/v6.1.0" }, "funding": [ { @@ -4920,50 +9584,51 @@ "type": "tidelift" } ], - "time": "2021-06-27T11:44:38+00:00" + "time": "2022-04-22T08:18:23+00:00" }, { "name": "symfony/translation", - "version": "v5.3.3", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "380b8c9e944d0e364b25f28e8e555241eb49c01c" + "reference": "b254416631615bc6fe49b0a67f18658827288147" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/380b8c9e944d0e364b25f28e8e555241eb49c01c", - "reference": "380b8c9e944d0e364b25f28e8e555241eb49c01c", + "url": "https://api.github.com/repos/symfony/translation/zipball/b254416631615bc6fe49b0a67f18658827288147", + "reference": "b254416631615bc6fe49b0a67f18658827288147", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "php": ">=8.1", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/translation-contracts": "^2.3" + "symfony/translation-contracts": "^2.3|^3.0" }, "conflict": { - "symfony/config": "<4.4", - "symfony/dependency-injection": "<5.0", - "symfony/http-kernel": "<5.0", - "symfony/twig-bundle": "<5.0", - "symfony/yaml": "<4.4" + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" }, "provide": { - "symfony/translation-implementation": "2.3" + "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/http-kernel": "^5.0", - "symfony/intl": "^4.4|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/service-contracts": "^1.1.2|^2", - "symfony/yaml": "^4.4|^5.0" + "symfony/routing": "^5.4|^6.0", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^5.4|^6.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -4999,7 +9664,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.3.3" + "source": "https://github.com/symfony/translation/tree/v6.1.0" }, "funding": [ { @@ -5015,24 +9680,24 @@ "type": "tidelift" } ], - "time": "2021-06-27T12:22:47+00:00" + "time": "2022-05-11T12:12:29+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.4.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95" + "reference": "bfddd2a1faa271b782b791c361cc16e2dd49dfaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/bfddd2a1faa271b782b791c361cc16e2dd49dfaa", + "reference": "bfddd2a1faa271b782b791c361cc16e2dd49dfaa", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.1" }, "suggest": { "symfony/translation-implementation": "" @@ -5040,7 +9705,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -5050,7 +9715,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Translation\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5077,7 +9745,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.1.0" }, "funding": [ { @@ -5093,35 +9761,35 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2022-04-22T07:30:54+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.3.3", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "46aa709affb9ad3355bd7a810f9662d71025c384" + "reference": "98587d939cb783aa04e828e8fa857edaca24c212" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/46aa709affb9ad3355bd7a810f9662d71025c384", - "reference": "46aa709affb9ad3355bd7a810f9662d71025c384", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/98587d939cb783aa04e828e8fa857edaca24c212", + "reference": "98587d939cb783aa04e828e8fa857edaca24c212", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" + "symfony/console": "<5.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "suggest": { @@ -5165,7 +9833,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.3.3" + "source": "https://github.com/symfony/var-dumper/tree/v6.1.0" }, "funding": [ { @@ -5181,30 +9849,133 @@ "type": "tidelift" } ], - "time": "2021-06-24T08:13:00+00:00" + "time": "2022-05-21T13:34:40+00:00" }, { - "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.3", + "name": "textalk/websocket", + "version": "1.5.8", "source": { "type": "git", - "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5" + "url": "https://github.com/Textalk/websocket-php.git", + "reference": "d05dbaa97500176447ffb1f1800573f23085ab13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/b43b05cf43c1b6d849478965062b6ef73e223bb5", - "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5", + "url": "https://api.github.com/repos/Textalk/websocket-php/zipball/d05dbaa97500176447ffb1f1800573f23085ab13", + "reference": "d05dbaa97500176447ffb1f1800573f23085ab13", + "shasum": "" + }, + "require": { + "php": "^7.2 | ^8.0", + "psr/log": "^1 | ^2 | ^3" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "WebSocket\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Fredrik Liljegren" + }, + { + "name": "Sören Jensen", + "email": "soren@abicart.se" + } + ], + "description": "WebSocket client and server", + "support": { + "issues": "https://github.com/Textalk/websocket-php/issues", + "source": "https://github.com/Textalk/websocket-php/tree/1.5.8" + }, + "time": "2022-04-26T06:28:24+00:00" + }, + { + "name": "tightenco/collect", + "version": "v8.83.12", + "source": { + "type": "git", + "url": "https://github.com/tighten/collect.git", + "reference": "7275c1b5f6bfc539d72bb5984d3b9a89e47e27c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tighten/collect/zipball/7275c1b5f6bfc539d72bb5984d3b9a89e47e27c9", + "reference": "7275c1b5f6bfc539d72bb5984d3b9a89e47e27c9", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0", + "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "nesbot/carbon": "^2.23.0", + "phpunit/phpunit": "^8.3" + }, + "type": "library", + "autoload": { + "files": [ + "src/Collect/Support/helpers.php", + "src/Collect/Support/alias.php" + ], + "psr-4": { + "Tightenco\\Collect\\": "src/Collect" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "Collect - Illuminate Collections as a separate package.", + "keywords": [ + "collection", + "laravel" + ], + "support": { + "issues": "https://github.com/tighten/collect/issues", + "source": "https://github.com/tighten/collect/tree/v8.83.12" + }, + "time": "2022-04-15T20:31:14+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/da444caae6aca7a19c0c140f68c6182e337d5b1c", + "reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "php": "^5.5 || ^7.0 || ^8.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" }, "type": "library", "extra": { @@ -5232,37 +10003,37 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.3" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.4" }, - "time": "2020-07-13T06:12:54+00:00" + "time": "2021-12-08T09:12:39+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.3.0", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56" + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", - "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/264dce589e7ce37a7ba99cb901eed8249fbec92f", + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.1", + "graham-campbell/result-type": "^1.0.2", "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.7.4", - "symfony/polyfill-ctype": "^1.17", - "symfony/polyfill-mbstring": "^1.17", - "symfony/polyfill-php80": "^1.17" + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1" + "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" }, "suggest": { "ext-filter": "Required to use the boolean validator." @@ -5270,7 +10041,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.4-dev" } }, "autoload": { @@ -5285,13 +10056,13 @@ "authors": [ { "name": "Graham Campbell", - "email": "graham@alt-three.com", - "homepage": "https://gjcampbell.co.uk/" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { "name": "Vance Lucas", "email": "vance@vancelucas.com", - "homepage": "https://vancelucas.com/" + "homepage": "https://github.com/vlucas" } ], "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", @@ -5302,7 +10073,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.1" }, "funding": [ { @@ -5314,20 +10085,20 @@ "type": "tidelift" } ], - "time": "2021-01-20T15:23:13+00:00" + "time": "2021-12-12T23:22:04+00:00" }, { "name": "voku/portable-ascii", - "version": "1.5.6", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "80953678b19901e5165c56752d087fc11526017c" + "reference": "b56450eed252f6801410d810c8e1727224ae0743" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/80953678b19901e5165c56752d087fc11526017c", - "reference": "80953678b19901e5165c56752d087fc11526017c", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", + "reference": "b56450eed252f6801410d810c8e1727224ae0743", "shasum": "" }, "require": { @@ -5364,7 +10135,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/1.5.6" + "source": "https://github.com/voku/portable-ascii/tree/2.0.1" }, "funding": [ { @@ -5388,7 +10159,7 @@ "type": "tidelift" } ], - "time": "2020-11-12T00:07:28+00:00" + "time": "2022-03-08T17:03:00+00:00" }, { "name": "webmozart/assert", @@ -5451,30 +10222,119 @@ ], "packages-dev": [ { - "name": "doctrine/instantiator", - "version": "1.4.0", + "name": "brianium/paratest", + "version": "v6.4.4", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "url": "https://github.com/paratestphp/paratest.git", + "reference": "589cdb23728b2a19872945580b95d8aa2c6619da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/589cdb23728b2a19872945580b95d8aa2c6619da", + "reference": "589cdb23728b2a19872945580b95d8aa2c6619da", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "php": "^7.3 || ^8.0", + "phpunit/php-code-coverage": "^9.2.11", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-timer": "^5.0.3", + "phpunit/phpunit": "^9.5.14", + "sebastian/environment": "^5.1.3", + "symfony/console": "^5.4.0 || ^6.0.0", + "symfony/process": "^5.4.0 || ^6.0.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0.0", + "ext-posix": "*", + "infection/infection": "^0.26.5", + "malukenho/mcbumpface": "^1.1.5", + "squizlabs/php_codesniffer": "^3.6.2", + "symfony/filesystem": "^v5.4.0 || ^6.0.0", + "vimeo/psalm": "^4.20.0" + }, + "bin": [ + "bin/paratest" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2022-03-28T07:55:11+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -5501,7 +10361,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, "funding": [ { @@ -5517,149 +10377,7 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" - }, - { - "name": "facade/flare-client-php", - "version": "1.8.1", - "source": { - "type": "git", - "url": "https://github.com/facade/flare-client-php.git", - "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f", - "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f", - "shasum": "" - }, - "require": { - "facade/ignition-contracts": "~1.0", - "illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0", - "php": "^7.1|^8.0", - "symfony/http-foundation": "^3.3|^4.1|^5.0", - "symfony/mime": "^3.4|^4.0|^5.1", - "symfony/var-dumper": "^3.4|^4.0|^5.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "phpunit/phpunit": "^7.5.16", - "spatie/phpunit-snapshot-assertions": "^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Facade\\FlareClient\\": "src" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Send PHP errors to Flare", - "homepage": "https://github.com/facade/flare-client-php", - "keywords": [ - "exception", - "facade", - "flare", - "reporting" - ], - "support": { - "issues": "https://github.com/facade/flare-client-php/issues", - "source": "https://github.com/facade/flare-client-php/tree/1.8.1" - }, - "funding": [ - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2021-05-31T19:23:29+00:00" - }, - { - "name": "facade/ignition", - "version": "2.10.2", - "source": { - "type": "git", - "url": "https://github.com/facade/ignition.git", - "reference": "43688227bbf27c43bc1ad83af224f135b6ef0ff4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/43688227bbf27c43bc1ad83af224f135b6ef0ff4", - "reference": "43688227bbf27c43bc1ad83af224f135b6ef0ff4", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "facade/flare-client-php": "^1.6", - "facade/ignition-contracts": "^1.0.2", - "filp/whoops": "^2.4", - "illuminate/support": "^7.0|^8.0", - "monolog/monolog": "^2.0", - "php": "^7.2.5|^8.0", - "symfony/console": "^5.0", - "symfony/var-dumper": "^5.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "mockery/mockery": "^1.3", - "orchestra/testbench": "^5.0|^6.0", - "psalm/plugin-laravel": "^1.2" - }, - "suggest": { - "laravel/telescope": "^3.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - }, - "laravel": { - "providers": [ - "Facade\\Ignition\\IgnitionServiceProvider" - ], - "aliases": { - "Flare": "Facade\\Ignition\\Facades\\Flare" - } - } - }, - "autoload": { - "psr-4": { - "Facade\\Ignition\\": "src" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A beautiful error page for Laravel applications.", - "homepage": "https://github.com/facade/ignition", - "keywords": [ - "error", - "flare", - "laravel", - "page" - ], - "support": { - "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", - "forum": "https://twitter.com/flareappio", - "issues": "https://github.com/facade/ignition/issues", - "source": "https://github.com/facade/ignition" - }, - "time": "2021-06-11T06:57:25+00:00" + "time": "2022-03-03T08:28:38+00:00" }, { "name": "facade/ignition-contracts", @@ -5716,21 +10434,21 @@ }, { "name": "filp/whoops", - "version": "2.13.0", + "version": "2.14.5", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "2edbc73a4687d9085c8f20f398eebade844e8424" + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/2edbc73a4687d9085c8f20f398eebade844e8424", - "reference": "2edbc73a4687d9085c8f20f398eebade844e8424", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", "shasum": "" }, "require": { "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1" + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { "mockery/mockery": "^0.9 || ^1.0", @@ -5775,7 +10493,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.13.0" + "source": "https://github.com/filp/whoops/tree/2.14.5" }, "funding": [ { @@ -5783,62 +10501,7 @@ "type": "github" } ], - "time": "2021-06-04T12:00:00+00:00" - }, - { - "name": "fzaninotto/faker", - "version": "v1.9.2", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/848d8125239d7dbf8ab25cb7f054f1a630e68c2e", - "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^2.9.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "support": { - "issues": "https://github.com/fzaninotto/Faker/issues", - "source": "https://github.com/fzaninotto/Faker/tree/v1.9.2" - }, - "abandoned": true, - "time": "2020-12-11T09:56:16+00:00" + "time": "2022-01-07T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -5893,16 +10556,16 @@ }, { "name": "mockery/mockery", - "version": "1.4.3", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea" + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea", - "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea", + "url": "https://api.github.com/repos/mockery/mockery/zipball/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", "shasum": "" }, "require": { @@ -5959,43 +10622,44 @@ ], "support": { "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.4.3" + "source": "https://github.com/mockery/mockery/tree/1.5.0" }, - "time": "2021-02-24T09:51:49+00:00" + "time": "2022-01-20T13:18:17+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6011,7 +10675,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { @@ -6019,42 +10683,41 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "nunomaduro/collision", - "version": "v5.5.0", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "b5cb36122f1c142c3c3ee20a0ae778439ef0244b" + "reference": "c379636dc50e829edb3a8bcb944a01aa1aed8f25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b5cb36122f1c142c3c3ee20a0ae778439ef0244b", - "reference": "b5cb36122f1c142c3c3ee20a0ae778439ef0244b", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/c379636dc50e829edb3a8bcb944a01aa1aed8f25", + "reference": "c379636dc50e829edb3a8bcb944a01aa1aed8f25", "shasum": "" }, "require": { - "facade/ignition-contracts": "^1.0", - "filp/whoops": "^2.7.2", - "php": "^7.3 || ^8.0", - "symfony/console": "^5.0" + "facade/ignition-contracts": "^1.0.2", + "filp/whoops": "^2.14.5", + "php": "^8.0.0", + "symfony/console": "^6.0.2" }, "require-dev": { - "brianium/paratest": "^6.1", - "fideloper/proxy": "^4.4.1", - "friendsofphp/php-cs-fixer": "^2.17.3", - "fruitcake/laravel-cors": "^2.0.3", - "laravel/framework": "^9.0", - "nunomaduro/larastan": "^0.6.2", - "nunomaduro/mock-final-classes": "^1.0", - "orchestra/testbench": "^7.0", - "phpstan/phpstan": "^0.12.64", - "phpunit/phpunit": "^9.5.0" + "brianium/paratest": "^6.4.1", + "laravel/framework": "^9.7", + "nunomaduro/larastan": "^1.0.2", + "nunomaduro/mock-final-classes": "^1.1.0", + "orchestra/testbench": "^7.3.0", + "phpunit/phpunit": "^9.5.11" }, "type": "library", "extra": { + "branch-alias": { + "dev-develop": "6.x-dev" + }, "laravel": { "providers": [ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" @@ -6095,7 +10758,7 @@ }, "funding": [ { - "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "url": "https://www.paypal.com/paypalme/enunomaduro", "type": "custom" }, { @@ -6107,20 +10770,118 @@ "type": "patreon" } ], - "time": "2021-06-22T20:47:22+00:00" + "time": "2022-04-05T15:31:38+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.1", + "name": "nunomaduro/larastan", + "version": "v2.1.6", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "url": "https://github.com/nunomaduro/larastan.git", + "reference": "d0d5114e757f516bb2a2679ee03b7102db61d587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/d0d5114e757f516bb2a2679ee03b7102db61d587", + "reference": "d0d5114e757f516bb2a2679ee03b7102db61d587", + "shasum": "" + }, + "require": { + "composer/pcre": "^3.0", + "ext-json": "*", + "illuminate/console": "^9", + "illuminate/container": "^9", + "illuminate/contracts": "^9", + "illuminate/database": "^9", + "illuminate/http": "^9", + "illuminate/pipeline": "^9", + "illuminate/support": "^9", + "mockery/mockery": "^1.4.4", + "php": "^8.0.2", + "phpmyadmin/sql-parser": "^5.5", + "phpstan/phpstan": "^1.7.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.2", + "orchestra/testbench": "^7.0.0", + "phpunit/phpunit": "^9.5.11" + }, + "suggest": { + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "NunoMaduro\\Larastan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" + ], + "support": { + "issues": "https://github.com/nunomaduro/larastan/issues", + "source": "https://github.com/nunomaduro/larastan/tree/v2.1.6" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/canvural", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2022-05-23T20:17:50+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { @@ -6165,22 +10926,22 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2020-06-27T14:33:11+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -6216,9 +10977,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2021-02-23T14:00:09+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -6275,16 +11036,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { @@ -6295,7 +11056,8 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -6325,22 +11087,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { @@ -6348,7 +11110,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -6374,39 +11137,112 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2022-03-15T21:29:03+00:00" }, { - "name": "phpspec/prophecy", - "version": "1.13.0", + "name": "phpmyadmin/sql-parser", + "version": "5.5.0", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "url": "https://github.com/phpmyadmin/sql-parser.git", + "reference": "8ab99cd0007d880f49f5aa1807033dbfa21b1cb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/8ab99cd0007d880f49f5aa1807033dbfa21b1cb5", + "reference": "8ab99cd0007d880f49f5aa1807033dbfa21b1cb5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "conflict": { + "phpmyadmin/motranslator": "<3.0" + }, + "require-dev": { + "phpmyadmin/coding-standard": "^3.0", + "phpmyadmin/motranslator": "^4.0 || ^5.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.11", + "zumba/json-serializer": "^3.0" + }, + "suggest": { + "ext-mbstring": "For best performance", + "phpmyadmin/motranslator": "Translate messages to your favorite locale" + }, + "bin": [ + "bin/highlight-query", + "bin/lint-query", + "bin/tokenize-query" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\SqlParser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "A validating SQL lexer and parser with a focus on MySQL dialect.", + "homepage": "https://github.com/phpmyadmin/sql-parser", + "keywords": [ + "analysis", + "lexer", + "parser", + "sql" + ], + "support": { + "issues": "https://github.com/phpmyadmin/sql-parser/issues", + "source": "https://github.com/phpmyadmin/sql-parser" + }, + "time": "2021-12-09T04:31:52+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -6441,29 +11277,88 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-12-08T12:19:24+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "name": "phpstan/phpstan", + "version": "1.7.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "48c4621b1a6dde32aab2e9a79658b843ffb614f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/48c4621b1a6dde32aab2e9a79658b843ffb614f7", + "reference": "48c4621b1a6dde32aab2e9a79658b843ffb614f7", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.7.3" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-29T11:36:09+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.13.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -6512,7 +11407,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" }, "funding": [ { @@ -6520,20 +11415,20 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2022-03-07T09:28:20+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -6572,7 +11467,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -6580,7 +11475,7 @@ "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -6765,16 +11660,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.6", + "version": "9.5.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb" + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", - "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", "shasum": "" }, "require": { @@ -6786,11 +11681,11 @@ "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", + "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -6804,7 +11699,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", + "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -6825,11 +11720,11 @@ } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6852,11 +11747,11 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { @@ -6864,7 +11759,73 @@ "type": "github" } ], - "time": "2021-06-23T05:14:38+00:00" + "time": "2022-04-01T12:37:26+00:00" + }, + { + "name": "predis/predis", + "version": "v1.1.10", + "source": { + "type": "git", + "url": "https://github.com/predis/predis.git", + "reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/predis/predis/zipball/a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e", + "reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net", + "role": "Creator & Maintainer" + }, + { + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/predis/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v1.1.10" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2022-01-05T17:46:08+00:00" }, { "name": "sebastian/cli-parser", @@ -7232,16 +12193,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { @@ -7283,7 +12244,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" }, "funding": [ { @@ -7291,20 +12252,20 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", "shasum": "" }, "require": { @@ -7353,14 +12314,14 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" }, "funding": [ { @@ -7368,20 +12329,20 @@ "type": "github" } ], - "time": "2020-09-28T05:24:23+00:00" + "time": "2021-11-11T14:18:36+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.3", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -7424,7 +12385,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" }, "funding": [ { @@ -7432,7 +12393,7 @@ "type": "github" } ], - "time": "2021-06-11T13:31:12+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", @@ -7723,28 +12684,28 @@ }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -7767,7 +12728,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" }, "funding": [ { @@ -7775,7 +12736,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2022-03-15T09:54:48+00:00" }, { "name": "sebastian/version", @@ -7831,17 +12792,313 @@ "time": "2020-09-28T06:39:44+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.2.0", + "name": "spatie/backtrace", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "url": "https://github.com/spatie/backtrace.git", + "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", + "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "ext-json": "*", + "phpunit/phpunit": "^9.3", + "symfony/var-dumper": "^5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2021-11-09T10:57:15+00:00" + }, + { + "name": "spatie/flare-client-php", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/flare-client-php.git", + "reference": "86a380f5b1ce839af04a08f1c8f2697184cdf23f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/86a380f5b1ce839af04a08f1c8f2697184cdf23f", + "reference": "86a380f5b1ce839af04a08f1c8f2697184cdf23f", + "shasum": "" + }, + "require": { + "illuminate/pipeline": "^8.0|^9.0", + "php": "^8.0", + "spatie/backtrace": "^1.2", + "symfony/http-foundation": "^5.0|^6.0", + "symfony/mime": "^5.2|^6.0", + "symfony/process": "^5.2|^6.0", + "symfony/var-dumper": "^5.2|^6.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.3.0", + "pestphp/pest": "^1.20", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/phpunit-snapshot-assertions": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\FlareClient\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/spatie/flare-client-php", + "keywords": [ + "exception", + "flare", + "reporting", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/flare-client-php/issues", + "source": "https://github.com/spatie/flare-client-php/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-05-16T12:13:39+00:00" + }, + { + "name": "spatie/ignition", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/ignition.git", + "reference": "997363fbcce809b1e55f571997d49017f9c623d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/ignition/zipball/997363fbcce809b1e55f571997d49017f9c623d9", + "reference": "997363fbcce809b1e55f571997d49017f9c623d9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "monolog/monolog": "^2.0", + "php": "^8.0", + "spatie/flare-client-php": "^1.1", + "symfony/console": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.4", + "pestphp/pest": "^1.20", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\Ignition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for PHP applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/ignition/issues", + "source": "https://github.com/spatie/ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-05-16T13:16:07+00:00" + }, + { + "name": "spatie/laravel-ignition", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ignition.git", + "reference": "51e5daaa7e43c154fe57f1ddfbba862f9fe57646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/51e5daaa7e43c154fe57f1ddfbba862f9fe57646", + "reference": "51e5daaa7e43c154fe57f1ddfbba862f9fe57646", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/support": "^8.77|^9.0", + "monolog/monolog": "^2.3", + "php": "^8.0", + "spatie/flare-client-php": "^1.0.1", + "spatie/ignition": "^1.2.4", + "symfony/console": "^5.0|^6.0", + "symfony/var-dumper": "^5.0|^6.0" + }, + "require-dev": { + "filp/whoops": "^2.14", + "livewire/livewire": "^2.8|dev-develop", + "mockery/mockery": "^1.4", + "nunomaduro/larastan": "^1.0", + "orchestra/testbench": "^6.23|^7.0", + "pestphp/pest": "^1.20", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/laravel-ray": "^1.27" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ], + "aliases": { + "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\LaravelIgnition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/laravel-ignition/issues", + "source": "https://github.com/spatie/laravel-ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-05-05T15:53:24+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -7870,7 +13127,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { @@ -7878,7 +13135,7 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [], @@ -7887,8 +13144,16 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^7.3.0" + "php": "^8.1", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-intl": "*", + "ext-pdo": "*" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "platform-overrides": { + "ext-pcntl": "8.0", + "ext-posix": "8.0" + }, + "plugin-api-version": "2.3.0" } diff --git a/config/api.php b/config/api.php new file mode 100644 index 000000000..7927bf697 --- /dev/null +++ b/config/api.php @@ -0,0 +1,24 @@ + env('API_URL'), + + 'path' => env('API_PATH'), +]; diff --git a/config/app.php b/config/app.php index 8409e00ea..fe766e81f 100644 --- a/config/app.php +++ b/config/app.php @@ -1,5 +1,9 @@ env('APP_URL', 'http://localhost'), - 'asset_url' => env('ASSET_URL', null), + 'asset_url' => env('ASSET_URL'), /* |-------------------------------------------------------------------------- @@ -123,6 +127,73 @@ return [ 'cipher' => 'AES-256-CBC', + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => 'file', + // 'store' => 'redis', + ], + + /* + |-------------------------------------------------------------------------- + | Debug Blacklist + |-------------------------------------------------------------------------- + | + | Obscure sensitive variables when an exception is uncaught for debug environments. + | + */ + + 'debug_blacklist' => [ + '_ENV' => [ + 'APP_KEY', + 'DB_PASSWORD', + 'REDIS_PASSWORD', + 'MAL_BEARER_TOKEN', + 'ELASTIC_PASS', + 'DO_BEARER_TOKEN', + 'IMAGE_ACCESS_KEY_ID', + 'IMAGE_SECRET_ACCESS_KEY', + 'VIDEO_ACCESS_KEY_ID', + 'VIDEO_SECRET_ACCESS_KEY', + 'ALGOLIA_APP_ID', + 'ALGOLIA_SECRET', + 'MAILGUN_SECRET', + 'POSTMARK_TOKEN', + 'DISCORD_BOT_API_TOKEN', + ], + '_SERVER' => [ + 'APP_KEY', + 'DB_PASSWORD', + 'REDIS_PASSWORD', + 'MAL_BEARER_TOKEN', + 'ELASTIC_PASS', + 'DO_BEARER_TOKEN', + 'IMAGE_ACCESS_KEY_ID', + 'IMAGE_SECRET_ACCESS_KEY', + 'VIDEO_ACCESS_KEY_ID', + 'VIDEO_SECRET_ACCESS_KEY', + 'ALGOLIA_APP_ID', + 'ALGOLIA_SECRET', + 'MAILGUN_SECRET', + 'POSTMARK_TOKEN', + 'DISCORD_BOT_API_TOKEN', + ], + '_POST' => [ + 'password', + ], + ], + /* |-------------------------------------------------------------------------- | Autoloaded Service Providers @@ -162,19 +233,18 @@ return [ Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, - /* - * Package Service Providers... - */ - /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, - // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, + App\Providers\FortifyServiceProvider::class, + App\Providers\HorizonServiceProvider::class, + App\Providers\JetstreamServiceProvider::class, + App\Providers\NovaServiceProvider::class, App\Providers\RouteServiceProvider::class, - + App\Providers\TelescopeServiceProvider::class, ], /* @@ -188,45 +258,8 @@ return [ | */ - 'aliases' => [ - - 'App' => Illuminate\Support\Facades\App::class, - 'Arr' => Illuminate\Support\Arr::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, - 'Bus' => Illuminate\Support\Facades\Bus::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Http' => Illuminate\Support\Facades\Http::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'Str' => Illuminate\Support\Str::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, - - ], + 'aliases' => Facade::defaultAliases()->merge([ + // ... + ])->toArray(), ]; diff --git a/config/audit.php b/config/audit.php new file mode 100644 index 000000000..085c06afd --- /dev/null +++ b/config/audit.php @@ -0,0 +1,171 @@ + env('AUDITING_ENABLED', true), + + /* + |-------------------------------------------------------------------------- + | Audit Implementation + |-------------------------------------------------------------------------- + | + | Define which Audit model implementation should be used. + | + */ + + 'implementation' => OwenIt\Auditing\Models\Audit::class, + + /* + |-------------------------------------------------------------------------- + | User Morph prefix & Guards + |-------------------------------------------------------------------------- + | + | Define the morph prefix and authentication guards for the User resolver. + | + */ + + 'user' => [ + 'morph_prefix' => 'user', + 'guards' => [ + 'web', + 'api', + 'sanctum', + ], + 'resolver' => OwenIt\Auditing\Resolvers\UserResolver::class, + ], + + /* + |-------------------------------------------------------------------------- + | Audit Resolvers + |-------------------------------------------------------------------------- + | + | Define the User, IP Address, User Agent and URL resolver implementations. + | + */ + 'resolvers' => [ + 'ip_address' => OwenIt\Auditing\Resolvers\IpAddressResolver::class, + 'user_agent' => OwenIt\Auditing\Resolvers\UserAgentResolver::class, + 'url' => OwenIt\Auditing\Resolvers\UrlResolver::class, + ], + + /* + |-------------------------------------------------------------------------- + | Audit Events + |-------------------------------------------------------------------------- + | + | The Eloquent events that trigger an Audit. + | + */ + + 'events' => [ + 'created', + 'updated', + 'deleted', + 'restored', + ], + + /* + |-------------------------------------------------------------------------- + | Strict Mode + |-------------------------------------------------------------------------- + | + | Enable the strict mode when auditing? + | + */ + + 'strict' => false, + + /* + |-------------------------------------------------------------------------- + | Global exclude + |-------------------------------------------------------------------------- + | + | Have something you always want to exclude by default? - add it here. + | Note that this is overwritten (not merged) with local exclude + | + */ + + 'exclude' => [], + + /* + |-------------------------------------------------------------------------- + | Empty Values + |-------------------------------------------------------------------------- + | + | Should Audit records be stored when the recorded old_values & new_values + | are both empty? + | + | Some events may be empty on purpose. Use allowed_empty_values to exclude + | those from the empty values check. For example when auditing + | model retrieved events which will never have new and old values + | + */ + + 'empty_values' => true, + 'allowed_empty_values' => [ + 'retrieved', + ], + + /* + |-------------------------------------------------------------------------- + | Audit Timestamps + |-------------------------------------------------------------------------- + | + | Should the created_at, updated_at and deleted_at timestamps be audited? + | + */ + + 'timestamps' => false, + + /* + |-------------------------------------------------------------------------- + | Audit Threshold + |-------------------------------------------------------------------------- + | + | Specify a threshold for the amount of Audit records a model can have. + | Zero means no limit. + | + */ + + 'threshold' => 0, + + /* + |-------------------------------------------------------------------------- + | Audit Driver + |-------------------------------------------------------------------------- + | + | The default audit driver used to keep track of changes. + | + */ + + 'driver' => 'database', + + /* + |-------------------------------------------------------------------------- + | Audit Driver Configurations + |-------------------------------------------------------------------------- + | + | Available audit drivers and respective configurations. + | + */ + + 'drivers' => [ + 'database' => [ + 'table' => 'audits', + 'connection' => null, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Audit Console + |-------------------------------------------------------------------------- + | + | Whether console events should be audited (eg. php artisan db:seed). + | + */ + + 'console' => false, +]; diff --git a/config/auth.php b/config/auth.php index ba1a4d8cb..96a6d63b1 100644 --- a/config/auth.php +++ b/config/auth.php @@ -1,5 +1,7 @@ [ - 'driver' => 'token', + 'driver' => 'sanctum', 'provider' => 'users', 'hash' => false, ], @@ -68,7 +70,7 @@ return [ 'providers' => [ 'users' => [ 'driver' => 'eloquent', - 'model' => App\Models\User::class, + 'model' => App\Models\Auth\User::class, ], // 'users' => [ @@ -86,7 +88,7 @@ return [ | than one user table or model in the application and you want to have | separate password reset settings based on the specific user types. | - | The expire time is the number of minutes that the reset token should be + | The expire time is the number of minutes that each reset token will be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | diff --git a/config/broadcasting.php b/config/broadcasting.php index 3bba1103e..c3b2073ac 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -1,5 +1,7 @@ env('PUSHER_APP_CLUSTER'), 'useTLS' => true, ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), ], 'redis' => [ diff --git a/config/cache.php b/config/cache.php index 4f41fdf96..14b15c4ed 100644 --- a/config/cache.php +++ b/config/cache.php @@ -1,5 +1,7 @@ env('CACHE_DRIVER', 'file'), @@ -29,6 +28,9 @@ return [ | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | + | Supported drivers: "apc", "array", "database", "file", + | "memcached", "redis", "dynamodb", "octane", "null" + | */ 'stores' => [ @@ -46,6 +48,7 @@ return [ 'driver' => 'database', 'table' => 'cache', 'connection' => null, + 'lock_connection' => null, ], 'file' => [ @@ -75,6 +78,7 @@ return [ 'redis' => [ 'driver' => 'redis', 'connection' => 'cache', + 'lock_connection' => 'default', ], 'dynamodb' => [ @@ -86,6 +90,10 @@ return [ 'endpoint' => env('DYNAMODB_ENDPOINT'), ], + 'octane' => [ + 'driver' => 'octane', + ], + ], /* @@ -93,12 +101,12 @@ return [ | Cache Key Prefix |-------------------------------------------------------------------------- | - | When utilizing a RAM based store such as APC or Memcached, there might - | be other applications utilizing the same cache. So, we'll specify a - | value to get prefixed to all our keys so we can avoid collisions. + | When utilizing the APC, database, memcached, Redis, or DynamoDB cache + | stores there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. | */ - 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), ]; diff --git a/config/cors.php b/config/cors.php index 5c9de8972..ec8d2d85e 100644 --- a/config/cors.php +++ b/config/cors.php @@ -1,5 +1,7 @@ ['api/*'], + 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], @@ -25,9 +27,9 @@ return [ 'allowed_headers' => ['*'], - 'exposed_headers' => false, + 'exposed_headers' => [], - 'max_age' => false, + 'max_age' => 0, 'supports_credentials' => false, diff --git a/config/database.php b/config/database.php index b42d9b30a..52095a12f 100644 --- a/config/database.php +++ b/config/database.php @@ -1,5 +1,7 @@ [ 'driver' => 'mysql', + 'read' => [ + 'host' => [ + env('DB_HOST_READ', '127.0.0.1'), + ], + ], + 'write' => [ + 'host' => [ + env('DB_HOST_WRITE', '127.0.0.1'), + ], + ], + 'sticky' => env('DB_STICKY', false), 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), @@ -65,6 +78,17 @@ return [ 'pgsql' => [ 'driver' => 'pgsql', + 'read' => [ + 'host' => [ + env('DB_HOST_READ', '127.0.0.1'), + ], + ], + 'write' => [ + 'host' => [ + env('DB_HOST_WRITE', '127.0.0.1'), + ], + ], + 'sticky' => env('DB_STICKY', false), 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), @@ -74,12 +98,23 @@ return [ 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, - 'schema' => 'public', + 'search_path' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', + 'read' => [ + 'host' => [ + env('DB_HOST_READ', '127.0.0.1'), + ], + ], + 'write' => [ + 'host' => [ + env('DB_HOST_WRITE', '127.0.0.1'), + ], + ], + 'sticky' => env('DB_STICKY', false), 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), @@ -89,6 +124,8 @@ return [ 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), ], ], @@ -124,12 +161,14 @@ return [ 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'scheme' => env('REDIS_SCHEME', 'tcp'), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), - 'password' => env('REDIS_PASSWORD', null), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), ], @@ -137,7 +176,8 @@ return [ 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), - 'password' => env('REDIS_PASSWORD', null), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), ], diff --git a/config/elastic.client.php b/config/elastic.client.php new file mode 100644 index 000000000..4910a3251 --- /dev/null +++ b/config/elastic.client.php @@ -0,0 +1,15 @@ + [ + [ + 'host' => env('ELASTIC_HOST', 'localhost'), + 'port' => env('ELASTIC_PORT', '9200'), + 'scheme' => env('ELASTIC_SCHEME', 'http'), + 'user' => env('ELASTIC_USER'), + 'pass' => env('ELASTIC_PASS'), + ], + ], +]; diff --git a/config/elastic.migrations.php b/config/elastic.migrations.php new file mode 100644 index 000000000..747406574 --- /dev/null +++ b/config/elastic.migrations.php @@ -0,0 +1,9 @@ + env('ELASTIC_MIGRATIONS_TABLE', 'elastic_migrations'), + 'storage_directory' => env('ELASTIC_MIGRATIONS_DIRECTORY', base_path('elastic/migrations')), + 'index_name_prefix' => env('ELASTIC_MIGRATIONS_INDEX_NAME_PREFIX', env('SCOUT_PREFIX', '')), +]; diff --git a/config/elastic.scout_driver.php b/config/elastic.scout_driver.php new file mode 100644 index 000000000..0dc18a901 --- /dev/null +++ b/config/elastic.scout_driver.php @@ -0,0 +1,7 @@ + env('ELASTIC_SCOUT_DRIVER_REFRESH_DOCUMENTS', false), +]; diff --git a/config/eloquent-viewable.php b/config/eloquent-viewable.php new file mode 100644 index 000000000..2509d83cd --- /dev/null +++ b/config/eloquent-viewable.php @@ -0,0 +1,109 @@ + [ + + /* + * Here you can configure the default `View` model. + */ + 'view' => [ + + 'table_name' => 'views', + 'connection' => env('DB_CONNECTION', 'mysql'), + + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Configuration + |-------------------------------------------------------------------------- + */ + 'cache' => [ + + /* + * Everthing will be stored under the following key. + */ + 'key' => 'cyrildewit.eloquent-viewable.cache', + + /* + * Here you may define the cache store that should be used. + */ + 'store' => env('CACHE_DRIVER', 'file'), + + ], + + /* + |-------------------------------------------------------------------------- + | Cooldown Configuration + |-------------------------------------------------------------------------- + */ + 'cooldown' => [ + + /* + * Everthing will be stored under the following key in the session. + */ + 'key' => 'cyrildewit.eloquent-viewable.cooldowns', + + ], + + /* + |-------------------------------------------------------------------------- + | Ignore Bots + |-------------------------------------------------------------------------- + | + | If you want to ignore bots, you can specify that here. The default + | service that determines if a visitor is a crawler is a package + | by JayBizzle called CrawlerDetect. + | + */ + 'ignore_bots' => true, + + /* + |-------------------------------------------------------------------------- + | Do Not Track Header + |-------------------------------------------------------------------------- + | + | If you want to honor the DNT header, you can specify that here. We won't + | record views from visitors with the Do Not Track header. + | + */ + 'honor_dnt' => false, + + /* + |-------------------------------------------------------------------------- + | Cookies + |-------------------------------------------------------------------------- + | + | This package binds visitors to views using a cookie. If you want to + | give this cookie a custom name, you can specify that here. + | + */ + + 'visitor_cookie_key' => 'eloquent_viewable', + + /* + |-------------------------------------------------------------------------- + | Ignore IP Addresses + |-------------------------------------------------------------------------- + | + | Ignore views of the following IP addresses. + | + */ + + 'ignored_ip_addresses' => [ + + // '127.0.0.1', + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php index 72804ad3a..ebfa5383c 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -1,5 +1,7 @@ env('FILESYSTEM_DRIVER', 'local'), - - /* - |-------------------------------------------------------------------------- - | Default Cloud Filesystem Disk - |-------------------------------------------------------------------------- - | - | Many applications store files both locally and in the cloud. For this - | reason, you may specify a default "cloud" driver here. This driver - | will be bound as the Cloud disk implementation in the container. - | - */ - - 'cloud' => env('FILESYSTEM_CLOUD', 's3'), + 'default' => env('FILESYSTEM_DISK', 'local'), /* |-------------------------------------------------------------------------- @@ -35,7 +24,7 @@ return [ | | Here you may configure as many filesystem "disks" as you wish, and you | may even configure multiple disks of the same driver. Defaults have - | been setup for each driver as an example of the required options. + | been set up for each driver as an example of the required values. | | Supported Drivers: "local", "ftp", "sftp", "s3" | @@ -46,6 +35,7 @@ return [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), + 'throw' => false, ], 'public' => [ @@ -53,26 +43,41 @@ return [ 'root' => storage_path('app/public'), 'url' => env('APP_URL').'/storage', 'visibility' => 'public', + 'throw' => false, ], - 'grill' => [ - 'driver' => 'local', - 'root' => public_path('img/grill'), - 'url' => env('APP_URL').'/img/grill', - 'visibility' => 'public', - ], - - 'spaces' => [ + 'images' => [ 'driver' => 's3', - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'region' => env('AWS_DEFAULT_REGION'), - 'bucket' => env('AWS_BUCKET'), - 'endpoint' => env('AWS_ENDPOINT'), - 'stream_reads' => env('AWS_STREAM_READS'), - 'disable_asserts' => true, + 'key' => env('IMAGE_ACCESS_KEY_ID'), + 'secret' => env('IMAGE_SECRET_ACCESS_KEY'), + 'region' => env('IMAGE_DEFAULT_REGION'), + 'bucket' => env('IMAGE_BUCKET'), + 'endpoint' => env('IMAGE_ENDPOINT'), + 'stream_reads' => env('IMAGE_STREAM_READS'), + 'disable_asserts' => env('IMAGE_DISABLE_ASSERTS'), + 'visibility' => env('IMAGE_VISIBILITY'), + 'url' => env('IMAGE_URL'), + 'throw' => false, ], + 'videos' => [ + 'driver' => 's3', + 'key' => env('VIDEO_ACCESS_KEY_ID'), + 'secret' => env('VIDEO_SECRET_ACCESS_KEY'), + 'region' => env('VIDEO_DEFAULT_REGION'), + 'bucket' => env('VIDEO_BUCKET'), + 'endpoint' => env('VIDEO_ENDPOINT'), + 'stream_reads' => env('VIDEO_STREAM_READS'), + 'disable_asserts' => env('VIDEO_DISABLE_ASSERTS'), + 'visibility' => env('VIDEO_VISIBILITY'), + 'throw' => false, + ], + + 'db-dumps' => [ + 'driver' => 'local', + 'root' => storage_path('db-dumps'), + 'throw' => false, + ], ], /* diff --git a/config/flags.php b/config/flags.php new file mode 100644 index 000000000..6d94f22c8 --- /dev/null +++ b/config/flags.php @@ -0,0 +1,46 @@ + (bool) env('ALLOW_VIDEO_STREAMS', false), + + /* + |-------------------------------------------------------------------------- + | Allow Discord Notifications + |-------------------------------------------------------------------------- + | + | When discord notifications are allowed, event listeners shall send discord + | notifications to the configured discord channel through the configured bot. + | If discord notifications are not allowed, event listeners shall not send + | discord notifications. + | + */ + + 'allow_discord_notifications' => (bool) env('ALLOW_DISCORD_NOTIFICATIONS', false), + + /* + |-------------------------------------------------------------------------- + | Allow View Recording + |-------------------------------------------------------------------------- + | + | When set to true, a view will be created for the viewable model in the show + | action of the resource controller. When set to false, a view will not be + | recorded. + | + */ + + 'allow_view_recording' => (bool) env('ALLOW_VIEW_RECORDING', false), +]; diff --git a/config/fortify.php b/config/fortify.php new file mode 100644 index 000000000..7502779ec --- /dev/null +++ b/config/fortify.php @@ -0,0 +1,150 @@ + 'web', + + /* + |-------------------------------------------------------------------------- + | Fortify Password Broker + |-------------------------------------------------------------------------- + | + | Here you may specify which password broker Fortify can use when a user + | is resetting their password. This configured value should match one + | of your password brokers setup in your "auth" configuration file. + | + */ + + 'passwords' => 'users', + + /* + |-------------------------------------------------------------------------- + | Username / Email + |-------------------------------------------------------------------------- + | + | This value defines which model attribute should be considered as your + | application's "username" field. Typically, this might be the email + | address of the users but you are free to change this value here. + | + | Out of the box, Fortify expects forgot password and reset password + | requests to have a field named 'email'. If the application uses + | another name for the field you may define it below as needed. + | + */ + + 'username' => 'email', + + 'email' => 'email', + + /* + |-------------------------------------------------------------------------- + | Home Path + |-------------------------------------------------------------------------- + | + | Here you may configure the path where users will get redirected during + | authentication or password reset when the operations are successful + | and the user is authenticated. You are free to change this value. + | + */ + + 'home' => RouteServiceProvider::HOME, + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Prefix / Subdomain + |-------------------------------------------------------------------------- + | + | Here you may specify which prefix Fortify will assign to all the routes + | that it registers with the application. If necessary, you may change + | subdomain under which all of the Fortify routes will be available. + | + */ + + 'prefix' => '', + + 'domain' => null, + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Middleware + |-------------------------------------------------------------------------- + | + | Here you may specify which middleware Fortify will assign to the routes + | that it registers with the application. If necessary, you may change + | these middleware but typically this provided default is preferred. + | + */ + + 'middleware' => ['web'], + + 'auth_middleware' => 'auth', + + /* + |-------------------------------------------------------------------------- + | Rate Limiting + |-------------------------------------------------------------------------- + | + | By default, Fortify will throttle logins to five requests per minute for + | every email and IP address combination. However, if you would like to + | specify a custom rate limiter to call then you may specify it here. + | + */ + + 'limiters' => [ + 'login' => 'login', + 'two-factor' => 'two-factor', + ], + + /* + |-------------------------------------------------------------------------- + | Register View Routes + |-------------------------------------------------------------------------- + | + | Here you may specify if the routes returning views should be disabled as + | you may not need them when building your own application. This may be + | especially true if you're writing a custom single-page application. + | + */ + + 'views' => true, + + /* + |-------------------------------------------------------------------------- + | Features + |-------------------------------------------------------------------------- + | + | Some of the Fortify features are optional. You may disable the features + | by removing them from this array. You're free to only remove some of + | these features or you can even remove all of these if you need to. + | + */ + + 'features' => [ + Features::resetPasswords(), + Features::emailVerification(), + Features::updateProfileInformation(), + Features::updatePasswords(), + Features::twoFactorAuthentication([ + 'confirm' => true, + 'confirmPassword' => true, + 'window' => 1, + ]), + ], + +]; diff --git a/config/hashing.php b/config/hashing.php index 842577087..41cba61a3 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -1,5 +1,7 @@ [ - 'memory' => 1024, - 'threads' => 2, - 'time' => 2, + 'memory' => 65536, + 'threads' => 1, + 'time' => 4, ], ]; diff --git a/config/horizon.php b/config/horizon.php new file mode 100644 index 000000000..f4c87ce71 --- /dev/null +++ b/config/horizon.php @@ -0,0 +1,203 @@ + env('HORIZON_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Horizon Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Horizon will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('HORIZON_PATH', 'horizon'), + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Connection + |-------------------------------------------------------------------------- + | + | This is the name of the Redis connection where Horizon will store the + | meta information required for it to function. It includes the list + | of supervisors, failed jobs, job metrics, and other information. + | + */ + + 'use' => 'default', + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Prefix + |-------------------------------------------------------------------------- + | + | This prefix will be used when storing all Horizon data in Redis. You + | may modify the prefix when you are running multiple installations + | of Horizon on the same server so that they don't have problems. + | + */ + + 'prefix' => env( + 'HORIZON_PREFIX', + Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:' + ), + + /* + |-------------------------------------------------------------------------- + | Horizon Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will get attached onto each Horizon route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Queue Wait Time Thresholds + |-------------------------------------------------------------------------- + | + | This option allows you to configure when the LongWaitDetected event + | will be fired. Every connection / queue combination may have its + | own, unique threshold (in seconds) before this event is fired. + | + */ + + 'waits' => [ + 'redis:default' => 60, + ], + + /* + |-------------------------------------------------------------------------- + | Job Trimming Times + |-------------------------------------------------------------------------- + | + | Here you can configure for how long (in minutes) you desire Horizon to + | persist the recent and failed jobs. Typically, recent jobs are kept + | for one hour while all failed jobs are stored for an entire week. + | + */ + + 'trim' => [ + 'recent' => 60, + 'pending' => 60, + 'completed' => 60, + 'recent_failed' => 10080, + 'failed' => 10080, + 'monitored' => 10080, + ], + + /* + |-------------------------------------------------------------------------- + | Metrics + |-------------------------------------------------------------------------- + | + | Here you can configure how many snapshots should be kept to display in + | the metrics graph. This will get used in combination with Horizon's + | `horizon:snapshot` schedule to define how long to retain metrics. + | + */ + + 'metrics' => [ + 'trim_snapshots' => [ + 'job' => 24, + 'queue' => 24, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Fast Termination + |-------------------------------------------------------------------------- + | + | When this option is enabled, Horizon's "terminate" command will not + | wait on all of the workers to terminate unless the --wait option + | is provided. Fast termination can shorten deployment delay by + | allowing a new instance of Horizon to start while the last + | instance will continue to terminate each of its workers. + | + */ + + 'fast_termination' => false, + + /* + |-------------------------------------------------------------------------- + | Memory Limit (MB) + |-------------------------------------------------------------------------- + | + | This value describes the maximum amount of memory the Horizon master + | supervisor may consume before it is terminated and restarted. For + | configuring these limits on your workers, see the next section. + | + */ + + 'memory_limit' => 64, + + /* + |-------------------------------------------------------------------------- + | Queue Worker Configuration + |-------------------------------------------------------------------------- + | + | Here you may define the queue worker settings used by your application + | in all environments. These supervisors and settings handle all your + | queued jobs and will be provisioned by Horizon during deployment. + | + */ + + 'defaults' => [ + 'supervisor-1' => [ + 'connection' => 'redis', + 'queue' => ['default'], + 'balance' => 'auto', + 'maxProcesses' => 1, + 'memory' => 128, + 'tries' => 1, + 'nice' => 0, + ], + ], + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + ], + ], + 'staging' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + ], + ], + 'local' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, + ], + ], + ], + +]; diff --git a/config/jetstream.php b/config/jetstream.php new file mode 100644 index 000000000..286239872 --- /dev/null +++ b/config/jetstream.php @@ -0,0 +1,67 @@ + 'livewire', + + /* + |-------------------------------------------------------------------------- + | Jetstream Route Middleware + |-------------------------------------------------------------------------- + | + | Here you may specify which middleware Jetstream will assign to the routes + | that it registers with the application. When necessary, you may modify + | these middleware; however, this default value is usually sufficient. + | + */ + + 'middleware' => ['web'], + + 'auth_session' => AuthenticateSession::class, + + /* + |-------------------------------------------------------------------------- + | Features + |-------------------------------------------------------------------------- + | + | Some of Jetstream's features are optional. You may disable the features + | by removing them from this array. You're free to only remove some of + | these features or you can even remove all of these if you need to. + | + */ + + 'features' => [ + Features::termsAndPrivacyPolicy(), + Features::api(), + Features::accountDeletion(), + ], + + /* + |-------------------------------------------------------------------------- + | Profile Photo Disk + |-------------------------------------------------------------------------- + | + | This configuration value determines the default disk that will be used + | when storing profile photos for your application's users. Typically + | this will be the "public" disk but you may adjust this if needed. + | + */ + + 'profile_photo_disk' => 'public', +]; diff --git a/config/logging.php b/config/logging.php index 088c204e2..244380965 100644 --- a/config/logging.php +++ b/config/logging.php @@ -1,5 +1,7 @@ env('LOG_CHANNEL', 'stack'), + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => false, + ], + /* |-------------------------------------------------------------------------- | Log Channels @@ -44,14 +62,14 @@ return [ 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', - 'days' => 14, + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 3, ], 'slack' => [ @@ -59,21 +77,23 @@ return [ 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', - 'level' => 'critical', + 'level' => env('LOG_LEVEL', 'critical'), ], 'papertrail' => [ 'driver' => 'monolog', - 'level' => 'debug', - 'handler' => SyslogUdpHandler::class, + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), ], ], 'stderr' => [ 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ @@ -83,12 +103,12 @@ return [ 'syslog' => [ 'driver' => 'syslog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'errorlog' => [ 'driver' => 'errorlog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'null' => [ diff --git a/config/mail.php b/config/mail.php index cfef410fd..1c7e4c766 100644 --- a/config/mail.php +++ b/config/mail.php @@ -1,5 +1,7 @@ env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN'), ], 'ses' => [ @@ -57,7 +61,7 @@ return [ 'sendmail' => [ 'transport' => 'sendmail', - 'path' => '/usr/sbin/sendmail -bs', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), ], 'log' => [ @@ -68,6 +72,14 @@ return [ 'array' => [ 'transport' => 'array', ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + ], ], /* diff --git a/config/nova.php b/config/nova.php new file mode 100644 index 000000000..6573ceb1d --- /dev/null +++ b/config/nova.php @@ -0,0 +1,205 @@ + env('NOVA_LICENSE_KEY'), + + /* + |-------------------------------------------------------------------------- + | Nova App Name + |-------------------------------------------------------------------------- + | + | This value is the name of your application. This value is used when the + | framework needs to display the name of the application within the UI + | or in other locations. Of course, you're free to change the value. + | + */ + + 'name' => env('NOVA_APP_NAME', env('APP_NAME')), + + /* + |-------------------------------------------------------------------------- + | Nova Domain Name + |-------------------------------------------------------------------------- + | + | This value is the "domain name" associated with your application. This + | can be used to prevent Nova's internal routes from being registered + | on subdomains which do not need access to your admin application. + | + */ + + 'domain' => env('NOVA_DOMAIN_NAME'), + + 'routes' => [ + 'login' => env('APP_URL').'/login', + 'logout' => env('APP_URL').'/logout', + 'forgot_password' => env('APP_URL').'/forgot-password', + 'reset_password' => env('APP_URL').'/reset-password', + ], + + /* + |-------------------------------------------------------------------------- + | Nova Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Nova will be accessible from. Feel free to + | change this path to anything you like. Note that this URI will not + | affect Nova's internal API routes which aren't exposed to users. + | + */ + + 'path' => env('NOVA_PATH'), + + /* + |-------------------------------------------------------------------------- + | Nova Authentication Guard + |-------------------------------------------------------------------------- + | + | This configuration option defines the authentication guard that will + | be used to protect your Nova routes. This option should match one + | of the authentication guards defined in the "auth" config file. + | + */ + + 'guard' => env('NOVA_GUARD'), + + /* + |-------------------------------------------------------------------------- + | Nova Password Reset Broker + |-------------------------------------------------------------------------- + | + | This configuration option defines the password broker that will be + | used when passwords are reset. This option should mirror one of + | the password reset options defined in the "auth" config file. + | + */ + + 'passwords' => env('NOVA_PASSWORDS'), + + /* + |-------------------------------------------------------------------------- + | Nova Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Nova route, giving you the + | chance to add your own middleware to this stack or override any of + | the existing middleware. Or, you can just stick with this stack. + | + */ + + 'middleware' => [ + 'web', + HandleInertiaRequests::class, + DispatchServingNovaEvent::class, + BootTools::class, + ], + + 'api_middleware' => [ + 'nova', + Authenticate::class, + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Nova Pagination Type + |-------------------------------------------------------------------------- + | + | This option defines the visual style used in Nova's resource pagination + | views. You may select between "simple", "load-more", and "links" for + | your applications. Feel free to adjust this option to your choice. + | + */ + + 'pagination' => 'simple', + + /* + |-------------------------------------------------------------------------- + | Nova Storage Disk + |-------------------------------------------------------------------------- + | + | This configuration option allows you to define the default disk that + | will be used to store files using the Image, File, and other file + | related field types. You're welcome to use any configured disk. + | + */ + + 'storage_disk' => env('NOVA_STORAGE_DISK', 'public'), + + /* + |-------------------------------------------------------------------------- + | Nova Currency + |-------------------------------------------------------------------------- + | + | This configuration option allows you to define the default currency + | used by the Currency field within Nova. You may change this to a + | valid ISO 4217 currency code to suit your application's needs. + | + */ + + 'currency' => 'USD', + + /* + |-------------------------------------------------------------------------- + | Branding + |-------------------------------------------------------------------------- + | + | These configuration values allow you to customize the branding of the + | Nova interface, including the primary color and the logo that will + | be displayed within the Nova interface. This logo value must be + | the absolute path to an SVG logo within the local filesystem. + | + */ + + 'brand' => [ + 'logo' => realpath(__DIR__.'/../public/img/logo.svg'), + + 'colors' => [ + '100' => '209, 251, 241', + '200' => '165, 246, 228', + '300' => '117, 234, 212', + '400' => '82, 212, 191', + '500' => '64, 184, 166', + '600' => '49, 148, 136', + '700' => '40, 118, 110', + '800' => '33, 94, 89', + '900' => '30, 78, 74', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Nova Action Resource Class + |-------------------------------------------------------------------------- + | + | This configuration option allows you to specify a custom resource class + | to use for action log entries instead of the default that ships with + | Nova, thus allowing for the addition of additional UI form fields. + | + */ + + 'actions' => [ + 'resource' => ActionResource::class, + ], + +]; diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 000000000..76ca468e7 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,166 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => Role::TABLE, + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => Permission::TABLE, + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false, if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true the package implements teams using the 'team_foreign_key'. If you want + * the migrations to register the 'team_foreign_key', you must set this to true + * before doing the migration. If you already did the migration then you must make a new + * migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and + * 'model_has_permissions'(view the latest version of package's migration file) + */ + + 'teams' => false, + + /* + * When set to true, the required permission names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + */ + + 'enable_wildcard_permission' => false, + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/config/queue.php b/config/queue.php index 122229666..485dd985a 100644 --- a/config/queue.php +++ b/config/queue.php @@ -1,5 +1,7 @@ 'jobs', 'queue' => 'default', 'retry_after' => 90, + 'after_commit' => false, ], 'beanstalkd' => [ @@ -47,6 +50,7 @@ return [ 'queue' => 'default', 'retry_after' => 90, 'block_for' => 0, + 'after_commit' => false, ], 'sqs' => [ @@ -54,9 +58,10 @@ return [ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), - 'queue' => env('SQS_QUEUE', 'your-queue-name'), + 'queue' => env('SQS_QUEUE', 'default'), 'suffix' => env('SQS_SUFFIX'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, ], 'redis' => [ @@ -65,6 +70,7 @@ return [ 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 90, 'block_for' => null, + 'after_commit' => false, ], ], diff --git a/config/route-discovery.php b/config/route-discovery.php new file mode 100644 index 000000000..13cd81186 --- /dev/null +++ b/config/route-discovery.php @@ -0,0 +1,32 @@ + [ + // app_path('Http/Controllers'), + ], + + /* + * Routes will be registered for all views found in these directories. + * The key of an item will be used as the prefix of the uri. + */ + 'discover_views_in_directory' => [ + // 'docs' => resource_path('views/docs'), + ], + + /* + * After having discovered all controllers, these classes will manipulate the routes + * before registering them to Laravel. + * + * In most cases, you shouldn't change these. + */ + 'pending_route_transformers' => [ + ...Spatie\RouteDiscovery\Config::defaultRouteTransformers(), + // + ], +]; diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 000000000..1f0e75f40 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,70 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort() + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => CarbonInterval::year()->totalMinutes, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, + 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, + ], + +]; diff --git a/config/scout.php b/config/scout.php new file mode 100644 index 000000000..788773866 --- /dev/null +++ b/config/scout.php @@ -0,0 +1,121 @@ + env('SCOUT_DRIVER', 'algolia'), + + /* + |-------------------------------------------------------------------------- + | Index Prefix + |-------------------------------------------------------------------------- + | + | Here you may specify a prefix that will be applied to all search index + | names used by Scout. This prefix may be useful if you have multiple + | "tenants" or applications sharing the same search infrastructure. + | + */ + + 'prefix' => env('SCOUT_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Queue Data Syncing + |-------------------------------------------------------------------------- + | + | This option allows you to control if the operations that sync your data + | with your search engines are queued. When this is set to "true" then + | all automatic data syncing will get queued for better performance. + | + */ + + 'queue' => env('SCOUT_QUEUE', false), + + /* + |-------------------------------------------------------------------------- + | Database Transactions + |-------------------------------------------------------------------------- + | + | This configuration option determines if your data will only be synced + | with your search indexes after every open database transaction has + | been committed, thus preventing any discarded data from syncing. + | + */ + + 'after_commit' => false, + + /* + |-------------------------------------------------------------------------- + | Chunk Sizes + |-------------------------------------------------------------------------- + | + | These options allow you to control the maximum chunk size when you are + | mass importing data into the search engine. This allows you to fine + | tune each of these chunk sizes based on the power of the servers. + | + */ + + 'chunk' => [ + 'searchable' => 500, + 'unsearchable' => 500, + ], + + /* + |-------------------------------------------------------------------------- + | Soft Deletes + |-------------------------------------------------------------------------- + | + | This option allows to control whether to keep soft deleted records in + | the search indexes. Maintaining soft deleted records can be useful + | if your application still needs to search for the records later. + | + */ + + 'soft_delete' => false, + + /* + |-------------------------------------------------------------------------- + | Identify User + |-------------------------------------------------------------------------- + | + | This option allows you to control whether to notify the search engine + | of the user performing the search. This is sometimes useful if the + | engine supports any analytics based on this application's users. + | + | Supported engines: "algolia" + | + */ + + 'identify' => env('SCOUT_IDENTIFY', false), + + /* + |-------------------------------------------------------------------------- + | Algolia Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Algolia settings. Algolia is a cloud hosted + | search engine which works great with Scout out of the box. Just plug + | in your application ID and admin API key to get started searching. + | + */ + + 'algolia' => [ + 'id' => env('ALGOLIA_APP_ID', ''), + 'secret' => env('ALGOLIA_SECRET', ''), + ], + +]; diff --git a/config/secure-headers.php b/config/secure-headers.php new file mode 100644 index 000000000..c1d6b6a50 --- /dev/null +++ b/config/secure-headers.php @@ -0,0 +1,663 @@ + '', + + /* + * X-Content-Type-Options + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + * + * Available Value: 'nosniff' + */ + + 'x-content-type-options' => 'nosniff', + + /* + * X-Download-Options + * + * Reference: https://msdn.microsoft.com/en-us/library/jj542450(v=vs.85).aspx + * + * Available Value: 'noopen' + */ + + 'x-download-options' => 'noopen', + + /* + * X-Frame-Options + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + * + * Available Value: 'deny', 'sameorigin', 'allow-from ' + */ + + 'x-frame-options' => 'sameorigin', + + /* + * X-Permitted-Cross-Domain-Policies + * + * Reference: https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html + * + * Available Value: 'all', 'none', 'master-only', 'by-content-type', 'by-ftp-filename' + */ + + 'x-permitted-cross-domain-policies' => 'none', + + /* + * X-Powered-By + * + * Note: it will not add to response header if the value is empty string. + */ + + 'x-powered-by' => '', + + /* + * X-XSS-Protection + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection + * + * Available Value: '1', '0', '1; mode=block' + */ + + 'x-xss-protection' => '1; mode=block', + + /* + * Referrer-Policy + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + * + * Available Value: 'no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', + * 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url' + */ + + 'referrer-policy' => 'no-referrer', + + /* + * Clear-Site-Data + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data + */ + + 'clear-site-data' => [ + 'enable' => (bool) env('ENABLE_CLEAR_SITE_DATA', false), + + 'all' => false, + + 'cache' => true, + + 'cookies' => true, + + 'storage' => true, + + 'executionContexts' => true, + ], + + /* + * HTTP Strict Transport Security + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security + * + * Please ensure your website had set up ssl/tls before enable hsts. + */ + + 'hsts' => [ + 'enable' => (bool) env('ENABLE_HSTS', false), + + 'max-age' => 31536000, + + 'include-sub-domains' => false, + + 'preload' => false, + ], + + /* + * Expect-CT + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT + */ + + 'expect-ct' => [ + 'enable' => (bool) env('ENABLE_EXPECT_CT', false), + + 'max-age' => 2147483648, + + 'enforce' => false, + + // report uri must be absolute-URI + 'report-uri' => null, + ], + + /* + * Permissions Policy + * + * Reference: https://w3c.github.io/webappsec-permissions-policy/ + */ + + 'permissions-policy' => [ + 'enable' => (bool) env('ENABLE_PERMISSIONS_POLICY', false), + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/accelerometer + 'accelerometer' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/ambient-light-sensor + 'ambient-light-sensor' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/autoplay + 'autoplay' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/battery + 'battery' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/camera + 'camera' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://www.chromestatus.com/feature/5690888397258752 + 'cross-origin-isolated' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/display-capture + 'display-capture' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/document-domain + 'document-domain' => [ + 'none' => false, + + '*' => true, + + 'self' => false, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/encrypted-media + 'encrypted-media' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://wicg.github.io/page-lifecycle/#execution-while-not-rendered + 'execution-while-not-rendered' => [ + 'none' => false, + + '*' => true, + + 'self' => false, + + 'origins' => [], + ], + + // https://wicg.github.io/page-lifecycle/#execution-while-out-of-viewport + 'execution-while-out-of-viewport' => [ + 'none' => false, + + '*' => true, + + 'self' => false, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/fullscreen + 'fullscreen' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/geolocation + 'geolocation' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/gyroscope + 'gyroscope' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/magnetometer + 'magnetometer' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/microphone + 'microphone' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/midi + 'midi' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://drafts.csswg.org/css-nav-1/ + 'navigation-override' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/payment + 'payment' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/picture-in-picture + 'picture-in-picture' => [ + 'none' => false, + + '*' => true, + + 'self' => false, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/publickey-credentials-get + 'publickey-credentials-get' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/screen-wake-lock + 'screen-wake-lock' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/sync-xhr + 'sync-xhr' => [ + 'none' => false, + + '*' => true, + + 'self' => false, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/usb + 'usb' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/web-share + 'web-share' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy/xr-spatial-tracking + 'xr-spatial-tracking' => [ + 'none' => false, + + '*' => false, + + 'self' => true, + + 'origins' => [], + ], + ], + + /* + * Content Security Policy + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP + */ + + 'csp' => [ + 'enable' => (bool) env('ENABLE_CSP', true), + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only + 'report-only' => false, + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to + 'report-to' => '', + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri + 'report-uri' => [ + // uri + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/block-all-mixed-content + 'block-all-mixed-content' => false, + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests + 'upgrade-insecure-requests' => false, + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/base-uri + 'base-uri' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/child-src + 'child-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src + 'connect-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src + 'default-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src + 'font-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action + 'form-action' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors + 'frame-ancestors' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src + 'frame-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src + 'img-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/manifest-src + 'manifest-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/media-src + 'media-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/navigate-to + 'navigate-to' => [ + 'unsafe-allow-redirects' => false, + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src + 'object-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/plugin-types + 'plugin-types' => [ + // 'application/pdf', + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/prefetch-src + 'prefetch-src' => [ + // + ], + + // https://w3c.github.io/webappsec-trusted-types/dist/spec/#integration-with-content-security-policy + 'require-trusted-types-for' => [ + 'script' => false, + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox + 'sandbox' => [ + 'enable' => false, + + 'allow-downloads-without-user-activation' => false, + + 'allow-forms' => false, + + 'allow-modals' => false, + + 'allow-orientation-lock' => false, + + 'allow-pointer-lock' => false, + + 'allow-popups' => false, + + 'allow-popups-to-escape-sandbox' => false, + + 'allow-presentation' => false, + + 'allow-same-origin' => false, + + 'allow-scripts' => false, + + 'allow-storage-access-by-user-activation' => false, + + 'allow-top-navigation' => false, + + 'allow-top-navigation-by-user-activation' => false, + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src + 'script-src' => [ + 'none' => false, + + 'self' => false, + + 'report-sample' => false, + + 'allow' => [ + // 'url', + ], + + 'schemes' => [ + // 'data:', + // 'https:', + ], + + /* followings are only work for `script` and `style` related directives */ + + 'unsafe-inline' => false, + + 'unsafe-eval' => false, + + // https://www.w3.org/TR/CSP3/#unsafe-hashes-usage + 'unsafe-hashes' => false, + + // Enable `strict-dynamic` will *ignore* `self`, `unsafe-inline`, + // `allow` and `schemes`. You can find more information from: + // https://www.w3.org/TR/CSP3/#strict-dynamic-usage + 'strict-dynamic' => false, + + 'hashes' => [ + 'sha256' => [ + // 'sha256-hash-value-with-base64-encode', + ], + + 'sha384' => [ + // 'sha384-hash-value-with-base64-encode', + ], + + 'sha512' => [ + // 'sha512-hash-value-with-base64-encode', + ], + ], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src-attr + 'script-src-attr' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src-elem + 'script-src-elem' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src + 'style-src' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src-attr + 'style-src-attr' => [ + // + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src-elem + 'style-src-elem' => [ + // + ], + + // https://w3c.github.io/webappsec-trusted-types/dist/spec/#trusted-types-csp-directive + 'trusted-types' => [ + 'enable' => false, + + 'allow-duplicates' => false, + + 'default' => false, + + 'policies' => [ + // + ], + ], + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src + 'worker-src' => [ + // + ], + ], +]; diff --git a/config/services.php b/config/services.php index 2a1d616c7..66f9451fa 100644 --- a/config/services.php +++ b/config/services.php @@ -1,5 +1,7 @@ env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'scheme' => 'https', ], 'postmark' => [ @@ -30,4 +33,17 @@ return [ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], + 'discord' => [ + 'token' => env('DISCORD_BOT_API_TOKEN'), + 'db_updates_discord_channel' => env('DB_UPDATES_DISCORD_CHANNEL'), + 'admin_discord_channel' => env('ADMIN_DISCORD_CHANNEL'), + ], + + 'mal' => [ + 'client' => env('MAL_CLIENT_ID'), + ], + + 'do' => [ + 'token' => env('DO_BEARER_TOKEN'), + ], ]; diff --git a/config/session.php b/config/session.php index bc9174f4b..2d0509228 100644 --- a/config/session.php +++ b/config/session.php @@ -1,5 +1,7 @@ env('SESSION_DRIVER', 'file'), + 'driver' => env('SESSION_DRIVER', 'database'), /* |-------------------------------------------------------------------------- @@ -72,7 +74,7 @@ return [ | */ - 'connection' => env('SESSION_CONNECTION', null), + 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- @@ -92,13 +94,15 @@ return [ | Session Cache Store |-------------------------------------------------------------------------- | - | When using the "apc", "memcached", or "dynamodb" session drivers you may + | While using one of the framework's cache driven session backends you may | list a cache store that should be used for these sessions. This value | must match with one of the application's configured cache "stores". | + | Affects: "apc", "dynamodb", "memcached", "redis" + | */ - 'store' => env('SESSION_STORE', null), + 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- @@ -153,7 +157,7 @@ return [ | */ - 'domain' => env('SESSION_DOMAIN', null), + 'domain' => env('SESSION_DOMAIN'), /* |-------------------------------------------------------------------------- @@ -162,11 +166,11 @@ return [ | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep - | the cookie from being sent to you if it can not be done securely. + | the cookie from being sent to you when it can't be done securely. | */ - 'secure' => env('SESSION_SECURE_COOKIE', null), + 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- @@ -188,9 +192,9 @@ return [ | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we - | do not enable this as other CSRF protection services are in place. + | will set this value to "lax" since this is a secure default value. | - | Supported: "lax", "strict", "none" + | Supported: "lax", "strict", "none", null | */ diff --git a/config/telescope.php b/config/telescope.php new file mode 100644 index 000000000..20b580183 --- /dev/null +++ b/config/telescope.php @@ -0,0 +1,178 @@ + env('TELESCOPE_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Telescope Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Telescope will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('TELESCOPE_PATH', 'telescope'), + + /* + |-------------------------------------------------------------------------- + | Telescope Storage Driver + |-------------------------------------------------------------------------- + | + | This configuration options determines the storage driver that will + | be used to store Telescope's data. In addition, you may set any + | custom options as needed by the particular driver you choose. + | + */ + + 'driver' => env('TELESCOPE_DRIVER', 'database'), + + 'storage' => [ + 'database' => [ + 'connection' => env('DB_CONNECTION', 'mysql'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Master Switch + |-------------------------------------------------------------------------- + | + | This option may be used to disable all Telescope watchers regardless + | of their individual configuration, which simply provides a single + | and convenient way to enable or disable Telescope data storage. + | + */ + + 'enabled' => env('TELESCOPE_ENABLED', true), + + /* + |-------------------------------------------------------------------------- + | Telescope Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Telescope route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => [ + 'web', + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Allowed / Ignored Paths & Commands + |-------------------------------------------------------------------------- + | + | The following array lists the URI paths and Artisan commands that will + | not be watched by Telescope. In addition to this list, some Laravel + | commands, like migrations and queue commands, are always ignored. + | + */ + + 'only_paths' => [ + // 'api/*' + ], + + 'ignore_paths' => [ + 'nova-api*', + ], + + 'ignore_commands' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Watchers + |-------------------------------------------------------------------------- + | + | The following array lists the "watchers" that will be registered with + | Telescope. The watchers gather the application's profile data when + | a request or task is executed. Feel free to customize this list. + | + */ + + 'watchers' => [ + Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true), + + Watchers\CacheWatcher::class => [ + 'enabled' => env('TELESCOPE_CACHE_WATCHER', true), + 'hidden' => [], + ], + + Watchers\CommandWatcher::class => [ + 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), + 'ignore' => [], + ], + + Watchers\DumpWatcher::class => env('TELESCOPE_DUMP_WATCHER', true), + + Watchers\EventWatcher::class => [ + 'enabled' => env('TELESCOPE_EVENT_WATCHER', true), + 'ignore' => [], + ], + + Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true), + + Watchers\GateWatcher::class => [ + 'enabled' => env('TELESCOPE_GATE_WATCHER', true), + 'ignore_abilities' => [], + 'ignore_packages' => true, + ], + + Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true), + Watchers\LogWatcher::class => env('TELESCOPE_LOG_WATCHER', true), + Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true), + + Watchers\ModelWatcher::class => [ + 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), + 'events' => ['eloquent.*'], + 'hydrations' => true, + ], + + Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true), + + Watchers\QueryWatcher::class => [ + 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), + 'ignore_packages' => true, + 'slow' => 100, + ], + + Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true), + + Watchers\RequestWatcher::class => [ + 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), + 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), + 'ignore_status_codes' => [], + ], + + Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true), + + Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true), + ], + +]; diff --git a/config/video.php b/config/video.php new file mode 100644 index 000000000..2ed61c43d --- /dev/null +++ b/config/video.php @@ -0,0 +1,24 @@ + env('VIDEO_URL'), + + 'path' => env('VIDEO_PATH'), +]; diff --git a/config/view.php b/config/view.php index 22b8a18d3..d9c90c045 100644 --- a/config/view.php +++ b/config/view.php @@ -1,5 +1,7 @@ (int) env('WIKI_FEATURED_ENTRY'), + + 'featured_video' => (int) env('WIKI_FEATURED_VIDEO'), +]; diff --git a/database/factories/Admin/AnnouncementFactory.php b/database/factories/Admin/AnnouncementFactory.php new file mode 100644 index 000000000..5d5e1d7d9 --- /dev/null +++ b/database/factories/Admin/AnnouncementFactory.php @@ -0,0 +1,38 @@ + + */ +class AnnouncementFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Announcement::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Announcement::ATTRIBUTE_CONTENT => $this->faker->sentence(), + ]; + } +} diff --git a/database/factories/Auth/InvitationFactory.php b/database/factories/Auth/InvitationFactory.php new file mode 100644 index 000000000..f18cc8cef --- /dev/null +++ b/database/factories/Auth/InvitationFactory.php @@ -0,0 +1,41 @@ + + */ +class InvitationFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Invitation::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Invitation::ATTRIBUTE_NAME => $this->faker->name(), + Invitation::ATTRIBUTE_EMAIL => $this->faker->safeEmail(), + Invitation::ATTRIBUTE_STATUS => InvitationStatus::OPEN, + ]; + } +} diff --git a/database/factories/Auth/UserFactory.php b/database/factories/Auth/UserFactory.php new file mode 100644 index 000000000..cb1fcccc9 --- /dev/null +++ b/database/factories/Auth/UserFactory.php @@ -0,0 +1,65 @@ + + */ +class UserFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = User::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + User::ATTRIBUTE_NAME => $this->faker->name(), + User::ATTRIBUTE_EMAIL => $this->faker->safeEmail(), + User::ATTRIBUTE_EMAIL_VERIFIED_AT => now(), + User::ATTRIBUTE_PASSWORD => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + User::ATTRIBUTE_REMEMBER_TOKEN => Str::random(10), + ]; + } + + /** + * Create and assign permission to user. + * + * @param string $ability + * @return static + */ + public function withPermission(string $ability): static + { + return $this->afterCreating( + function (User $user) use ($ability) { + $permission = Permission::findOrCreate($ability); + + App::make(PermissionRegistrar::class)->forgetCachedPermissions(); + + $user->givePermissionTo($permission); + } + ); + } +} diff --git a/database/factories/Billing/BalanceFactory.php b/database/factories/Billing/BalanceFactory.php new file mode 100644 index 000000000..3b2236313 --- /dev/null +++ b/database/factories/Billing/BalanceFactory.php @@ -0,0 +1,44 @@ + + */ +class BalanceFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Balance::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Balance::ATTRIBUTE_BALANCE => $this->faker->randomFloat(2), + Balance::ATTRIBUTE_FREQUENCY => BalanceFrequency::getRandomValue(), + Balance::ATTRIBUTE_DATE => $this->faker->date(), + Balance::ATTRIBUTE_SERVICE => Service::getRandomValue(), + Balance::ATTRIBUTE_USAGE => $this->faker->randomFloat(2), + ]; + } +} diff --git a/database/factories/Billing/TransactionFactory.php b/database/factories/Billing/TransactionFactory.php new file mode 100644 index 000000000..e1b185fdc --- /dev/null +++ b/database/factories/Billing/TransactionFactory.php @@ -0,0 +1,43 @@ + + */ +class TransactionFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Transaction::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Transaction::ATTRIBUTE_AMOUNT => $this->faker->randomFloat(2), + Transaction::ATTRIBUTE_DATE => $this->faker->date(), + Transaction::ATTRIBUTE_DESCRIPTION => $this->faker->sentence(), + Transaction::ATTRIBUTE_EXTERNAL_ID => $this->faker->uuid(), + Transaction::ATTRIBUTE_SERVICE => Service::getRandomValue(), + ]; + } +} diff --git a/database/factories/Document/PageFactory.php b/database/factories/Document/PageFactory.php new file mode 100644 index 000000000..590f8ce9a --- /dev/null +++ b/database/factories/Document/PageFactory.php @@ -0,0 +1,41 @@ + + */ +class PageFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Page::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Page::ATTRIBUTE_BODY => $this->faker->sentences(3, true), + Page::ATTRIBUTE_NAME => $this->faker->words(3, true), + Page::ATTRIBUTE_SLUG => Str::slug($this->faker->text(191), '_'), + ]; + } +} diff --git a/database/factories/Pivots/AnimeImageFactory.php b/database/factories/Pivots/AnimeImageFactory.php new file mode 100644 index 000000000..211f45076 --- /dev/null +++ b/database/factories/Pivots/AnimeImageFactory.php @@ -0,0 +1,38 @@ + + */ +class AnimeImageFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeImage::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/Pivots/AnimeResourceFactory.php b/database/factories/Pivots/AnimeResourceFactory.php new file mode 100644 index 000000000..d1b3ce78c --- /dev/null +++ b/database/factories/Pivots/AnimeResourceFactory.php @@ -0,0 +1,39 @@ + + */ +class AnimeResourceFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeResource::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + AnimeResource::ATTRIBUTE_AS => Str::random(), + ]; + } +} diff --git a/database/factories/Pivots/AnimeSeriesFactory.php b/database/factories/Pivots/AnimeSeriesFactory.php new file mode 100644 index 000000000..28eaab5be --- /dev/null +++ b/database/factories/Pivots/AnimeSeriesFactory.php @@ -0,0 +1,38 @@ + + */ +class AnimeSeriesFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeSeries::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/Pivots/AnimeStudioFactory.php b/database/factories/Pivots/AnimeStudioFactory.php new file mode 100644 index 000000000..7a9c59faa --- /dev/null +++ b/database/factories/Pivots/AnimeStudioFactory.php @@ -0,0 +1,38 @@ + + */ +class AnimeStudioFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeStudio::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/Pivots/AnimeThemeEntryVideoFactory.php b/database/factories/Pivots/AnimeThemeEntryVideoFactory.php new file mode 100644 index 000000000..502504808 --- /dev/null +++ b/database/factories/Pivots/AnimeThemeEntryVideoFactory.php @@ -0,0 +1,38 @@ + + */ +class AnimeThemeEntryVideoFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeThemeEntryVideo::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/Pivots/ArtistImageFactory.php b/database/factories/Pivots/ArtistImageFactory.php new file mode 100644 index 000000000..329d0dae4 --- /dev/null +++ b/database/factories/Pivots/ArtistImageFactory.php @@ -0,0 +1,38 @@ + + */ +class ArtistImageFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = ArtistImage::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/Pivots/ArtistMemberFactory.php b/database/factories/Pivots/ArtistMemberFactory.php new file mode 100644 index 000000000..d05434910 --- /dev/null +++ b/database/factories/Pivots/ArtistMemberFactory.php @@ -0,0 +1,39 @@ + + */ +class ArtistMemberFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = ArtistMember::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + ArtistMember::ATTRIBUTE_AS => Str::random(), + ]; + } +} diff --git a/database/factories/Pivots/ArtistResourceFactory.php b/database/factories/Pivots/ArtistResourceFactory.php new file mode 100644 index 000000000..cb4cdc963 --- /dev/null +++ b/database/factories/Pivots/ArtistResourceFactory.php @@ -0,0 +1,39 @@ + + */ +class ArtistResourceFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = ArtistResource::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + ArtistResource::ATTRIBUTE_AS => Str::random(), + ]; + } +} diff --git a/database/factories/Pivots/ArtistSongFactory.php b/database/factories/Pivots/ArtistSongFactory.php new file mode 100644 index 000000000..6603cbecc --- /dev/null +++ b/database/factories/Pivots/ArtistSongFactory.php @@ -0,0 +1,39 @@ + + */ +class ArtistSongFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = ArtistSong::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + ArtistSong::ATTRIBUTE_AS => Str::random(), + ]; + } +} diff --git a/database/factories/Pivots/StudioResourceFactory.php b/database/factories/Pivots/StudioResourceFactory.php new file mode 100644 index 000000000..32e782560 --- /dev/null +++ b/database/factories/Pivots/StudioResourceFactory.php @@ -0,0 +1,39 @@ + + */ +class StudioResourceFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = StudioResource::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + StudioResource::ATTRIBUTE_AS => Str::random(), + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php deleted file mode 100644 index fa9360e79..000000000 --- a/database/factories/UserFactory.php +++ /dev/null @@ -1,28 +0,0 @@ -define(User::class, function (Faker $faker) { - return [ - 'name' => $faker->name, - 'email' => $faker->unique()->safeEmail, - 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password - 'remember_token' => Str::random(10), - ]; -}); diff --git a/database/factories/Wiki/Anime/AnimeSynonymFactory.php b/database/factories/Wiki/Anime/AnimeSynonymFactory.php new file mode 100644 index 000000000..98764d06a --- /dev/null +++ b/database/factories/Wiki/Anime/AnimeSynonymFactory.php @@ -0,0 +1,38 @@ + + */ +class AnimeSynonymFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeSynonym::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + AnimeSynonym::ATTRIBUTE_TEXT => $this->faker->words(3, true), + ]; + } +} diff --git a/database/factories/Wiki/Anime/AnimeThemeFactory.php b/database/factories/Wiki/Anime/AnimeThemeFactory.php new file mode 100644 index 000000000..91ea580d0 --- /dev/null +++ b/database/factories/Wiki/Anime/AnimeThemeFactory.php @@ -0,0 +1,41 @@ + + */ +class AnimeThemeFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeTheme::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + AnimeTheme::ATTRIBUTE_GROUP => $this->faker->words(3, true), + AnimeTheme::ATTRIBUTE_SEQUENCE => $this->faker->randomDigitNotNull(), + AnimeTheme::ATTRIBUTE_TYPE => ThemeType::getRandomValue(), + ]; + } +} diff --git a/database/factories/Wiki/Anime/Theme/AnimeThemeEntryFactory.php b/database/factories/Wiki/Anime/Theme/AnimeThemeEntryFactory.php new file mode 100644 index 000000000..6ee10c6d8 --- /dev/null +++ b/database/factories/Wiki/Anime/Theme/AnimeThemeEntryFactory.php @@ -0,0 +1,42 @@ + + */ +class AnimeThemeEntryFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = AnimeThemeEntry::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + AnimeThemeEntry::ATTRIBUTE_EPISODES => $this->faker->word(), + AnimeThemeEntry::ATTRIBUTE_NOTES => $this->faker->word(), + AnimeThemeEntry::ATTRIBUTE_NSFW => $this->faker->boolean(), + AnimeThemeEntry::ATTRIBUTE_SPOILER => $this->faker->boolean(), + AnimeThemeEntry::ATTRIBUTE_VERSION => $this->faker->randomDigitNotNull(), + ]; + } +} diff --git a/database/factories/Wiki/AnimeFactory.php b/database/factories/Wiki/AnimeFactory.php new file mode 100644 index 000000000..ced92fdf0 --- /dev/null +++ b/database/factories/Wiki/AnimeFactory.php @@ -0,0 +1,95 @@ + + */ +class AnimeFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Anime::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Anime::ATTRIBUTE_NAME => $this->faker->words(3, true), + Anime::ATTRIBUTE_SEASON => AnimeSeason::getRandomValue(), + Anime::ATTRIBUTE_SLUG => Str::slug($this->faker->text(191), '_'), + Anime::ATTRIBUTE_SYNOPSIS => $this->faker->text(), + Anime::ATTRIBUTE_YEAR => $this->faker->numberBetween(1960, intval(date('Y')) + 1), + ]; + } + + /** + * Define the model's default Eloquent API Resource state. + * + * @return static + */ + public function jsonApiResource(): static + { + return $this->afterCreating( + function (Anime $anime) { + AnimeSynonym::factory() + ->for($anime) + ->count($this->faker->numberBetween(1, 3)) + ->create(); + + AnimeTheme::factory() + ->for($anime) + ->for(Song::factory()) + ->has( + AnimeThemeEntry::factory() + ->count($this->faker->numberBetween(1, 3)) + ->has(Video::factory()->count($this->faker->numberBetween(1, 3))) + ) + ->count($this->faker->numberBetween(1, 3)) + ->create(); + + Series::factory() + ->hasAttached($anime, [], 'anime') + ->count($this->faker->numberBetween(1, 3)) + ->create(); + + ExternalResource::factory() + ->hasAttached($anime, [], 'anime') + ->count($this->faker->numberBetween(1, 3)) + ->create(); + + Image::factory() + ->hasAttached($anime, [], 'anime') + ->count($this->faker->numberBetween(1, 3)) + ->create(); + } + ); + } +} diff --git a/database/factories/Wiki/ArtistFactory.php b/database/factories/Wiki/ArtistFactory.php new file mode 100644 index 000000000..d0f9b6ce9 --- /dev/null +++ b/database/factories/Wiki/ArtistFactory.php @@ -0,0 +1,83 @@ + + */ +class ArtistFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Artist::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Artist::ATTRIBUTE_SLUG => Str::slug($this->faker->text(191), '_'), + Artist::ATTRIBUTE_NAME => $this->faker->words(3, true), + ]; + } + + /** + * Define the model's default Eloquent API Resource state. + * + * @return static + */ + public function jsonApiResource(): static + { + return $this->afterCreating( + function (Artist $artist) { + Song::factory() + ->hasAttached($artist) + ->has(AnimeTheme::factory()->for(Anime::factory())) + ->count($this->faker->randomDigitNotNull()) + ->create(); + + Artist::factory() + ->hasAttached($artist, [], 'members') + ->count($this->faker->randomDigitNotNull()) + ->create(); + + Artist::factory() + ->hasAttached($artist, [], 'groups') + ->count($this->faker->randomDigitNotNull()) + ->create(); + + ExternalResource::factory() + ->hasAttached($artist) + ->count($this->faker->randomDigitNotNull()) + ->create(); + + Image::factory() + ->hasAttached($artist) + ->count($this->faker->randomDigitNotNull()) + ->create(); + } + ); + } +} diff --git a/database/factories/Wiki/ExternalResourceFactory.php b/database/factories/Wiki/ExternalResourceFactory.php new file mode 100644 index 000000000..c6a0d8102 --- /dev/null +++ b/database/factories/Wiki/ExternalResourceFactory.php @@ -0,0 +1,41 @@ + + */ +class ExternalResourceFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = ExternalResource::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + ExternalResource::ATTRIBUTE_EXTERNAL_ID => $this->faker->randomNumber(), + ExternalResource::ATTRIBUTE_LINK => $this->faker->url(), + ExternalResource::ATTRIBUTE_SITE => ResourceSite::getRandomValue(), + ]; + } +} diff --git a/database/factories/Wiki/ImageFactory.php b/database/factories/Wiki/ImageFactory.php new file mode 100644 index 000000000..30381dbf2 --- /dev/null +++ b/database/factories/Wiki/ImageFactory.php @@ -0,0 +1,41 @@ + + */ +class ImageFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Image::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Image::ATTRIBUTE_FACET => ImageFacet::getRandomValue(), + Image::ATTRIBUTE_PATH => Str::random(), + ]; + } +} diff --git a/database/factories/Wiki/SeriesFactory.php b/database/factories/Wiki/SeriesFactory.php new file mode 100644 index 000000000..eca9fd95d --- /dev/null +++ b/database/factories/Wiki/SeriesFactory.php @@ -0,0 +1,40 @@ + + */ +class SeriesFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Series::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Series::ATTRIBUTE_NAME => $this->faker->words(3, true), + Series::ATTRIBUTE_SLUG => Str::slug($this->faker->text(191), '_'), + ]; + } +} diff --git a/database/factories/Wiki/SongFactory.php b/database/factories/Wiki/SongFactory.php new file mode 100644 index 000000000..af54639be --- /dev/null +++ b/database/factories/Wiki/SongFactory.php @@ -0,0 +1,38 @@ + + */ +class SongFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Song::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Song::ATTRIBUTE_TITLE => $this->faker->words(3, true), + ]; + } +} diff --git a/database/factories/Wiki/StudioFactory.php b/database/factories/Wiki/StudioFactory.php new file mode 100644 index 000000000..c8c798e7b --- /dev/null +++ b/database/factories/Wiki/StudioFactory.php @@ -0,0 +1,40 @@ + + */ +class StudioFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Studio::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Studio::ATTRIBUTE_NAME => $this->faker->words(3, true), + Studio::ATTRIBUTE_SLUG => Str::slug($this->faker->text(191), '_'), + ]; + } +} diff --git a/database/factories/Wiki/VideoFactory.php b/database/factories/Wiki/VideoFactory.php new file mode 100644 index 000000000..ddcb6d679 --- /dev/null +++ b/database/factories/Wiki/VideoFactory.php @@ -0,0 +1,52 @@ + + */ +class VideoFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string