mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
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 <ProWeebDev@users.noreply.github.com> Co-authored-by: vagrant <vagrant@homestead> * 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 <ProWeebDev@users.noreply.github.com> Co-authored-by: vagrant <vagrant@homestead> Co-authored-by: Manuel S <ManuelDevelopment@users.noreply.github.com>
This commit is contained in:
@@ -13,3 +13,6 @@ trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
||||
|
||||
+173
-27
@@ -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=
|
||||
|
||||
+8
-3
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
+6
-1
@@ -1,7 +1,6 @@
|
||||
/node_modules
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/img/grill
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
@@ -12,3 +11,9 @@ Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
.vscode
|
||||
.idea
|
||||
auth.json
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
public/css/app.css
|
||||
public/js/app.js
|
||||
|
||||
+1
-3
@@ -1,7 +1,5 @@
|
||||
version: 8.1
|
||||
preset: laravel
|
||||
disabled:
|
||||
- unused_use
|
||||
finder:
|
||||
not-name:
|
||||
- index.php
|
||||
- server.php
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<p align="center">
|
||||
<a href="https://github.styleci.io/repos/111264405"><img src="https://github.styleci.io/repos/111264405/shield?branch=main" alt="StyleCI"></a>
|
||||
<a href="https://github.com/AnimeThemes/animethemes-server/actions/workflows/test.yml"><img src="https://github.com/AnimeThemes/animethemes-server/workflows/Tests/badge.svg?branch=wiki" alt="tests"></a>
|
||||
<a href="https://github.com/AnimeThemes/animethemes-server/actions/workflows/static-analysis.yml"><img src="https://github.com/AnimeThemes/animethemes-server/workflows/Static%20Analysis/badge.svg?branch=wiki" alt="static-analysis"></a>
|
||||
<a href="https://github.styleci.io/repos/111264405?branch=wiki"><img src="https://github.styleci.io/repos/111264405/shield?branch=wiki" alt="StyleCI"></a>
|
||||
<a href="https://discordapp.com/invite/m9zbVyQ"><img src="https://img.shields.io/discord/354388306580078594.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2"></a>
|
||||
<a href="https://github.com/AnimeThemes/animethemes-server/blob/master/LICENSE"><img src="https://img.shields.io/github/license/AnimeThemes/animethemes-server"></a>
|
||||
<a href="https://github.com/AnimeThemes/animethemes-server/blob/wiki/LICENSE"><img src="https://img.shields.io/github/license/AnimeThemes/animethemes-server"></a>
|
||||
<a href="https://reddit.com/r/AnimeThemes"><img src="https://img.shields.io/reddit/subreddit-subscribers/AnimeThemes?style=social"></a>
|
||||
<a href="https://twitter.com/AnimeThemesMoe"><img src="https://img.shields.io/twitter/follow/AnimeThemesMoe?style=social"></a>
|
||||
</p>
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
|
||||
/**
|
||||
* Class CreateNewUser.
|
||||
*/
|
||||
class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array $input
|
||||
* @return Model
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(array $input): Model
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['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')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||
|
||||
/**
|
||||
* Class ResetUserPassword.
|
||||
*/
|
||||
class ResetUserPassword implements ResetsUserPasswords
|
||||
{
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param mixed $user
|
||||
* @param array $input
|
||||
* @return void
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function reset($user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'password' => Password::required(),
|
||||
])->validate();
|
||||
|
||||
$user->forceFill([
|
||||
User::ATTRIBUTE_PASSWORD => Hash::make(Arr::get($input, 'password')),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Validation\Validator as IlluminateValidator;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||
|
||||
/**
|
||||
* Class UpdateUserPassword.
|
||||
*/
|
||||
class UpdateUserPassword implements UpdatesUserPasswords
|
||||
{
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param mixed $user
|
||||
* @param array $input
|
||||
* @return void
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function update($user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'current_password' => ['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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
|
||||
/**
|
||||
* Class UpdateUserProfileInformation.
|
||||
*/
|
||||
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
{
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param mixed $user
|
||||
* @param array $input
|
||||
* @return void
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function update(mixed $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Jetstream;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Jetstream\Contracts\DeletesUsers;
|
||||
|
||||
/**
|
||||
* Class DeleteUser.
|
||||
*/
|
||||
class DeleteUser implements DeletesUsers
|
||||
{
|
||||
/**
|
||||
* Delete the given user.
|
||||
*
|
||||
* @param mixed $user
|
||||
* @return void
|
||||
*/
|
||||
public function delete($user): void
|
||||
{
|
||||
DB::transaction(function () use ($user) {
|
||||
$user->tokens->each->delete();
|
||||
$user->delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns;
|
||||
|
||||
/**
|
||||
* Trait DetectsRedis.
|
||||
*/
|
||||
trait DetectsRedis
|
||||
{
|
||||
/**
|
||||
* Determine if the application uses any Redis services.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function appUsesRedis(): bool
|
||||
{
|
||||
// We assume here that if the cache, session, broadcasting or queue is powered by Redis,
|
||||
// then the application is using Redis.
|
||||
return config('cache.stores.'.config('cache.default').'.driver') === 'redis'
|
||||
|| config('broadcasting.connections.'.config('broadcasting.default').'.driver') === 'redis'
|
||||
|| config('session.driver') === 'redis'
|
||||
|| config('queue.connections.'.config('queue.default').'.driver') === 'redis';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Discord;
|
||||
|
||||
use App\Discord\DiscordEmbedField;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Trait HasAttributeUpdateEmbedFields.
|
||||
*/
|
||||
trait HasAttributeUpdateEmbedFields
|
||||
{
|
||||
use HasDiscordEmbedFields;
|
||||
|
||||
/**
|
||||
* Initialize embed fields with inline attribute changes.
|
||||
*
|
||||
* @param Model $model
|
||||
* @return void
|
||||
*/
|
||||
protected function initializeEmbedFields(Model $model): void
|
||||
{
|
||||
$original = $model->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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Discord;
|
||||
|
||||
use App\Discord\DiscordEmbedField;
|
||||
|
||||
/**
|
||||
* Trait HasDiscordEmbedFields.
|
||||
*/
|
||||
trait HasDiscordEmbedFields
|
||||
{
|
||||
/**
|
||||
* The array of embed fields.
|
||||
*
|
||||
* @var DiscordEmbedField[]
|
||||
*/
|
||||
protected array $embedFields = [];
|
||||
|
||||
/**
|
||||
* Add discord embed field.
|
||||
*
|
||||
* @param DiscordEmbedField $embedField
|
||||
* @return void
|
||||
*/
|
||||
protected function addEmbedField(DiscordEmbedField $embedField): void
|
||||
{
|
||||
$this->embedFields[] = $embedField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get discord embed fields.
|
||||
*
|
||||
* @return DiscordEmbedField[]
|
||||
*/
|
||||
protected function getEmbedFields(): array
|
||||
{
|
||||
return $this->embedFields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Http\Requests\Api;
|
||||
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* Trait ValidatesConditionally.
|
||||
*/
|
||||
trait ValidatesConditionally
|
||||
{
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
* Note: This function is invoked by name if it exists.
|
||||
* We define a decorator that provides better clarity for what conditional validation needs to be applied.
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return void
|
||||
*
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$this->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
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Http\Requests\Api;
|
||||
|
||||
use App\Contracts\Http\Api\Field\FieldInterface;
|
||||
use App\Contracts\Http\Api\Schema\SchemaInterface;
|
||||
use App\Http\Api\Parser\FieldParser;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Trait ValidatesFields.
|
||||
*/
|
||||
trait ValidatesFields
|
||||
{
|
||||
use ValidatesParameters;
|
||||
|
||||
/**
|
||||
* Restrict the allowed values for the schema fields.
|
||||
*
|
||||
* @param SchemaInterface $schema
|
||||
* @return array<string, array>
|
||||
*/
|
||||
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())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Http\Requests\Api;
|
||||
|
||||
use App\Contracts\Http\Api\Schema\SchemaInterface;
|
||||
use App\Enums\Http\Api\Filter\BinaryLogicalOperator;
|
||||
use App\Enums\Http\Api\Filter\LogicalOperator;
|
||||
use App\Enums\Http\Api\Filter\UnaryLogicalOperator;
|
||||
use App\Http\Api\Criteria\Filter\Criteria;
|
||||
use App\Http\Api\Filter\Filter;
|
||||
use App\Http\Api\Parser\FilterParser;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Fluent;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Validator;
|
||||
use Spatie\ValidationRules\Rules\Delimited;
|
||||
|
||||
/**
|
||||
* Trait ValidatesFilters.
|
||||
*/
|
||||
trait ValidatesFilters
|
||||
{
|
||||
use ValidatesParameters;
|
||||
|
||||
/**
|
||||
* Get the list of formatted filters for the schema.
|
||||
*
|
||||
* @param SchemaInterface $schema
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getSchemaFormattedFilters(SchemaInterface $schema): array
|
||||
{
|
||||
$schemaFilters = [];
|
||||
|
||||
foreach ($schema->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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Http\Requests\Api;
|
||||
|
||||
use App\Contracts\Http\Api\Schema\SchemaInterface;
|
||||
use App\Http\Api\Include\AllowedInclude;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
/**
|
||||
* Trait ValidatesIncludes.
|
||||
*/
|
||||
trait ValidatesIncludes
|
||||
{
|
||||
use ValidatesParameters;
|
||||
|
||||
/**
|
||||
* Restrict the allowed values for the schema includes.
|
||||
*
|
||||
* @param string $param
|
||||
* @param SchemaInterface $schema
|
||||
* @return array<string, array>
|
||||
*/
|
||||
protected function restrictAllowedIncludeValues(string $param, SchemaInterface $schema): array
|
||||
{
|
||||
return $this->restrictAllowedValues(
|
||||
$param,
|
||||
Arr::map($schema->allowedIncludes(), fn (AllowedInclude $include) => $include->path())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Http\Requests\Api;
|
||||
|
||||
use App\Http\Api\Criteria\Paging\Criteria;
|
||||
use App\Http\Api\Criteria\Paging\LimitCriteria;
|
||||
use App\Http\Api\Criteria\Paging\OffsetCriteria;
|
||||
use App\Http\Api\Parser\PagingParser;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Trait ValidatesPaging.
|
||||
*/
|
||||
trait ValidatesPaging
|
||||
{
|
||||
use ValidatesParameters;
|
||||
|
||||
/**
|
||||
* Validate offset pagination.
|
||||
*
|
||||
* @return array<string, array>
|
||||
*/
|
||||
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<string, 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<string, 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<string, array>
|
||||
*/
|
||||
protected function range(string $param, int $min = 1, int $max = Criteria::MAX_RESULTS): array
|
||||
{
|
||||
return $this->optional($param, ['integer', "min:$min", "max:$max"]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Http\Requests\Api;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Spatie\ValidationRules\Rules\Delimited;
|
||||
|
||||
/**
|
||||
* Trait ValidatesParameters.
|
||||
*/
|
||||
trait ValidatesParameters
|
||||
{
|
||||
/**
|
||||
* Restrict the allowed types for the parameter.
|
||||
*
|
||||
* @param string $param
|
||||
* @param Arrayable<int, string>|string[] $types
|
||||
* @return array<string, 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<int, string>|string[]|string $values
|
||||
* @param array $customRules
|
||||
* @return array<string, 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<string, array>
|
||||
*/
|
||||
protected function prohibit(string $param): array
|
||||
{
|
||||
return [
|
||||
$param => [
|
||||
'prohibited',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional parameter.
|
||||
*
|
||||
* @param string $param
|
||||
* @param array $customRules
|
||||
* @return array<string, 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<string, array>
|
||||
*/
|
||||
protected function require(string $param, array $customRules = []): array
|
||||
{
|
||||
return [
|
||||
$param => array_merge(
|
||||
['required'],
|
||||
$customRules,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Http\Requests\Api;
|
||||
|
||||
use App\Contracts\Http\Api\Schema\SchemaInterface;
|
||||
use App\Enums\Http\Api\Sort\Direction;
|
||||
use App\Rules\Api\DistinctIgnoringDirectionRule;
|
||||
use App\Rules\Api\RandomSoleRule;
|
||||
|
||||
/**
|
||||
* Trait ValidatesSorts.
|
||||
*/
|
||||
trait ValidatesSorts
|
||||
{
|
||||
use ValidatesParameters;
|
||||
|
||||
/**
|
||||
* Get allowed sorts for schema.
|
||||
*
|
||||
* @param SchemaInterface $schema
|
||||
* @return string[]
|
||||
*/
|
||||
protected function formatAllowedSortValues(SchemaInterface $schema): array
|
||||
{
|
||||
$allowedSorts = [];
|
||||
|
||||
foreach ($schema->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<string, array>
|
||||
*/
|
||||
protected function restrictAllowedSortValues(string $param, SchemaInterface $schema): array
|
||||
{
|
||||
return $this->restrictAllowedValues(
|
||||
$param,
|
||||
$this->formatAllowedSortValues($schema),
|
||||
[new DistinctIgnoringDirectionRule(), new RandomSoleRule()]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Repositories\Billing;
|
||||
|
||||
use App\Concerns\Repositories\ReconcilesRepositories;
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Models\Billing\Balance;
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Trait ReconcilesBalanceRepositories.
|
||||
*/
|
||||
trait ReconcilesBalanceRepositories
|
||||
{
|
||||
use ReconcilesRepositories;
|
||||
|
||||
/**
|
||||
* The columns used for create and delete set operations.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function columnsForCreateDelete(): array
|
||||
{
|
||||
return [
|
||||
Balance::ATTRIBUTE_DATE,
|
||||
Balance::ATTRIBUTE_ID,
|
||||
Balance::ATTRIBUTE_SERVICE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for create and delete set operation item comparison.
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
protected function diffCallbackForCreateDelete(): Closure
|
||||
{
|
||||
return fn (Balance $first, Balance $second) => $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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Repositories\Billing;
|
||||
|
||||
use App\Concerns\Repositories\ReconcilesRepositories;
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Models\Billing\Transaction;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Trait ReconcilesTransactionRepositories.
|
||||
*/
|
||||
trait ReconcilesTransactionRepositories
|
||||
{
|
||||
use ReconcilesRepositories;
|
||||
|
||||
/**
|
||||
* The columns used for create and delete set operations.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function columnsForCreateDelete(): array
|
||||
{
|
||||
return [
|
||||
Transaction::ATTRIBUTE_AMOUNT,
|
||||
Transaction::ATTRIBUTE_DATE,
|
||||
Transaction::ATTRIBUTE_EXTERNAL_ID,
|
||||
Transaction::ATTRIBUTE_ID,
|
||||
Transaction::ATTRIBUTE_SERVICE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for create and delete set operation item comparison.
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
protected function diffCallbackForCreateDelete(): Closure
|
||||
{
|
||||
return fn (Transaction $first, Transaction $second) => [$first->external_id, $first->date->format(AllowedDateFormat::YMD), $first->amount]
|
||||
<=> [$second->external_id, $second->date->format(AllowedDateFormat::YMD), $second->amount];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Repositories;
|
||||
|
||||
use App\Contracts\Repositories\Repository;
|
||||
use App\Models\BaseModel;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Trait ReconcilesRepositories.
|
||||
*/
|
||||
trait ReconcilesRepositories
|
||||
{
|
||||
/**
|
||||
* The number of models created.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $created = 0;
|
||||
|
||||
/**
|
||||
* The number of models whose creation failed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $createdFailed = 0;
|
||||
|
||||
/**
|
||||
* The number of models deleted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $deleted = 0;
|
||||
|
||||
/**
|
||||
* The number of models whose deletion failed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $deletedFailed = 0;
|
||||
|
||||
/**
|
||||
* The number of models updated.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $updated = 0;
|
||||
|
||||
/**
|
||||
* The number of models whose update failed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $updatedFailed = 0;
|
||||
|
||||
/**
|
||||
* Callback for successful model creation.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return void
|
||||
*/
|
||||
protected function handleCreated(BaseModel $model): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for failed model creation.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return void
|
||||
*/
|
||||
protected function handleFailedCreation(BaseModel $model): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for successful model deletion.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return void
|
||||
*/
|
||||
protected function handleDeleted(BaseModel $model): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for failed model deletion.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return void
|
||||
*/
|
||||
protected function handleFailedDeletion(BaseModel $model): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for successful model update.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return void
|
||||
*/
|
||||
protected function handleUpdated(BaseModel $model): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for failed model update.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return void
|
||||
*/
|
||||
protected function handleFailedUpdate(BaseModel $model): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for exception.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @return void
|
||||
*/
|
||||
protected function handleException(Exception $exception): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for handling completion of reconciliation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function postReconciliationTask(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if any changes, successful or not, were made during reconciliation.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasResults(): bool
|
||||
{
|
||||
return $this->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Concerns\Repositories\Wiki;
|
||||
|
||||
use App\Concerns\Repositories\ReconcilesRepositories;
|
||||
use App\Models\Wiki\Video;
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Trait ReconcilesVideoRepositories.
|
||||
*/
|
||||
trait ReconcilesVideoRepositories
|
||||
{
|
||||
use ReconcilesRepositories;
|
||||
|
||||
/**
|
||||
* The columns used for create and delete set operations.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function columnsForCreateDelete(): array
|
||||
{
|
||||
return [
|
||||
Video::ATTRIBUTE_BASENAME,
|
||||
Video::ATTRIBUTE_ID,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for create and delete set operation item comparison.
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
protected function diffCallbackForCreateDelete(): Closure
|
||||
{
|
||||
return fn (Video $first, Video $second) => $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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Announcement;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AnnouncementCreateCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'create:announcement
|
||||
{--alias= : An internal identifier with which an announcement can be referred}
|
||||
{--content= : The Announcement Text}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create Announcement for Welcome Screen';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Alias is required
|
||||
$alias = $this->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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Announcement;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AnnouncementDeleteCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'delete:announcement
|
||||
{--alias= : An internal identifier to which an announcement can be referred}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete Announcement from Welcome Screen';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Alias is required
|
||||
$alias = $this->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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Announcement;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class AnnouncementListCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'list:announcement';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'List Welcome Screen Announcements';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Display Announcements as table
|
||||
$headers = ['Alias', 'Content'];
|
||||
|
||||
$announcements = Announcement::all(['alias', 'content'])->toArray();
|
||||
|
||||
$this->table($headers, $announcements);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Announcement;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AnnouncementReadCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'read:announcement
|
||||
{--alias= : An internal identifier to which an announcement can be referred}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Read Welcome Screen Announcement by Alias';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Alias is required
|
||||
$alias = $this->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);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Announcement;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AnnouncementUpdateCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'update:announcement
|
||||
{--alias= : An internal identifier to which an announcement can be referred}
|
||||
{--content= : The Announcement Text}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update Announcement for Welcome Screen';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Alias is required
|
||||
$alias = $this->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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Billing\Balance;
|
||||
|
||||
use App\Concerns\Repositories\Billing\ReconcilesBalanceRepositories;
|
||||
use App\Console\Commands\Billing\ServiceReconcileCommand;
|
||||
use App\Contracts\Repositories\Repository;
|
||||
use App\Enums\Models\Billing\Service;
|
||||
use App\Models\BaseModel;
|
||||
use App\Repositories\Eloquent\Billing\DigitalOceanBalanceRepository as DigitalOceanDestinationRepository;
|
||||
use App\Repositories\Service\DigitalOcean\Billing\DigitalOceanBalanceRepository as DigitalOceanSourceRepository;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BalanceReconcileCommand.
|
||||
*/
|
||||
class BalanceReconcileCommand extends ServiceReconcileCommand
|
||||
{
|
||||
use ReconcilesBalanceRepositories;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reconcile:balance {service}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Perform set reconciliation between vendor billing API and balance database';
|
||||
|
||||
/**
|
||||
* 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 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Billing;
|
||||
|
||||
use App\Contracts\Repositories\Repository;
|
||||
use App\Enums\Models\Billing\Service;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class ServiceReconcileCommand.
|
||||
*/
|
||||
abstract class ServiceReconcileCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$key = $this->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;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Billing\Transaction;
|
||||
|
||||
use App\Concerns\Repositories\Billing\ReconcilesTransactionRepositories;
|
||||
use App\Console\Commands\Billing\ServiceReconcileCommand;
|
||||
use App\Contracts\Repositories\Repository;
|
||||
use App\Enums\Models\Billing\Service;
|
||||
use App\Models\Billing\Transaction;
|
||||
use App\Repositories\Eloquent\Billing\DigitalOceanTransactionRepository as DigitalOceanDestinationRepository;
|
||||
use App\Repositories\Service\DigitalOcean\Billing\DigitalOceanTransactionRepository as DigitalOceanSourceRepository;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class TransactionReconcileCommand.
|
||||
*/
|
||||
class TransactionReconcileCommand extends ServiceReconcileCommand
|
||||
{
|
||||
use ReconcilesTransactionRepositories;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reconcile:transaction {service}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Perform set reconciliation between vendor billing API and transaction database';
|
||||
|
||||
/**
|
||||
* 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 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\MySqlConnection;
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use PDO;
|
||||
use Spatie\DbDumper\Databases\MySql;
|
||||
use Spatie\DbDumper\Databases\PostgreSql;
|
||||
use Spatie\DbDumper\Databases\Sqlite;
|
||||
use Spatie\DbDumper\DbDumper;
|
||||
|
||||
/**
|
||||
* Class DatabaseDumpCommand.
|
||||
*/
|
||||
abstract class DatabaseDumpCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$create = $this->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;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Document;
|
||||
|
||||
use App\Console\Commands\DatabaseDumpCommand;
|
||||
use App\Models\Document\Page;
|
||||
|
||||
/**
|
||||
* Class DocumentDatabaseDumpCommand.
|
||||
*/
|
||||
class DocumentDatabaseDumpCommand extends DatabaseDumpCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:dump-document {--C|create : Whether the dumper should include create table statements}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Produces sanitized database dump, targeting document-related tables for seeding purposes';
|
||||
|
||||
/**
|
||||
* The list of tables to include in the dump.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function allowedTables(): array
|
||||
{
|
||||
return [
|
||||
Page::TABLE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The directory that the file should be dumped to.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDumpFilePath(): string
|
||||
{
|
||||
return 'document';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class PruneDatabaseDumpsCommand.
|
||||
*/
|
||||
class PruneDatabaseDumpsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:prune-dumps {--H|hours=72 : The number of hours to retain sanitized database dumps}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Prune stale database dumps from local storage';
|
||||
|
||||
/**
|
||||
* The number of dumps deleted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $deleted = 0;
|
||||
|
||||
/**
|
||||
* The number of dumps whose deletion failed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $deletedFailed = 0;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$hours = $this->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;
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Video;
|
||||
use Aws\S3\Exception\S3Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class VideoReconcileCommand extends Command
|
||||
{
|
||||
// Result Counts
|
||||
public $created = 0;
|
||||
public $created_failed = 0;
|
||||
public $deleted = 0;
|
||||
public $deleted_failed = 0;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reconcile:video';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Perform set reconcile between object storage and video database';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
// Get metadata for all objects in storage
|
||||
$fs = Storage::disk('spaces');
|
||||
$files = $fs->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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Wiki\Video;
|
||||
|
||||
use App\Concerns\Repositories\Wiki\ReconcilesVideoRepositories;
|
||||
use App\Models\BaseModel;
|
||||
use App\Repositories\Eloquent\Wiki\VideoRepository as VideoDestinationRepository;
|
||||
use App\Repositories\Service\DigitalOcean\VideoRepository as VideoSourceRepository;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class VideoReconcileCommand.
|
||||
*/
|
||||
class VideoReconcileCommand extends Command
|
||||
{
|
||||
use ReconcilesVideoRepositories;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reconcile:video
|
||||
{--path= : The directory of videos to reconcile. Ex: 2022/Spring/. If unspecified, all directories will be listed.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Perform set reconciliation between object storage and video database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$sourceRepository = App::make(VideoSourceRepository::class);
|
||||
|
||||
$destinationRepository = App::make(VideoDestinationRepository::class);
|
||||
|
||||
$path = $this->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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Wiki;
|
||||
|
||||
use App\Console\Commands\DatabaseDumpCommand;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeSynonym;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Models\Wiki\Image;
|
||||
use App\Models\Wiki\Series;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Studio;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Pivots\AnimeImage;
|
||||
use App\Pivots\AnimeResource;
|
||||
use App\Pivots\AnimeSeries;
|
||||
use App\Pivots\AnimeStudio;
|
||||
use App\Pivots\AnimeThemeEntryVideo;
|
||||
use App\Pivots\ArtistImage;
|
||||
use App\Pivots\ArtistMember;
|
||||
use App\Pivots\ArtistResource;
|
||||
use App\Pivots\ArtistSong;
|
||||
use App\Pivots\StudioResource;
|
||||
|
||||
/**
|
||||
* Class WikiDatabaseDumpCommand.
|
||||
*/
|
||||
class WikiDatabaseDumpCommand extends DatabaseDumpCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:dump-wiki {--C|create : Whether the dumper should include create table statements}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Produces sanitized database dump, targeting wiki-related tables for seeding purposes';
|
||||
|
||||
/**
|
||||
* The list of tables to include in the dump.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function allowedTables(): array
|
||||
{
|
||||
return [
|
||||
Anime::TABLE,
|
||||
AnimeImage::TABLE,
|
||||
AnimeResource::TABLE,
|
||||
AnimeSeries::TABLE,
|
||||
AnimeStudio::TABLE,
|
||||
AnimeSynonym::TABLE,
|
||||
AnimeTheme::TABLE,
|
||||
AnimeThemeEntry::TABLE,
|
||||
AnimeThemeEntryVideo::TABLE,
|
||||
Artist::TABLE,
|
||||
ArtistImage::TABLE,
|
||||
ArtistMember::TABLE,
|
||||
ArtistResource::TABLE,
|
||||
ArtistSong::TABLE,
|
||||
ExternalResource::TABLE,
|
||||
Image::TABLE,
|
||||
Series::TABLE,
|
||||
Song::TABLE,
|
||||
Studio::TABLE,
|
||||
StudioResource::TABLE,
|
||||
Video::TABLE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The directory that the file should be dumped to.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDumpFilePath(): string
|
||||
{
|
||||
return 'wiki';
|
||||
}
|
||||
}
|
||||
+89
-19
@@ -1,44 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\VideoReconcileCommand;
|
||||
use App\Console\Commands\Billing\Balance\BalanceReconcileCommand;
|
||||
use App\Console\Commands\Billing\Transaction\TransactionReconcileCommand;
|
||||
use App\Console\Commands\Document\DocumentDatabaseDumpCommand;
|
||||
use App\Console\Commands\PruneDatabaseDumpsCommand;
|
||||
use App\Console\Commands\Wiki\WikiDatabaseDumpCommand;
|
||||
use App\Enums\Models\Billing\Service;
|
||||
use App\Models\BaseModel;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Database\Console\PruneCommand as PruneModelsCommand;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use Illuminate\Queue\Console\PruneFailedJobsCommand;
|
||||
use Laravel\Horizon\Console\SnapshotCommand;
|
||||
use Laravel\Sanctum\Console\Commands\PruneExpired;
|
||||
use Laravel\Telescope\Console\PruneCommand as PruneTelescopeEntriesCommand;
|
||||
|
||||
/**
|
||||
* Class Kernel.
|
||||
*/
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
\App\Console\Commands\AnnouncementCreateCommand::class,
|
||||
\App\Console\Commands\AnnouncementReadCommand::class,
|
||||
\App\Console\Commands\AnnouncementUpdateCommand::class,
|
||||
\App\Console\Commands\AnnouncementDeleteCommand::class,
|
||||
\App\Console\Commands\AnnouncementListCommand::class,
|
||||
\App\Console\Commands\VideoReconcileCommand::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @param Schedule $schedule
|
||||
* @return void
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
$schedule->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');
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants\Config;
|
||||
|
||||
/**
|
||||
* Class FlagConstants.
|
||||
*/
|
||||
class FlagConstants
|
||||
{
|
||||
final public const ALLOW_VIDEO_STREAMS_FLAG = 'allow_video_streams';
|
||||
|
||||
final public const ALLOW_VIDEO_STREAMS_FLAG_QUALIFIED = 'flags.allow_video_streams';
|
||||
|
||||
final public const ALLOW_DISCORD_NOTIFICATIONS_FLAG = 'allow_discord_notifications';
|
||||
|
||||
final public const ALLOW_DISCORD_NOTIFICATIONS_FLAG_QUALIFIED = 'flags.allow_discord_notifications';
|
||||
|
||||
final public const ALLOW_VIEW_RECORDING_FLAG = 'allow_view_recording';
|
||||
|
||||
final public const ALLOW_VIEW_RECORDING_FLAG_QUALIFIED = 'flags.allow_view_recording';
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants\Config;
|
||||
|
||||
/**
|
||||
* Class ServiceConstants.
|
||||
*/
|
||||
class ServiceConstants
|
||||
{
|
||||
final public const ADMIN_DISCORD_CHANNEL_QUALIFIED = 'services.discord.admin_discord_channel';
|
||||
|
||||
final public const DB_UPDATES_DISCORD_CHANNEL_QUALIFIED = 'services.discord.db_updates_discord_channel';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants\Config;
|
||||
|
||||
/**
|
||||
* Class WikiConstants.
|
||||
*/
|
||||
class WikiConstants
|
||||
{
|
||||
final public const FEATURED_ENTRY_SETTING_QUALIFIED = 'wiki.featured_entry';
|
||||
|
||||
final public const FEATURED_THEME_SETTING = 'featured_theme';
|
||||
|
||||
final public const FEATURED_VIDEO_SETTING_QUALIFIED = 'wiki.featured_video';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Events;
|
||||
|
||||
/**
|
||||
* Interface CascadesDeletesEvent.
|
||||
*/
|
||||
interface CascadesDeletesEvent
|
||||
{
|
||||
/**
|
||||
* Perform cascading deletes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cascadeDeletes(): void;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Events;
|
||||
|
||||
/**
|
||||
* Interface CascadesRestoresEvent.
|
||||
*/
|
||||
interface CascadesRestoresEvent
|
||||
{
|
||||
/**
|
||||
* Perform cascading restores.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cascadeRestores(): void;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Events;
|
||||
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
/**
|
||||
* Interface DiscordMessageEvent.
|
||||
*/
|
||||
interface DiscordMessageEvent
|
||||
{
|
||||
/**
|
||||
* Get Discord message payload.
|
||||
*
|
||||
* @return DiscordMessage
|
||||
*/
|
||||
public function getDiscordMessage(): DiscordMessage;
|
||||
|
||||
/**
|
||||
* Get Discord channel the message will be sent to.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDiscordChannel(): string;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Events;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Nova\Notifications\NovaNotification;
|
||||
|
||||
/**
|
||||
* Interface NovaNotificationEvent.
|
||||
*/
|
||||
interface NovaNotificationEvent
|
||||
{
|
||||
/**
|
||||
* Determine if the notifications should be sent.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldSend(): bool;
|
||||
|
||||
/**
|
||||
* Get the nova notification.
|
||||
*
|
||||
* @return NovaNotification
|
||||
*/
|
||||
public function getNotification(): NovaNotification;
|
||||
|
||||
/**
|
||||
* Get the users to notify.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getUsers(): Collection;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Events;
|
||||
|
||||
/**
|
||||
* Interface UpdateRelatedIndicesEvent.
|
||||
*/
|
||||
interface UpdateRelatedIndicesEvent
|
||||
{
|
||||
/**
|
||||
* Perform updates on related indices.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateRelatedIndices(): void;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Field;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Interface CreatableField.
|
||||
*/
|
||||
interface CreatableField
|
||||
{
|
||||
/**
|
||||
* Set the creation validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getCreationRules(Request $request): array;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Field;
|
||||
|
||||
/**
|
||||
* Interface FieldInterface.
|
||||
*/
|
||||
interface FieldInterface
|
||||
{
|
||||
/**
|
||||
* Get the field key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey(): string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Field;
|
||||
|
||||
use App\Http\Api\Filter\Filter;
|
||||
|
||||
/**
|
||||
* Interface FilterableColumn.
|
||||
*/
|
||||
interface FilterableField
|
||||
{
|
||||
/**
|
||||
* Get the filters that can be applied to the field.
|
||||
*
|
||||
* @return Filter
|
||||
*/
|
||||
public function getFilter(): Filter;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Field;
|
||||
|
||||
use App\Http\Api\Criteria\Field\Criteria;
|
||||
|
||||
/**
|
||||
* Interface SelectableField.
|
||||
*/
|
||||
interface SelectableField
|
||||
{
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Field;
|
||||
|
||||
use App\Http\Api\Sort\Sort;
|
||||
|
||||
/**
|
||||
* Interface SortableColumn.
|
||||
*/
|
||||
interface SortableField
|
||||
{
|
||||
/**
|
||||
* Get the sort that can be applied to the field.
|
||||
*
|
||||
* @return Sort
|
||||
*/
|
||||
public function getSort(): Sort;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Field;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Interface UpdatableField.
|
||||
*/
|
||||
interface UpdatableField
|
||||
{
|
||||
/**
|
||||
* Set the update validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateRules(Request $request): array;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Query;
|
||||
|
||||
use App\Http\Api\Schema\Schema;
|
||||
|
||||
/**
|
||||
* Interface QueryInterface.
|
||||
*/
|
||||
interface QueryInterface
|
||||
{
|
||||
/**
|
||||
* Get the resource schema.
|
||||
*
|
||||
* @return Schema
|
||||
*/
|
||||
public function schema(): Schema;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Api\Schema;
|
||||
|
||||
use App\Contracts\Http\Api\Field\FieldInterface;
|
||||
use App\Http\Api\Filter\Filter;
|
||||
use App\Http\Api\Include\AllowedInclude;
|
||||
use App\Http\Api\Sort\Sort;
|
||||
|
||||
/**
|
||||
* Interface SchemaInterface.
|
||||
*/
|
||||
interface SchemaInterface
|
||||
{
|
||||
/**
|
||||
* Get the type of the resource.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string;
|
||||
|
||||
/**
|
||||
* Get the allowed includes.
|
||||
*
|
||||
* @return AllowedInclude[]
|
||||
*/
|
||||
public function allowedIncludes(): array;
|
||||
|
||||
/**
|
||||
* Get the direct fields of the resource.
|
||||
*
|
||||
* @return FieldInterface[]
|
||||
*/
|
||||
public function fields(): array;
|
||||
|
||||
/**
|
||||
* Get the filters of the resource.
|
||||
*
|
||||
* @return Filter[]
|
||||
*/
|
||||
public function filters(): array;
|
||||
|
||||
/**
|
||||
* Get the sorts of the resource.
|
||||
*
|
||||
* @return Sort[]
|
||||
*/
|
||||
public function sorts(): array;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Http\Requests\Api;
|
||||
|
||||
/**
|
||||
* Marker interface SearchableRequest.
|
||||
*/
|
||||
interface SearchableRequest
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Models;
|
||||
|
||||
/**
|
||||
* Interface Nameable.
|
||||
*/
|
||||
interface Nameable
|
||||
{
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Pipes;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Interface Pipe.
|
||||
*/
|
||||
interface Pipe
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param User $user
|
||||
* @param Closure(User): mixed $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(User $user, Closure $next): mixed;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Repositories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface Repository.
|
||||
*/
|
||||
interface Repository
|
||||
{
|
||||
/**
|
||||
* Get models from the repository.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return Collection
|
||||
*/
|
||||
public function get(array $columns = ['*']): Collection;
|
||||
|
||||
/**
|
||||
* Save model to the repository.
|
||||
*
|
||||
* @param Model $model
|
||||
* @return bool
|
||||
*/
|
||||
public function save(Model $model): bool;
|
||||
|
||||
/**
|
||||
* Delete model from the repository.
|
||||
*
|
||||
* @param Model $model
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(Model $model): bool;
|
||||
|
||||
/**
|
||||
* Update model in the repository.
|
||||
*
|
||||
* @param Model $model
|
||||
* @param array $attributes
|
||||
* @return bool
|
||||
*/
|
||||
public function update(Model $model, array $attributes): bool;
|
||||
|
||||
/**
|
||||
* Validate repository filter.
|
||||
*
|
||||
* @param string $filter
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function validateFilter(string $filter, mixed $value = null): bool;
|
||||
|
||||
/**
|
||||
* Filter repository models.
|
||||
*
|
||||
* @param string $filter
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function handleFilter(string $filter, mixed $value = null): void;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Discord;
|
||||
|
||||
use BenSampo\Enum\Enum;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Str;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class DiscordEmbedField.
|
||||
*/
|
||||
class DiscordEmbedField implements Arrayable, JsonSerializable
|
||||
{
|
||||
final public const DEFAULT_FIELD_VALUE = '-';
|
||||
|
||||
/**
|
||||
* The value of the field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected readonly string $value;
|
||||
|
||||
/**
|
||||
* Create a new field instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @param bool $inline
|
||||
*/
|
||||
final public function __construct(
|
||||
protected readonly string $name,
|
||||
mixed $value,
|
||||
protected readonly bool $inline = false
|
||||
) {
|
||||
$this->value = $this->formatEmbedFieldValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'value' => $this->value,
|
||||
'inline' => $this->inline,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object into something JSON serializable.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BenSampo\Enum\Contracts\LocalizedEnum;
|
||||
use BenSampo\Enum\Enum;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class BaseEnum.
|
||||
*/
|
||||
abstract class BaseEnum extends Enum implements LocalizedEnum
|
||||
{
|
||||
/**
|
||||
* Make a new instance from an enum description.
|
||||
*
|
||||
* @param string $description
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromDescription(string $description): ?static
|
||||
{
|
||||
return Arr::first(
|
||||
static::getInstances(),
|
||||
fn (BaseEnum $enum) => Str::lower($enum->description) === Str::lower($description)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Discord;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class EmbedColor.
|
||||
*
|
||||
* @method static static GREEN()
|
||||
* @method static static YELLOW()
|
||||
* @method static static RED()
|
||||
*/
|
||||
final class EmbedColor extends BaseEnum
|
||||
{
|
||||
public const GREEN = 3066993;
|
||||
public const YELLOW = 16776960;
|
||||
public const RED = 15158332;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Filter;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class AllowedDateFormat.
|
||||
*/
|
||||
final class AllowedDateFormat extends BaseEnum
|
||||
{
|
||||
public const YMDHISU = 'Y-m-d\TH:i:s.u';
|
||||
public const YMDHIS = 'Y-m-d\TH:i:s';
|
||||
public const YMDHI = 'Y-m-d\TH:i';
|
||||
public const YMDH = 'Y-m-d\TH';
|
||||
public const YMD = 'Y-m-d';
|
||||
public const YM = 'Y-m';
|
||||
public const Y = 'Y';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Filter;
|
||||
|
||||
/**
|
||||
* Class BinaryLogicalOperator.
|
||||
*
|
||||
* @method static static AND()
|
||||
* @method static static OR()
|
||||
*/
|
||||
final class BinaryLogicalOperator extends LogicalOperator
|
||||
{
|
||||
public const AND = 'and';
|
||||
public const OR = 'or';
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Filter;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class ComparisonOperator.
|
||||
*
|
||||
* @method static static EQ()
|
||||
* @method static static NE()
|
||||
* @method static static LT()
|
||||
* @method static static GT()
|
||||
* @method static static LTE()
|
||||
* @method static static GTE()
|
||||
* @method static static LIKE()
|
||||
* @method static static NOTLIKE()
|
||||
*/
|
||||
final class ComparisonOperator extends BaseEnum
|
||||
{
|
||||
public const EQ = '=';
|
||||
public const NE = '<>';
|
||||
public const LT = '<';
|
||||
public const GT = '>';
|
||||
public const LTE = '<=';
|
||||
public const GTE = '>=';
|
||||
public const LIKE = 'like';
|
||||
public const NOTLIKE = 'not like';
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Filter;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class LogicalOperator.
|
||||
*/
|
||||
abstract class LogicalOperator extends BaseEnum
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Filter;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class TrashedStatus.
|
||||
*/
|
||||
final class TrashedStatus extends BaseEnum
|
||||
{
|
||||
public const WITH = 'with';
|
||||
public const WITHOUT = 'without';
|
||||
public const ONLY = 'only';
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Filter;
|
||||
|
||||
/**
|
||||
* Class UnaryLogicalOperator.
|
||||
*/
|
||||
final class UnaryLogicalOperator extends LogicalOperator
|
||||
{
|
||||
public const NOT = 'not';
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Paging;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class PaginationStrategy.
|
||||
*
|
||||
* @method static static NONE()
|
||||
* @method static static LIMIT()
|
||||
* @method static static OFFSET()
|
||||
*/
|
||||
final class PaginationStrategy extends BaseEnum
|
||||
{
|
||||
public const NONE = 0;
|
||||
public const LIMIT = 1;
|
||||
public const OFFSET = 2;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Http\Api\Sort;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class Direction.
|
||||
*
|
||||
* @method static static ASCENDING()
|
||||
* @method static static DESCENDING()
|
||||
*/
|
||||
final class Direction extends BaseEnum
|
||||
{
|
||||
public const ASCENDING = 'asc';
|
||||
public const DESCENDING = 'desc';
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Auth;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class InvitationStatus.
|
||||
*
|
||||
* @method static static OPEN()
|
||||
* @method static static CLOSED()
|
||||
*/
|
||||
final class InvitationStatus extends BaseEnum
|
||||
{
|
||||
public const OPEN = 0;
|
||||
public const CLOSED = 1;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Billing;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class BalanceFrequency.
|
||||
*/
|
||||
final class BalanceFrequency extends BaseEnum
|
||||
{
|
||||
public const ONCE = 0;
|
||||
public const ANNUALLY = 1;
|
||||
public const BIANNUALLY = 2;
|
||||
public const QUARTERLY = 3;
|
||||
public const MONTHLY = 4;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Billing;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class Service.
|
||||
*
|
||||
* @method static static OTHER()
|
||||
* @method static static DIGITALOCEAN()
|
||||
* @method static static AWS()
|
||||
* @method static static HOVER()
|
||||
* @method static static WALKERSERVERS()
|
||||
*/
|
||||
final class Service extends BaseEnum
|
||||
{
|
||||
public const OTHER = 0;
|
||||
public const DIGITALOCEAN = 1;
|
||||
public const AWS = 2;
|
||||
public const HOVER = 3;
|
||||
public const WALKERSERVERS = 4;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Wiki;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class AnimeSeason.
|
||||
*/
|
||||
final class AnimeSeason extends BaseEnum
|
||||
{
|
||||
public const WINTER = 0;
|
||||
public const SPRING = 1;
|
||||
public const SUMMER = 2;
|
||||
public const FALL = 3;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Wiki;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class ImageFacet.
|
||||
*
|
||||
* @method static static COVER_SMALL()
|
||||
* @method static static COVER_LARGE()
|
||||
* @method static static GRILL()
|
||||
*/
|
||||
final class ImageFacet extends BaseEnum
|
||||
{
|
||||
public const COVER_SMALL = 0;
|
||||
public const COVER_LARGE = 1;
|
||||
public const GRILL = 2;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Wiki;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class ResourceSite.
|
||||
*
|
||||
* @method static static OFFICIAL_SITE()
|
||||
* @method static static TWITTER()
|
||||
* @method static static ANIDB()
|
||||
* @method static static ANILIST()
|
||||
* @method static static ANIME_PLANET()
|
||||
* @method static static ANN()
|
||||
* @method static static KITSU()
|
||||
* @method static static MAL()
|
||||
* @method static static WIKI()
|
||||
*/
|
||||
final class ResourceSite extends BaseEnum
|
||||
{
|
||||
// Official Media
|
||||
public const OFFICIAL_SITE = 0;
|
||||
public const TWITTER = 1;
|
||||
|
||||
// Tracking Sites
|
||||
public const ANIDB = 2;
|
||||
public const ANILIST = 3;
|
||||
public const ANIME_PLANET = 4;
|
||||
public const ANN = 5;
|
||||
public const KITSU = 6;
|
||||
public const MAL = 7;
|
||||
|
||||
// Compendia
|
||||
public const WIKI = 8;
|
||||
|
||||
/**
|
||||
* Get domain by resource site.
|
||||
*
|
||||
* @param int|null $value the resource site key
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getDomain(?int $value): ?string
|
||||
{
|
||||
return match ($value) {
|
||||
self::TWITTER => '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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Wiki;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class ThemeType.
|
||||
*/
|
||||
final class ThemeType extends BaseEnum
|
||||
{
|
||||
public const OP = 0;
|
||||
public const ED = 1;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Wiki;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class VideoOverlap.
|
||||
*
|
||||
* @method static static NONE()
|
||||
* @method static static TRANS()
|
||||
* @method static static OVER()
|
||||
*/
|
||||
final class VideoOverlap extends BaseEnum
|
||||
{
|
||||
public const NONE = 0;
|
||||
public const TRANS = 1;
|
||||
public const OVER = 2;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Models\Wiki;
|
||||
|
||||
use App\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Class VideoSource.
|
||||
*/
|
||||
final class VideoSource extends BaseEnum
|
||||
{
|
||||
public const WEB = 0;
|
||||
public const RAW = 1;
|
||||
public const BD = 2;
|
||||
public const DVD = 3;
|
||||
public const VHS = 4;
|
||||
public const LD = 5;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\Announcement;
|
||||
|
||||
use App\Events\Base\Admin\AdminCreatedEvent;
|
||||
use App\Models\Admin\Announcement;
|
||||
|
||||
/**
|
||||
* Class AnnouncementCreated.
|
||||
*
|
||||
* @extends AdminCreatedEvent<Announcement>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\Announcement;
|
||||
|
||||
use App\Events\Base\Admin\AdminDeletedEvent;
|
||||
use App\Models\Admin\Announcement;
|
||||
|
||||
/**
|
||||
* Class AnnouncementDeleted.
|
||||
*
|
||||
* @extends AdminDeletedEvent<Announcement>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\Announcement;
|
||||
|
||||
use App\Events\Base\Admin\AdminRestoredEvent;
|
||||
use App\Models\Admin\Announcement;
|
||||
|
||||
/**
|
||||
* Class AnnouncementRestored.
|
||||
*
|
||||
* @extends AdminRestoredEvent<Announcement>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\Announcement;
|
||||
|
||||
use App\Events\Base\Admin\AdminUpdatedEvent;
|
||||
use App\Models\Admin\Announcement;
|
||||
|
||||
/**
|
||||
* Class AnnouncementUpdated.
|
||||
*
|
||||
* @extends AdminUpdatedEvent<Announcement>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\Invitation;
|
||||
|
||||
use App\Events\Base\Admin\AdminCreatedEvent;
|
||||
use App\Models\Auth\Invitation;
|
||||
|
||||
/**
|
||||
* Class InvitationCreated.
|
||||
*
|
||||
* @extends AdminCreatedEvent<Invitation>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\Invitation;
|
||||
|
||||
use App\Events\Base\Admin\AdminDeletedEvent;
|
||||
use App\Models\Auth\Invitation;
|
||||
|
||||
/**
|
||||
* Class InvitationDeleted.
|
||||
*
|
||||
* @extends AdminDeletedEvent<Invitation>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\Invitation;
|
||||
|
||||
use App\Events\Base\Admin\AdminRestoredEvent;
|
||||
use App\Models\Auth\Invitation;
|
||||
|
||||
/**
|
||||
* Class InvitationRestored.
|
||||
*
|
||||
* @extends AdminRestoredEvent<Invitation>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\Invitation;
|
||||
|
||||
use App\Events\Base\Admin\AdminUpdatedEvent;
|
||||
use App\Models\Auth\Invitation;
|
||||
|
||||
/**
|
||||
* Class InvitationUpdated.
|
||||
*
|
||||
* @extends AdminUpdatedEvent<Invitation>
|
||||
*/
|
||||
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.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\User;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Contracts\Events\DiscordMessageEvent;
|
||||
use App\Enums\Discord\EmbedColor;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
/**
|
||||
* Class UserCreated.
|
||||
*/
|
||||
class UserCreated extends UserEvent implements DiscordMessageEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Get Discord message payload.
|
||||
*
|
||||
* @return DiscordMessage
|
||||
*/
|
||||
public function getDiscordMessage(): DiscordMessage
|
||||
{
|
||||
$user = $this->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\User;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Contracts\Events\DiscordMessageEvent;
|
||||
use App\Enums\Discord\EmbedColor;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
/**
|
||||
* Class UserDeleted.
|
||||
*/
|
||||
class UserDeleted extends UserEvent implements DiscordMessageEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* Get Discord message payload.
|
||||
*
|
||||
* @return DiscordMessage
|
||||
*/
|
||||
public function getDiscordMessage(): DiscordMessage
|
||||
{
|
||||
$user = $this->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\User;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
|
||||
/**
|
||||
* Class UserEvent.
|
||||
*/
|
||||
abstract class UserEvent
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param User $user
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(protected User $user)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that has fired this event.
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\User;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Contracts\Events\DiscordMessageEvent;
|
||||
use App\Enums\Discord\EmbedColor;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
/**
|
||||
* Class UserRestored.
|
||||
*/
|
||||
class UserRestored extends UserEvent implements DiscordMessageEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Get Discord message payload.
|
||||
*
|
||||
* @return DiscordMessage
|
||||
*/
|
||||
public function getDiscordMessage(): DiscordMessage
|
||||
{
|
||||
$user = $this->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\User;
|
||||
|
||||
use App\Concerns\Discord\HasAttributeUpdateEmbedFields;
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Contracts\Events\DiscordMessageEvent;
|
||||
use App\Enums\Discord\EmbedColor;
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
/**
|
||||
* Class UserUpdated.
|
||||
*/
|
||||
class UserUpdated extends UserEvent implements DiscordMessageEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use HasAttributeUpdateEmbedFields;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param User $user
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
parent::__construct($user);
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Base\Admin;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Events\Base\BaseCreatedEvent;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class AdminCreatedEvent.
|
||||
*
|
||||
* @template TModel of \App\Models\BaseModel
|
||||
* @extends BaseCreatedEvent<TModel>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Base\Admin;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Events\Base\BaseDeletedEvent;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class AdminDeletedEvent.
|
||||
*
|
||||
* @template TModel of \App\Models\BaseModel
|
||||
* @extends BaseDeletedEvent<TModel>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Base\Admin;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Events\Base\BaseRestoredEvent;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class AdminRestoredEvent.
|
||||
*
|
||||
* @template TModel of \App\Models\BaseModel
|
||||
* @extends BaseRestoredEvent<TModel>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Base\Admin;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Events\Base\BaseUpdatedEvent;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class AdminUpdatedEvent.
|
||||
*
|
||||
* @template TModel of \App\Models\BaseModel
|
||||
* @extends BaseUpdatedEvent<TModel>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user