├── .env.dist
├── .env.test
├── .gitignore
├── .symfony.insight.yaml
├── LICENSE
├── README.md
├── assets
├── _routes.js
├── app.js
└── styles
│ └── app.css
├── bin
├── console
└── phpunit
├── composer.json
├── composer.lock
├── config
├── bundles.php
├── packages
│ ├── asset_mapper.yaml
│ ├── cache.yaml
│ ├── csrf.yaml
│ ├── debug.yaml
│ ├── doctrine.yaml
│ ├── doctrine_migrations.yaml
│ ├── framework.yaml
│ ├── mailer.yaml
│ ├── messenger.yaml
│ ├── monolog.yaml
│ ├── notifier.yaml
│ ├── routing.yaml
│ ├── security.yaml
│ ├── translation.yaml
│ ├── twig.yaml
│ ├── validator.yaml
│ └── web_profiler.yaml
├── preload.php
├── routes.yaml
├── routes
│ ├── framework.yaml
│ ├── security.yaml
│ └── web_profiler.yaml
├── services.yaml
└── validator
│ └── validation.yaml
├── docker-compose.override.yml
├── docker-compose.yml
├── images
├── buffer.svg
├── tick-mask.svg
└── tick.svg
├── importmap.php
├── migrations
├── Version20201115082734.php
├── Version20230402040938.php
├── Version20230410131624.php
├── Version20230624052414.php
├── Version20230624062646.php
├── Version20250104062315.php
└── Version20250104074617.php
├── phpstan.neon
├── phpunit.xml.dist
├── public
├── app
│ ├── images
│ │ ├── icon-192x192.png
│ │ ├── icon-512x512.png
│ │ └── screenshots
│ │ │ ├── androidphone-item-dark.png
│ │ │ ├── androidphone-item-light.png
│ │ │ ├── ipad-feeds-search.png
│ │ │ ├── ipad-homescreen.jpg
│ │ │ ├── ipad-item-dark.png
│ │ │ ├── ipad-item-light.png
│ │ │ └── ipad-item-share.png
│ ├── translations
│ │ ├── en.json
│ │ └── fr.json
│ └── views
│ │ ├── author.html
│ │ ├── category.html
│ │ ├── feed.html
│ │ ├── item.html
│ │ ├── member.html
│ │ └── misc.html
├── favicon.ico
├── index.php
├── robots.txt
├── serviceworker.js
└── site.webmanifest
├── src
├── Command
│ ├── CollectionCommand.php
│ ├── ElasticsearchCheckCommand.php
│ ├── ElasticsearchCommand.php
│ ├── ElasticsearchCreateCommand.php
│ ├── ElasticsearchRemoveCommand.php
│ ├── ElasticsearchStatusCommand.php
│ ├── GenerateVapidCommand.php
│ ├── MaxmindCommand.php
│ ├── MemberCreateCommand.php
│ ├── PhpunitCommand.php
│ ├── PushCommand.php
│ └── SetupCommand.php
├── Controller
│ ├── AbstractAppController.php
│ ├── AuthorController.php
│ ├── CategoryController.php
│ ├── ConnectionController.php
│ ├── EnclosureController.php
│ ├── FeedController.php
│ ├── IndexController.php
│ ├── ItemController.php
│ ├── LoginController.php
│ ├── LogoutController.php
│ ├── MemberController.php
│ ├── MemberPasskeyController.php
│ ├── PinboardController.php
│ ├── ProfileController.php
│ ├── ProxyController.php
│ ├── PushController.php
│ └── SearchController.php
├── DataFixtures
│ └── feed.sql
├── Entity
│ ├── Action.php
│ ├── ActionAuthor.php
│ ├── ActionCategory.php
│ ├── ActionFeed.php
│ ├── ActionItem.php
│ ├── ActionTrait.php
│ ├── Author.php
│ ├── Category.php
│ ├── Collection.php
│ ├── CollectionFeed.php
│ ├── Connection.php
│ ├── DateCreatedTrait.php
│ ├── Enclosure.php
│ ├── ExtraFieldsTrait.php
│ ├── Feed.php
│ ├── FeedCategory.php
│ ├── IdTrait.php
│ ├── Item.php
│ ├── ItemCategory.php
│ ├── Member.php
│ └── MemberPasskey.php
├── Event
│ ├── ActionAuthorEvent.php
│ ├── ActionCategoryEvent.php
│ ├── ActionEvent.php
│ ├── ActionFeedEvent.php
│ ├── ActionItemEvent.php
│ ├── AuthorEvent.php
│ ├── CategoryEvent.php
│ ├── CollectionEvent.php
│ ├── CollectionFeedEvent.php
│ ├── ConnectionEvent.php
│ ├── EnclosureEvent.php
│ ├── FeedCategoryEvent.php
│ ├── FeedEvent.php
│ ├── ItemCategoryEvent.php
│ ├── ItemEvent.php
│ └── MemberEvent.php
├── EventSubscriber
│ ├── PinboardSubscriber.php
│ ├── SearchSubscriber.php
│ └── SubscribeSubscriber.php
├── Form
│ └── Type
│ │ ├── AuthorType.php
│ │ ├── CategoryType.php
│ │ ├── FeedType.php
│ │ ├── ImportOpmlType.php
│ │ ├── LoginType.php
│ │ ├── MemberPasskeyType.php
│ │ ├── MemberType.php
│ │ ├── ProfileType.php
│ │ └── PushType.php
├── Helper
│ ├── CleanHelper.php
│ ├── DeviceDetectorHelper.php
│ ├── ExtraFieldsHelper.php
│ ├── JwtHelper.php
│ └── MaxmindHelper.php
├── Kernel.php
├── Manager
│ ├── AbstractManager.php
│ ├── ActionAuthorManager.php
│ ├── ActionCategoryManager.php
│ ├── ActionFeedManager.php
│ ├── ActionItemManager.php
│ ├── ActionManager.php
│ ├── AuthorManager.php
│ ├── CategoryManager.php
│ ├── CollectionFeedManager.php
│ ├── CollectionManager.php
│ ├── ConnectionManager.php
│ ├── EnclosureManager.php
│ ├── FeedCategoryManager.php
│ ├── FeedManager.php
│ ├── ItemCategoryManager.php
│ ├── ItemManager.php
│ ├── MemberManager.php
│ ├── MemberPasskeyManager.php
│ ├── PushManager.php
│ └── SearchManager.php
├── Model
│ ├── DeviceDetectorModel.php
│ ├── ImportOpmlModel.php
│ ├── JwtPayloadModel.php
│ ├── LoginModel.php
│ ├── ProfileModel.php
│ ├── QueryParameterFilterModel.php
│ ├── QueryParameterPageModel.php
│ └── QueryParameterSortModel.php
├── Repository
│ ├── AbstractRepository.php
│ ├── ActionAuthorRepository.php
│ ├── ActionCategoryRepository.php
│ ├── ActionFeedRepository.php
│ ├── ActionItemRepository.php
│ ├── ActionRepository.php
│ ├── AuthorRepository.php
│ ├── CategoryRepository.php
│ ├── CollectionFeedRepository.php
│ ├── CollectionRepository.php
│ ├── ConnectionRepository.php
│ ├── EnclosureRepository.php
│ ├── FeedCategoryRepository.php
│ ├── FeedRepository.php
│ ├── ItemCategoryRepository.php
│ ├── ItemRepository.php
│ ├── MemberPasskeyRepository.php
│ └── MemberRepository.php
└── Security
│ ├── ApiAccessDeniedHandler.php
│ ├── ApiAccessTokenHandler.php
│ ├── SecurityMemberProvider.php
│ └── Voter
│ ├── AbstractVoter.php
│ ├── AuthorVoter.php
│ ├── CategoryVoter.php
│ ├── ConnectionVoter.php
│ ├── EnclosureVoter.php
│ ├── FeedVoter.php
│ ├── ItemVoter.php
│ ├── MemberPasskeyVoter.php
│ └── MemberVoter.php
├── symfony.lock
├── templates
└── base.html.twig
├── tests
├── ControllerAsAnonymous
│ ├── AuthorControllerTest.php
│ ├── CategoryControllerTest.php
│ ├── ConnectionControllerTest.php
│ ├── EnclosureControllerTest.php
│ ├── FeedControllerTest.php
│ ├── ItemControllerTest.php
│ ├── MemberPasskeyControllerTest.php
│ └── ProfileControllerTest.php
├── ControllerAsConnected
│ ├── AbstractControllerTest.php
│ ├── AuthorControllerTest.php
│ ├── CategoryControllerTest.php
│ ├── EnclosureControllerTest.php
│ ├── FeedControllerTest.php
│ └── ItemControllerTest.php
├── Entity
│ ├── ActionTest.php
│ ├── AuthorTest.php
│ ├── CategoryTest.php
│ ├── FeedTest.php
│ └── ItemTest.php
├── Manager
│ ├── ActionAuthorManagerTest.php
│ ├── ActionCategoryManagerTest.php
│ ├── ActionFeedManagerTest.php
│ ├── ActionItemManagerTest.php
│ ├── ActionManagerTest.php
│ ├── AuthorManagerTest.php
│ ├── CategoryManagerTest.php
│ ├── CollectionFeedManagerTest.php
│ ├── CollectionManagerTest.php
│ ├── ConnectionManagerTest.php
│ ├── EnclosureManagerTest.php
│ ├── FeedCategoryManagerTest.php
│ ├── FeedManagerTest.php
│ ├── ItemCategoryManagerTest.php
│ ├── ItemManagerTest.php
│ ├── MemberManagerTest.php
│ └── MemberPasskeyManagerTest.php
└── bootstrap.php
├── translations
└── .gitignore
└── update.sh
/.env.dist:
--------------------------------------------------------------------------------
1 | # In all environments, the following files are loaded if they exist,
2 | # the latter taking precedence over the former:
3 | #
4 | # * .env contains default values for the environment variables needed by the app
5 | # * .env.local uncommitted file with local overrides
6 | # * .env.$APP_ENV committed environment-specific defaults
7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides
8 | #
9 | # Real environment variables win over .env files.
10 | #
11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
12 | # https://symfony.com/doc/current/configuration/secrets.html
13 | #
14 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
15 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
16 |
17 | ###> symfony/framework-bundle ###
18 | APP_ENV=prod
19 | APP_SECRET=
20 | ###< symfony/framework-bundle ###
21 |
22 | ###> doctrine/doctrine-bundle ###
23 | # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
24 | # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
25 | #
26 | DATABASE_URL="mysql://your-user:your-password@127.0.0.1:3306/app?serverVersion=8.0.36&charset=utf8mb4"
27 | ###< doctrine/doctrine-bundle ###
28 |
29 | ###> symfony/messenger ###
30 | MESSENGER_TRANSPORT_DSN=sync://
31 | ###< symfony/messenger ###
32 |
33 | ###> symfony/mailer ###
34 | MAILER_DSN=null://null
35 | ###< symfony/mailer ###
36 |
37 | ###> app ###
38 | VAPID_PUBLIC_KEY=
39 | VAPID_PRIVATE_KEY=
40 | MAXMIND_ENABLED=false
41 | MAXMIND_ACCOUNT_ID=
42 | MAXMIND_LICENSE_KEY=
43 | ELASTICSEARCH_ENABLED=false
44 | ELASTICSEARCH_INDEX=feed
45 | ELASTICSEARCH_URL=http://127.0.0.1:9200
46 | ELASTICSEARCH_USERNAME=
47 | ELASTICSEARCH_PASSWORD=
48 | ELASTICSEARCH_API_KEY=
49 | SSL_VERIFY_PEER=true
50 | SSL_VERIFY_HOST=true
51 | LDAP_ENABLED=false
52 | LDAP_SERVER='ldap://127.0.0.1'
53 | LDAP_PORT=389
54 | LDAP_PROTOCOL=3
55 | LDAP_ROOT_DN='cn=Manager,dc=my-domain,dc=com'
56 | LDAP_ROOT_PW='secret'
57 | LDAP_BASE_DN='dc=my-domain,dc=com'
58 | LDAP_SEARCH_USER='mail=[email]'
59 | LDAP_SEARCH_GROUP_ADMIN='cn=admingroup'
60 | ###< app ###
61 |
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | # define your env variables for the test env here
2 | KERNEL_CLASS='App\Kernel'
3 | APP_SECRET='$ecretf0rt3st'
4 | SYMFONY_DEPRECATIONS_HELPER=999999
5 | PANTHER_APP_ENV=panther
6 | PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
7 |
8 | DATABASE_URL="mysql://your-user:your-password@your-host:3306/app?serverVersion=8&charset=utf8mb4"
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ###> symfony/framework-bundle ###
3 | /.env.local
4 | /.env.local.php
5 | /.env.*.local
6 | /config/secrets/prod/prod.decrypt.private.php
7 | /public/bundles/
8 | /var/
9 | /vendor/
10 | ###< symfony/framework-bundle ###
11 |
12 | ###> phpunit/phpunit ###
13 | /phpunit.xml
14 | .phpunit.result.cache
15 | ###< phpunit/phpunit ###
16 |
17 | ###> symfony/phpunit-bridge ###
18 | .phpunit.result.cache
19 | /phpunit.xml
20 | ###< symfony/phpunit-bridge ###
21 |
22 | ###> app ###
23 | .php-cs-fixer.cache
24 | /config/jwt-keys/application.key
25 | /config/jwt-keys/application.pub
26 | /maxmind
27 | /public/.htaccess
28 | ###< app ###
29 |
30 | ###> symfony/asset-mapper ###
31 | /public/assets/
32 | /assets/vendor/
33 | ###< symfony/asset-mapper ###
34 |
--------------------------------------------------------------------------------
/.symfony.insight.yaml:
--------------------------------------------------------------------------------
1 | php_version: 8.4
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016-present Stéphane Dion
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/assets/styles/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | overscroll-behavior-y: none;
3 | }
4 | main {
5 | height: 100%;
6 | overflow-x: hidden;
7 | overflow-y: auto;
8 | overscroll-behavior: contain;
9 | position: relative;
10 | }
11 | .favicon, a.favicon:link, a.favicon:visited {
12 | background-position: center left;
13 | background-repeat: no-repeat;
14 | background-size: 13px 13px;
15 | padding-left: 16px;
16 | }
17 | .mdl-card[dir="rtl"] .favicon, .mdl-card[dir="rtl"] a.favicon:link, .mdl-card[dir="rtl"] a.favicon:visited {
18 | background-position: center right;
19 | padding-left: 0px;
20 | padding-right: 16px;
21 | }
22 | h1 .favicon, h1.favicon, h1 a.favicon:link, h1 a.favicon:visited {
23 | background-size: 16px 16px;
24 | padding-left: 19px;
25 | }
26 | .mdl-card[dir="rtl"] h1 .favicon, .mdl-card[dir="rtl"] h1.favicon, .mdl-card[dir="rtl"] h1 a.favicon:link, .mdl-card[dir="rtl"] h1 a.favicon:visited {
27 | padding-left: 0px;
28 | padding-right: 19px;
29 | }
30 |
31 | img, iframe, video {
32 | max-width: 100%;
33 | }
34 | figure {
35 | display: block;
36 | -webkit-margin-start: 0px;
37 | -webkit-margin-end: 0px;
38 | }
39 | textarea {
40 | min-height: 250px;
41 | }
42 |
43 | body.anonymous .connected {
44 | display: none;
45 | }
46 | body.connected .anonymous {
47 | display: none;
48 | }
49 |
50 | body.online .offline {
51 | display: none;
52 | }
53 | body.offline .online {
54 | display: none;
55 | }
56 |
57 | body.not_administrator .administrator {
58 | display: none;
59 | }
60 | body.administrator .not_administrator {
61 | display: none;
62 | }
63 |
64 | body.no_entries .action-down, body.no_entries .action-up, body.no_entries .action-unread {
65 | display: none;
66 | }
67 |
68 | body.collapse .mdl-card__supporting-text {
69 | display: none;
70 | }
71 |
72 | body .mdl-card.collapse .mdl-card__supporting-text {
73 | display: none;
74 | }
75 |
76 | body .mdl-card.expand .mdl-card__supporting-text {
77 | display: block;
78 | }
79 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | = 80000) {
10 | require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
11 | } else {
12 | define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
13 | require PHPUNIT_COMPOSER_INSTALL;
14 | PHPUnit\TextUI\Command::main();
15 | }
16 | } else {
17 | if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
18 | echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
19 | exit(1);
20 | }
21 |
22 | require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
23 | }
24 |
--------------------------------------------------------------------------------
/config/bundles.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
5 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
6 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
7 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
8 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
9 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
10 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
11 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
12 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
13 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
14 | Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true],
15 | ];
16 |
--------------------------------------------------------------------------------
/config/packages/asset_mapper.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | asset_mapper:
3 | # The paths to make available to the asset mapper.
4 | paths:
5 | - assets/
6 | missing_import_mode: strict
7 |
8 | when@prod:
9 | framework:
10 | asset_mapper:
11 | missing_import_mode: warn
12 |
--------------------------------------------------------------------------------
/config/packages/cache.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | cache:
3 | # Unique name of your app: used to compute stable namespaces for cache keys.
4 | #prefix_seed: your_vendor_name/app_name
5 |
6 | # The "app" cache stores to the filesystem by default.
7 | # The data in this cache should persist between deploys.
8 | # Other options include:
9 |
10 | # Redis
11 | #app: cache.adapter.redis
12 | #default_redis_provider: redis://localhost
13 |
14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
15 | #app: cache.adapter.apcu
16 |
17 | # Namespaced pools use the above "app" backend by default
18 | #pools:
19 | #my.dedicated.cache: null
20 |
--------------------------------------------------------------------------------
/config/packages/csrf.yaml:
--------------------------------------------------------------------------------
1 | # Enable stateless CSRF protection for forms and logins/logouts
2 | framework:
3 | form:
4 | csrf_protection:
5 | token_id: submit
6 |
7 | csrf_protection:
8 | stateless_token_ids:
9 | - submit
10 | - authenticate
11 | - logout
12 |
--------------------------------------------------------------------------------
/config/packages/debug.yaml:
--------------------------------------------------------------------------------
1 | when@dev:
2 | debug:
3 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
4 | # See the "server:dump" command to start a new server.
5 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
6 |
--------------------------------------------------------------------------------
/config/packages/doctrine.yaml:
--------------------------------------------------------------------------------
1 | doctrine:
2 | dbal:
3 | url: '%env(resolve:DATABASE_URL)%'
4 |
5 | # IMPORTANT: You MUST configure your server version,
6 | # either here or in the DATABASE_URL env var (see .env file)
7 | #server_version: '16'
8 |
9 | profiling_collect_backtrace: '%kernel.debug%'
10 | use_savepoints: true
11 | orm:
12 | auto_generate_proxy_classes: true
13 | enable_lazy_ghost_objects: true
14 | report_fields_where_declared: true
15 | validate_xml_mapping: true
16 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
17 | identity_generation_preferences:
18 | Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
19 | auto_mapping: true
20 | mappings:
21 | App:
22 | type: attribute
23 | is_bundle: false
24 | dir: '%kernel.project_dir%/src/Entity'
25 | prefix: 'App\Entity'
26 | alias: App
27 | controller_resolver:
28 | auto_mapping: false
29 |
30 | when@test:
31 | doctrine:
32 | dbal:
33 | # "TEST_TOKEN" is typically set by ParaTest
34 | #dbname_suffix: '_test%env(default::TEST_TOKEN)%'
35 |
36 | when@prod:
37 | doctrine:
38 | orm:
39 | auto_generate_proxy_classes: false
40 | proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
41 | query_cache_driver:
42 | type: pool
43 | pool: doctrine.system_cache_pool
44 | result_cache_driver:
45 | type: pool
46 | pool: doctrine.result_cache_pool
47 |
48 | framework:
49 | cache:
50 | pools:
51 | doctrine.result_cache_pool:
52 | adapter: cache.app
53 | doctrine.system_cache_pool:
54 | adapter: cache.system
55 |
--------------------------------------------------------------------------------
/config/packages/doctrine_migrations.yaml:
--------------------------------------------------------------------------------
1 | doctrine_migrations:
2 | migrations_paths:
3 | # namespace is arbitrary but should be different from App\Migrations
4 | # as migrations classes should NOT be autoloaded
5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations'
6 | enable_profiler: false
7 |
--------------------------------------------------------------------------------
/config/packages/framework.yaml:
--------------------------------------------------------------------------------
1 | # see https://symfony.com/doc/current/reference/configuration/framework.html
2 | framework:
3 | secret: '%env(APP_SECRET)%'
4 |
5 | # Enables session support. Note that the session will ONLY be started if you read or write from it.
6 | # Remove or comment this section to explicitly disable session support.
7 | session:
8 | enabled: true
9 | handler_id: null
10 | cookie_secure: auto
11 | cookie_samesite: lax
12 |
13 | #esi: true
14 | #fragments: true
15 |
16 | when@test:
17 | framework:
18 | test: true
19 | session:
20 | storage_factory_id: session.storage.factory.mock_file
21 |
--------------------------------------------------------------------------------
/config/packages/mailer.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | mailer:
3 | dsn: '%env(MAILER_DSN)%'
4 |
--------------------------------------------------------------------------------
/config/packages/messenger.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | messenger:
3 | failure_transport: failed
4 |
5 | transports:
6 | # https://symfony.com/doc/current/messenger.html#transport-configuration
7 | async:
8 | dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
9 | options:
10 | use_notify: true
11 | check_delayed_interval: 60000
12 | retry_strategy:
13 | max_retries: 3
14 | multiplier: 2
15 | failed: 'doctrine://default?queue_name=failed'
16 | # sync: 'sync://'
17 |
18 | default_bus: messenger.bus.default
19 |
20 | buses:
21 | messenger.bus.default: []
22 |
23 | routing:
24 | Symfony\Component\Mailer\Messenger\SendEmailMessage: async
25 | Symfony\Component\Notifier\Message\ChatMessage: async
26 | Symfony\Component\Notifier\Message\SmsMessage: async
27 |
28 | # Route your messages to the transports
29 | # 'App\Message\YourMessage': async
30 |
--------------------------------------------------------------------------------
/config/packages/monolog.yaml:
--------------------------------------------------------------------------------
1 | monolog:
2 | channels:
3 | - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
4 |
5 | handlers:
6 | main:
7 | action_level: 'warning'
8 | buffer_size: 50
9 | excluded_http_codes: [400, 401, 403, 404, 405]
10 | handler: 'nested'
11 | type: 'fingers_crossed'
12 |
13 | nested:
14 | channels: ['!event']
15 | level: 'info'
16 | max_files: 10
17 | path: '%kernel.logs_dir%/%kernel.environment%.log'
18 | type: 'rotating_file'
19 |
20 | when@dev:
21 | monolog:
22 | handlers:
23 | deprecation:
24 | channels: ['deprecation']
25 | level: 'info'
26 | max_files: 10
27 | path: '%kernel.logs_dir%/%kernel.environment%-deprecation.log'
28 | type: 'rotating_file'
29 |
--------------------------------------------------------------------------------
/config/packages/notifier.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | notifier:
3 | chatter_transports:
4 | texter_transports:
5 | channel_policy:
6 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo
7 | urgent: ['email']
8 | high: ['email']
9 | medium: ['email']
10 | low: ['email']
11 | admin_recipients:
12 | - { email: admin@example.com }
13 |
--------------------------------------------------------------------------------
/config/packages/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
4 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
5 | #default_uri: http://localhost
6 |
7 | when@prod:
8 | framework:
9 | router:
10 | strict_requirements: null
11 |
--------------------------------------------------------------------------------
/config/packages/security.yaml:
--------------------------------------------------------------------------------
1 | security:
2 | # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
3 | password_hashers:
4 | Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
5 | # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
6 | providers:
7 | app_member_provider:
8 | id: App\Security\SecurityMemberProvider
9 |
10 | firewalls:
11 | dev:
12 | pattern: ^/(_(profiler|wdt)|css|images|js)/
13 | security: false
14 |
15 | api:
16 | pattern: ^/api
17 | context: api
18 | lazy: true
19 | provider: app_member_provider
20 | stateless: true
21 | access_denied_handler: App\Security\ApiAccessDeniedHandler
22 | access_token:
23 | token_handler: App\Security\ApiAccessTokenHandler
24 |
25 | main:
26 | lazy: true
27 | provider: app_member_provider
28 | stateless: false
29 |
30 | # activate different ways to authenticate
31 | # https://symfony.com/doc/current/security.html#the-firewall
32 |
33 | # https://symfony.com/doc/current/security/impersonating_user.html
34 | # switch_user: true
35 |
36 | # Easy way to control access for large sections of your site
37 | # Note: Only the *first* access control that matches will be used
38 | access_control:
39 | # - { path: ^/admin, roles: ROLE_ADMIN }
40 | # - { path: ^/profile, roles: ROLE_USER }
41 | - { path: ^/api, roles: ROLE_USER }
42 |
43 | when@test:
44 | security:
45 | password_hashers:
46 | # By default, password hashers are resource intensive and take time. This is
47 | # important to generate secure password hashes. In tests however, secure hashes
48 | # are not important, waste resources and increase test times. The following
49 | # reduces the work factor to the lowest possible values.
50 | Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
51 | algorithm: auto
52 | cost: 4 # Lowest possible value for bcrypt
53 | time_cost: 3 # Lowest possible value for argon
54 | memory_cost: 10 # Lowest possible value for argon
55 |
--------------------------------------------------------------------------------
/config/packages/translation.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | default_locale: en
3 | translator:
4 | default_path: '%kernel.project_dir%/translations'
5 | fallbacks:
6 | - en
7 | providers:
8 |
--------------------------------------------------------------------------------
/config/packages/twig.yaml:
--------------------------------------------------------------------------------
1 | twig:
2 | default_path: '%kernel.project_dir%/templates'
3 | globals:
4 | vapidPublicKey: '%env(VAPID_PUBLIC_KEY)%'
5 | file_name_pattern: '*.twig'
6 |
7 | when@test:
8 | twig:
9 | strict_variables: true
10 |
--------------------------------------------------------------------------------
/config/packages/validator.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | validation:
3 | # Enables validator auto-mapping support.
4 | # For instance, basic validation constraints will be inferred from Doctrine's metadata.
5 | #auto_mapping:
6 | # App\Entity\: []
7 |
8 | when@test:
9 | framework:
10 | validation:
11 | not_compromised_password: false
12 |
--------------------------------------------------------------------------------
/config/packages/web_profiler.yaml:
--------------------------------------------------------------------------------
1 | when@dev:
2 | web_profiler:
3 | toolbar: true
4 | intercept_redirects: false
5 |
6 | framework:
7 | profiler:
8 | only_exceptions: false
9 | collect_serializer_data: true
10 |
11 | when@test:
12 | web_profiler:
13 | toolbar: false
14 | intercept_redirects: false
15 |
16 | framework:
17 | profiler: { collect: false }
18 |
--------------------------------------------------------------------------------
/config/preload.php:
--------------------------------------------------------------------------------
1 | doctrine/doctrine-bundle ###
5 | database:
6 | ports:
7 | - "5432"
8 | ###< doctrine/doctrine-bundle ###
9 |
10 | ###> symfony/mailer ###
11 | mailer:
12 | image: schickling/mailcatcher
13 | ports: ["1025", "1080"]
14 | ###< symfony/mailer ###
15 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | ###> doctrine/doctrine-bundle ###
5 | database:
6 | image: postgres:${POSTGRES_VERSION:-15}-alpine
7 | environment:
8 | POSTGRES_DB: ${POSTGRES_DB:-app}
9 | # You should definitely change the password in production
10 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
11 | POSTGRES_USER: ${POSTGRES_USER:-app}
12 | volumes:
13 | - database_data:/var/lib/postgresql/data:rw
14 | # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
15 | # - ./docker/db/data:/var/lib/postgresql/data:rw
16 | ###< doctrine/doctrine-bundle ###
17 |
18 | volumes:
19 | ###> doctrine/doctrine-bundle ###
20 | database_data:
21 | ###< doctrine/doctrine-bundle ###
22 |
--------------------------------------------------------------------------------
/images/buffer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/tick-mask.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/tick.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/importmap.php:
--------------------------------------------------------------------------------
1 | [
16 | 'path' => './assets/app.js',
17 | 'entrypoint' => true,
18 | ],
19 | 'bootstrap-icons/font/bootstrap-icons.min.css' => [
20 | 'version' => '1.11.3',
21 | 'type' => 'css',
22 | ],
23 | 'bootstrap' => [
24 | 'version' => '5.3.3',
25 | ],
26 | '@popperjs/core' => [
27 | 'version' => '2.11.8',
28 | ],
29 | 'bootstrap/dist/css/bootstrap.min.css' => [
30 | 'version' => '5.3.3',
31 | 'type' => 'css',
32 | ],
33 | 'moment-timezone' => [
34 | 'version' => '0.5.47',
35 | ],
36 | 'moment' => [
37 | 'version' => '2.30.1',
38 | ],
39 | 'jquery.scrollto' => [
40 | 'version' => '2.1.3',
41 | ],
42 | 'jquery' => [
43 | 'version' => '3.7.1',
44 | ],
45 | 'handlebars' => [
46 | 'version' => '4.7.8',
47 | ],
48 | 'i18next' => [
49 | 'version' => '24.2.2',
50 | ],
51 | 'file-saver' => [
52 | 'version' => '2.0.5',
53 | ],
54 | ];
55 |
--------------------------------------------------------------------------------
/migrations/Version20230402040938.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE connection ADD extra_fields JSON DEFAULT NULL, DROP ip, DROP agent');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE connection ADD ip VARCHAR(255) DEFAULT NULL, ADD agent VARCHAR(255) DEFAULT NULL, DROP extra_fields');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20230410131624.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE connection CHANGE token token VARCHAR(500) NOT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE connection CHANGE token token VARCHAR(255) NOT NULL');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20230624052414.php:
--------------------------------------------------------------------------------
1 | addSql('DELETE FROM action_item WHERE action_id = 12;');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/migrations/Version20230624062646.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE messenger_messages CHANGE created_at created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', CHANGE available_at available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', CHANGE delivered_at delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE messenger_messages CHANGE created_at created_at DATETIME NOT NULL, CHANGE available_at available_at DATETIME NOT NULL, CHANGE delivered_at delivered_at DATETIME DEFAULT NULL');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20250104062315.php:
--------------------------------------------------------------------------------
1 | addSql('CREATE TABLE member_passkey (id INT UNSIGNED AUTO_INCREMENT NOT NULL, member_id INT UNSIGNED NOT NULL, title VARCHAR(255) NOT NULL, credential_id VARCHAR(255) NOT NULL, public_key TEXT NOT NULL, last_time_active DATETIME DEFAULT NULL, date_created DATETIME NOT NULL, INDEX member_id (member_id), INDEX credential_id (credential_id), INDEX date_created (date_created), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
24 | $this->addSql('ALTER TABLE member_passkey ADD CONSTRAINT FK_BF7183E87597D3FE FOREIGN KEY (member_id) REFERENCES `member` (id) ON DELETE CASCADE');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->addSql('ALTER TABLE member_passkey DROP FOREIGN KEY FK_BF7183E87597D3FE');
31 | $this->addSql('DROP TABLE member_passkey');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/migrations/Version20250104074617.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE `member` ADD passkey_challenge VARCHAR(255) DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE `member` DROP passkey_challenge');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 6
3 | paths:
4 | - bin/
5 | - config/
6 | - public/
7 | - src/
8 | - tests/
9 | checkGenericClassInNonGenericObjectType: false
10 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | tests
23 |
24 |
25 |
26 |
27 |
28 | src
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/app/images/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/icon-192x192.png
--------------------------------------------------------------------------------
/public/app/images/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/icon-512x512.png
--------------------------------------------------------------------------------
/public/app/images/screenshots/androidphone-item-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/screenshots/androidphone-item-dark.png
--------------------------------------------------------------------------------
/public/app/images/screenshots/androidphone-item-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/screenshots/androidphone-item-light.png
--------------------------------------------------------------------------------
/public/app/images/screenshots/ipad-feeds-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/screenshots/ipad-feeds-search.png
--------------------------------------------------------------------------------
/public/app/images/screenshots/ipad-homescreen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/screenshots/ipad-homescreen.jpg
--------------------------------------------------------------------------------
/public/app/images/screenshots/ipad-item-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/screenshots/ipad-item-dark.png
--------------------------------------------------------------------------------
/public/app/images/screenshots/ipad-item-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/screenshots/ipad-item-light.png
--------------------------------------------------------------------------------
/public/app/images/screenshots/ipad-item-share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/app/images/screenshots/ipad-item-share.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | collectionManager = $collectionManager;
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output): int
26 | {
27 | date_default_timezone_set('UTC');
28 |
29 | $this->collectionManager->start();
30 |
31 | return Command::SUCCESS;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Command/ElasticsearchCheckCommand.php:
--------------------------------------------------------------------------------
1 | searchManager = $searchManager;
24 | }
25 |
26 | protected function execute(InputInterface $input, OutputInterface $output): int
27 | {
28 | date_default_timezone_set('UTC');
29 |
30 | if ($this->searchManager->getEnabled()) {
31 | $path = '/_refresh';
32 | $this->searchManager->query('GET', $path);
33 |
34 | $types = ['author', 'category', 'feed', 'item'];
35 |
36 | $rows = [];
37 | foreach ($types as $type) {
38 | $path = '/'.$this->searchManager->getIndex().'_'.$type.'/_stats';
39 | $result = $this->searchManager->query('GET', $path);
40 | if (false === isset($result->error)) {
41 | $rows[] = [$type, $result['_all']['primaries']['docs']['count']];
42 | }
43 | }
44 |
45 | $table = new Table($output);
46 | $table->setHeaders(['index', 'count'])->setRows($rows);
47 | $table->render();
48 | }
49 |
50 | return Command::SUCCESS;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Command/ElasticsearchCommand.php:
--------------------------------------------------------------------------------
1 | searchManager = $searchManager;
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output): int
26 | {
27 | date_default_timezone_set('UTC');
28 |
29 | $this->searchManager->start();
30 |
31 | return Command::SUCCESS;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Command/ElasticsearchCreateCommand.php:
--------------------------------------------------------------------------------
1 | searchManager = $searchManager;
25 | }
26 |
27 | protected function execute(InputInterface $input, OutputInterface $output): int
28 | {
29 | date_default_timezone_set('UTC');
30 |
31 | $helper = new QuestionHelper();
32 |
33 | $question = new ConfirmationQuestion('Create Elasticsearch indexes? (yes) ', false);
34 |
35 | if ($helper->ask($input, $output, $question)) {
36 | $this->searchManager->create();
37 | }
38 |
39 | return Command::SUCCESS;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Command/ElasticsearchRemoveCommand.php:
--------------------------------------------------------------------------------
1 | searchManager = $searchManager;
25 | }
26 |
27 | protected function execute(InputInterface $input, OutputInterface $output): int
28 | {
29 | date_default_timezone_set('UTC');
30 |
31 | $helper = new QuestionHelper();
32 |
33 | $question = new ConfirmationQuestion('Remove Elasticsearch records? (yes) ', false);
34 |
35 | if ($helper->ask($input, $output, $question)) {
36 | $this->searchManager->remove();
37 | }
38 |
39 | return Command::SUCCESS;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Command/ElasticsearchStatusCommand.php:
--------------------------------------------------------------------------------
1 | searchManager = $searchManager;
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output): int
26 | {
27 | date_default_timezone_set('UTC');
28 |
29 | if ($this->searchManager->getEnabled()) {
30 | $path = '/';
31 | $result = $this->searchManager->query('GET', $path);
32 | if (true === is_array($result)) {
33 | if (true === isset($result['error'])) {
34 | $output->writeln(''.print_r($result, true).'');
35 | } else {
36 | $output->writeln(''.print_r($result, true).'');
37 | }
38 | } else {
39 | $output->writeln(''.$result.'');
40 | }
41 | } else {
42 | $output->writeln('Elasticsearch not enabled');
43 | }
44 |
45 | return Command::SUCCESS;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Command/GenerateVapidCommand.php:
--------------------------------------------------------------------------------
1 | setDescription('Generate VAPID keys');
19 | }
20 |
21 | protected function execute(InputInterface $input, OutputInterface $output): int
22 | {
23 | $output->writeln(''.$this->getDescription().'');
24 |
25 | $vapid = VAPID::createVapidKeys();
26 |
27 | $output->writeln('VAPID_PUBLIC_KEY=\''.$vapid['publicKey'].'\'');
28 | $output->writeln('VAPID_PRIVATE_KEY=\''.$vapid['privateKey'].'\'');
29 |
30 | return Command::SUCCESS;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Command/PhpunitCommand.php:
--------------------------------------------------------------------------------
1 | writeln('PHP version: '.phpversion().'');
19 |
20 | $output->writeln('Symfony version: '.Kernel::VERSION.'');
21 |
22 | $output->writeln('');
23 |
24 | return Command::SUCCESS;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Command/PushCommand.php:
--------------------------------------------------------------------------------
1 | pushManager = $pushManager;
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output): int
26 | {
27 | date_default_timezone_set('UTC');
28 |
29 | $this->pushManager->sendNotifications();
30 |
31 | return Command::SUCCESS;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Command/SetupCommand.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
25 | $this->kernelProjectDir = $kernelProjectDir;
26 | }
27 |
28 | protected function execute(InputInterface $input, OutputInterface $output): int
29 | {
30 | $connection = $this->entityManager->getConnection();
31 |
32 | //add actions
33 | $file = file_get_contents($this->kernelProjectDir.'/src/DataFixtures/feed.sql');
34 | if ($file) {
35 | $sql = 'SELECT COUNT(*) AS total FROM action';
36 | $count = $connection->fetchAssociative($sql);
37 |
38 | if (true === isset($count['total']) && 0 == $count['total']) {
39 | $connection->executeQuery($file);
40 | $output->writeln('Feed data inserted');
41 | } else {
42 | $output->writeln('Feed data already inserted');
43 | }
44 | }
45 |
46 | return Command::SUCCESS;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Controller/IndexController.php:
--------------------------------------------------------------------------------
1 | headers->set('X-Frame-Options', 'sameorigin');
18 | return $this->render('base.html.twig', [], $response);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Controller/LogoutController.php:
--------------------------------------------------------------------------------
1 | headers->get('Authorization')) {
25 | $payloadjwtPayloadModel = JwtHelper::getPayload(str_replace('Bearer ', '', $request->headers->get('Authorization')));
26 | if ($payloadjwtPayloadModel) {
27 | $token = $payloadjwtPayloadModel->getJwtId();
28 |
29 | if ($connection = $this->connectionManager->getOne(['type' => Connection::TYPE_LOGIN, 'token' => $token, 'member' => $this->getMember()])) {
30 | $data['entry'] = $connection->toArray();
31 | $data['entry_entity'] = 'connection';
32 |
33 | $this->connectionManager->remove($connection);
34 | }
35 | }
36 | }
37 | } catch (\Exception $e) {
38 | throw new AccessDeniedException();
39 | }
40 |
41 | return $this->jsonResponse($data);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Controller/PinboardController.php:
--------------------------------------------------------------------------------
1 | maxmindEnabled = $maxmindEnabled;
23 | }
24 |
25 | #[Route('/pinboard', name: 'index', methods: ['POST'])]
26 | public function pinboard(Request $request): JsonResponse
27 | {
28 | $data = [];
29 |
30 | $content = $this->getContent($request);
31 |
32 | if (true === isset($content['token']) && '' !== $content['token']) {
33 | $connection = $this->connectionManager->getOne(['type' => Connection::TYPE_PINBOARD, 'member' => $this->getMember()]);
34 |
35 | if ($connection) {
36 | $connection->setToken($content['token']);
37 | } else {
38 | $extraFields = DeviceDetectorHelper::asArray($request);
39 |
40 | if ($extraFields['ip'] && '127.0.0.1' != $extraFields['ip'] && true === $this->maxmindEnabled) {
41 | $data = MaxmindHelper::get($extraFields['ip']);
42 | $extraFields = array_merge($extraFields, $data);
43 | }
44 |
45 | $connection = new Connection();
46 | $connection->setMember($this->getMember());
47 | $connection->setType(Connection::TYPE_PINBOARD);
48 | $connection->setToken($content['token']);
49 | $connection->setExtraFields($extraFields);
50 | }
51 | $this->connectionManager->persist($connection);
52 |
53 | $data['entry'] = $connection->toArray();
54 | $data['entry_entity'] = 'connection';
55 | } else {
56 | $data['errors'][] = [
57 | 'status' => '400',
58 | 'source' => [
59 | 'pointer' => '/data/attributes/token',
60 | ],
61 | 'title' => 'Invalid attribute',
62 | 'detail' => 'This value should not be blank.',
63 | ];
64 |
65 | return $this->jsonResponse($data, JsonResponse::HTTP_BAD_REQUEST);
66 | }
67 |
68 | return $this->jsonResponse($data);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Controller/ProxyController.php:
--------------------------------------------------------------------------------
1 | [
28 | 'method' => 'GET',
29 | 'user_agent'=> $request->headers->get('User-Agent'),
30 | ]
31 | ];
32 |
33 | $context = stream_context_create($opts);
34 |
35 | if ($content = file_get_contents($file, false, $context)) {
36 | $contentType = (new \finfo(FILEINFO_MIME))->buffer($content);
37 |
38 | $response->setContent($content);
39 | $response->setStatusCode(Response::HTTP_OK);
40 | if ($contentType) {
41 | $response->headers->set('Content-Type', $contentType);
42 | }
43 |
44 | return $response;
45 | }
46 | } catch (\Exception $e) {
47 | throw new NotFoundHttpException($e->getMessage());
48 | }
49 | }
50 | }
51 |
52 | $response->setStatusCode(Response::HTTP_NOT_FOUND);
53 | return $response;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/DataFixtures/feed.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO `action` VALUES(1, NULL, 'read', '2016-09-28 12:25:57');
2 | INSERT INTO `action` VALUES(2, NULL, 'star', '2016-09-28 12:25:57');
3 | INSERT INTO `action` VALUES(3, NULL, 'subscribe', '2016-09-28 12:25:57');
4 | INSERT INTO `action` VALUES(4, NULL, 'read_all', '2016-09-28 12:25:57');
5 | INSERT INTO `action` VALUES(5, NULL, 'exclude', '2016-09-28 12:25:57');
6 | INSERT INTO `action` VALUES(6, NULL, 'evernote', '2016-09-28 12:25:57');
7 | INSERT INTO `action` VALUES(7, NULL, 'email', '2016-09-28 12:25:57');
8 | INSERT INTO `action` VALUES(8, NULL, 'purge', '2016-09-28 12:25:57');
9 | INSERT INTO `action` VALUES(11, NULL, 'elasticsearch', '2016-09-28 12:25:57');
10 | INSERT INTO `action` VALUES(12, NULL, 'unread', '2016-09-28 12:25:57');
11 | INSERT INTO `action` VALUES(13, NULL, 'unstar', '2016-09-28 00:00:00');
12 | INSERT INTO `action` VALUES(14, NULL, 'include', '2016-09-28 00:00:00');
13 | INSERT INTO `action` VALUES(15, NULL, 'unsubscribe', '2016-09-28 00:00:00');
14 |
15 | UPDATE `action` SET reverse = 1 WHERE id = 12;
16 | UPDATE `action` SET reverse = 2 WHERE id = 13;
17 | UPDATE `action` SET reverse = 3 WHERE id = 15;
18 | UPDATE `action` SET reverse = 5 WHERE id = 14;
19 |
20 | UPDATE `action` SET reverse = 12 WHERE id = 1;
21 | UPDATE `action` SET reverse = 13 WHERE id = 2;
22 | UPDATE `action` SET reverse = 15 WHERE id = 3;
23 | UPDATE `action` SET reverse = 14 WHERE id = 5;
24 |
--------------------------------------------------------------------------------
/src/Entity/Action.php:
--------------------------------------------------------------------------------
1 | dateCreated = new \Datetime();
31 | }
32 |
33 | public function __toString()
34 | {
35 | return $this->getTitle() ?? '';
36 | }
37 |
38 | public function getTitle(): ?string
39 | {
40 | return $this->title;
41 | }
42 |
43 | public function setTitle(?string $title): self
44 | {
45 | $this->title = $title;
46 |
47 | return $this;
48 | }
49 |
50 | public function getReverse(): ?Action
51 | {
52 | return $this->reverse;
53 | }
54 |
55 | public function setReverse(?Action $reverse): self
56 | {
57 | $this->reverse = $reverse;
58 |
59 | return $this;
60 | }
61 |
62 | /**
63 | * @return array
64 | */
65 | public function toArray(): array
66 | {
67 | return [
68 | 'id' => $this->getId(),
69 | 'title' => $this->getTitle(),
70 | ];
71 | }
72 |
73 | /**
74 | * @return array
75 | */
76 | public function getJsonApiData(): array
77 | {
78 | return [
79 | 'id' => strval($this->getId()),
80 | 'type' => 'action',
81 | 'attributes' => [
82 | 'title' => $this->getTitle(),
83 | 'date_created' => $this->getDateCreated() ? $this->getDateCreated()->format('Y-m-d H:i:s') : null,
84 | ],
85 | ];
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Entity/ActionAuthor.php:
--------------------------------------------------------------------------------
1 | dateCreated = new \Datetime();
31 | }
32 |
33 | public function getAuthor(): ?Author
34 | {
35 | return $this->author;
36 | }
37 |
38 | public function setAuthor(?Author $author): self
39 | {
40 | $this->author = $author;
41 |
42 | return $this;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Entity/ActionCategory.php:
--------------------------------------------------------------------------------
1 | dateCreated = new \Datetime();
31 | }
32 |
33 | public function getCategory(): ?Category
34 | {
35 | return $this->category;
36 | }
37 |
38 | public function setCategory(?Category $category): self
39 | {
40 | $this->category = $category;
41 |
42 | return $this;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Entity/ActionFeed.php:
--------------------------------------------------------------------------------
1 | dateCreated = new \Datetime();
31 | }
32 |
33 | public function getFeed(): ?Feed
34 | {
35 | return $this->feed;
36 | }
37 |
38 | public function setFeed(?Feed $feed): self
39 | {
40 | $this->feed = $feed;
41 |
42 | return $this;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Entity/ActionItem.php:
--------------------------------------------------------------------------------
1 | dateCreated = new \Datetime();
31 | }
32 |
33 | public function getItem(): ?Item
34 | {
35 | return $this->item;
36 | }
37 |
38 | public function setItem(?Item $item): self
39 | {
40 | $this->item = $item;
41 |
42 | return $this;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Entity/ActionTrait.php:
--------------------------------------------------------------------------------
1 | action;
20 | }
21 |
22 | public function setAction(?Action $action): self
23 | {
24 | $this->action = $action;
25 |
26 | return $this;
27 | }
28 |
29 | public function getMember(): ?Member
30 | {
31 | return $this->member;
32 | }
33 |
34 | public function setMember(?Member $member): self
35 | {
36 | $this->member = $member;
37 |
38 | return $this;
39 | }
40 |
41 | /**
42 | * @return array
43 | */
44 | public function toArray(): array
45 | {
46 | return [
47 | 'action' => $this->getAction() ? $this->getAction()->toArray() : null,
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Entity/DateCreatedTrait.php:
--------------------------------------------------------------------------------
1 | dateCreated;
15 | }
16 |
17 | public function setDateCreated(?\DateTimeInterface $dateCreated): self
18 | {
19 | $this->dateCreated = $dateCreated;
20 |
21 | return $this;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Entity/ExtraFieldsTrait.php:
--------------------------------------------------------------------------------
1 | $extraFields
12 | */
13 | #[ORM\Column(name: "extra_fields", type: "json", nullable: true)]
14 | private ?array $extraFields = [];
15 |
16 | /**
17 | * @return array
18 | */
19 | public function getExtraFields(): ?array
20 | {
21 | return $this->extraFields ?? [];
22 | }
23 |
24 | /**
25 | * @param array|null $extraFields
26 | */
27 | public function setExtraFields(?array $extraFields): self
28 | {
29 | $this->extraFields = $extraFields;
30 | return $this;
31 | }
32 |
33 | public function getExtraField(string $path, ?string $type = null): mixed
34 | {
35 | return ExtraFieldsHelper::getPath($path, $this->extraFields, $type);
36 | }
37 |
38 | public function setExtraField(string $path, mixed $value): self
39 | {
40 | $this->extraFields = $this->extraFields ?? [];
41 | ExtraFieldsHelper::setPath($path, $value, $this->extraFields);
42 |
43 | return $this;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Entity/FeedCategory.php:
--------------------------------------------------------------------------------
1 | category;
31 | }
32 |
33 | public function setCategory(?Category $category): self
34 | {
35 | $this->category = $category;
36 |
37 | return $this;
38 | }
39 |
40 | public function getFeed(): ?Feed
41 | {
42 | return $this->feed;
43 | }
44 |
45 | public function setFeed(?Feed $feed): self
46 | {
47 | $this->feed = $feed;
48 |
49 | return $this;
50 | }
51 |
52 |
53 | /**
54 | * @return array
55 | */
56 | public function toArray(): array
57 | {
58 | return [
59 | 'id' => $this->getCategory() ? $this->getCategory()->getId() : null,
60 | 'title' => $this->getCategory() ? $this->getCategory()->getTitle() : null,
61 | ];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Entity/IdTrait.php:
--------------------------------------------------------------------------------
1 | true]), ORM\Id, ORM\GeneratedValue(strategy: "IDENTITY")]
10 | private ?int $id = null;
11 |
12 | public function getId(): ?int
13 | {
14 | return $this->id;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Entity/ItemCategory.php:
--------------------------------------------------------------------------------
1 | category;
31 | }
32 |
33 | public function setCategory(?Category $category): self
34 | {
35 | $this->category = $category;
36 |
37 | return $this;
38 | }
39 |
40 | public function getItem(): ?Item
41 | {
42 | return $this->item;
43 | }
44 |
45 | public function setItem(?Item $item): self
46 | {
47 | $this->item = $item;
48 |
49 | return $this;
50 | }
51 |
52 | /**
53 | * @return array
54 | */
55 | public function toArray(): array
56 | {
57 | return [
58 | 'id' => $this->getCategory() ? $this->getCategory()->getId() : null,
59 | 'title' => $this->getCategory() ? $this->getCategory()->getTitle() : null,
60 | ];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Event/ActionAuthorEvent.php:
--------------------------------------------------------------------------------
1 | actionAuthor = $actionAuthor;
21 | }
22 |
23 | public function getActionAuthor(): ActionAuthor
24 | {
25 | return $this->actionAuthor;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/ActionCategoryEvent.php:
--------------------------------------------------------------------------------
1 | actionCategory = $actionCategory;
21 | }
22 |
23 | public function getActionCategory(): ActionCategory
24 | {
25 | return $this->actionCategory;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/ActionEvent.php:
--------------------------------------------------------------------------------
1 | action = $action;
21 | }
22 |
23 | public function getAction(): Action
24 | {
25 | return $this->action;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/ActionFeedEvent.php:
--------------------------------------------------------------------------------
1 | actionFeed = $actionFeed;
21 | }
22 |
23 | public function getActionFeed(): ActionFeed
24 | {
25 | return $this->actionFeed;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/ActionItemEvent.php:
--------------------------------------------------------------------------------
1 | actionItem = $actionItem;
21 | }
22 |
23 | public function getActionItem(): ActionItem
24 | {
25 | return $this->actionItem;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/AuthorEvent.php:
--------------------------------------------------------------------------------
1 | author = $author;
21 | }
22 |
23 | public function getAuthor(): Author
24 | {
25 | return $this->author;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/CategoryEvent.php:
--------------------------------------------------------------------------------
1 | category = $category;
21 | }
22 |
23 | public function getCategory(): Category
24 | {
25 | return $this->category;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/CollectionEvent.php:
--------------------------------------------------------------------------------
1 | collection = $collection;
21 | }
22 |
23 | public function getCollection(): Collection
24 | {
25 | return $this->collection;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/CollectionFeedEvent.php:
--------------------------------------------------------------------------------
1 | collectionFeed = $collectionFeed;
21 | }
22 |
23 | public function getCollectionFeed(): CollectionFeed
24 | {
25 | return $this->collectionFeed;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/ConnectionEvent.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
21 | }
22 |
23 | public function getConnection(): Connection
24 | {
25 | return $this->connection;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/EnclosureEvent.php:
--------------------------------------------------------------------------------
1 | enclosure = $enclosure;
21 | }
22 |
23 | public function getEnclosure(): Enclosure
24 | {
25 | return $this->enclosure;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/FeedCategoryEvent.php:
--------------------------------------------------------------------------------
1 | feedCategory = $feedCategory;
21 | }
22 |
23 | public function getFeedCategory(): FeedCategory
24 | {
25 | return $this->feedCategory;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/FeedEvent.php:
--------------------------------------------------------------------------------
1 | feed = $feed;
21 | }
22 |
23 | public function getFeed(): Feed
24 | {
25 | return $this->feed;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/ItemCategoryEvent.php:
--------------------------------------------------------------------------------
1 | itemCategory = $itemCategory;
21 | }
22 |
23 | public function getItemCategory(): ItemCategory
24 | {
25 | return $this->itemCategory;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/ItemEvent.php:
--------------------------------------------------------------------------------
1 | item = $item;
21 | }
22 |
23 | public function getItem(): Item
24 | {
25 | return $this->item;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Event/MemberEvent.php:
--------------------------------------------------------------------------------
1 | member = $member;
21 | }
22 |
23 | public function getMember(): Member
24 | {
25 | return $this->member;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/EventSubscriber/PinboardSubscriber.php:
--------------------------------------------------------------------------------
1 | connectionManager = $connectionManager;
20 | }
21 |
22 | public static function getSubscribedEvents(): array
23 | {
24 | return [
25 | ActionItemEvent::CREATED => 'add',
26 | ActionItemEvent::DELETED => 'delete',
27 | ];
28 | }
29 |
30 | public function add(ActionItemEvent $actionItemEvent): void
31 | {
32 | $actionItem = $actionItemEvent->getActionItem();
33 |
34 | if ($actionItem->getAction() && $actionItem->getAction()->getTitle() == 'star') {
35 | $this->query('add', $actionItem);
36 | }
37 | }
38 |
39 | public function delete(ActionItemEvent $actionItemEvent): void
40 | {
41 | $actionItem = $actionItemEvent->getActionItem();
42 |
43 | if ($actionItem->getAction() && $actionItem->getAction()->getTitle() == 'star') {
44 | $this->query('delete', $actionItem);
45 | }
46 | }
47 |
48 | private function query(string $method, ActionItem $actionItem): void
49 | {
50 | $member = $actionItem->getMember();
51 |
52 | if ($connection = $this->connectionManager->getOne(['type' => Connection::TYPE_PINBOARD, 'member' => $member])) {
53 | $item = $actionItem->getItem();
54 |
55 | if ($item) {
56 | $url = 'https://api.pinboard.in/v1/posts/'.$method;
57 |
58 | $fields = [
59 | 'auth_token' => $connection->getToken(),
60 | 'url' => $item->getLink(),
61 | 'description' => $item->getTitle(),
62 | 'replace' => 'yes',
63 | ];
64 |
65 | $ci = curl_init();
66 | curl_setopt($ci, CURLOPT_URL, $url.'?'.http_build_query($fields));
67 | curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'GET');
68 | curl_setopt($ci, CURLOPT_RETURNTRANSFER, 1);
69 | curl_exec($ci);
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/EventSubscriber/SearchSubscriber.php:
--------------------------------------------------------------------------------
1 | searchManager = $searchManager;
24 | $this->itemManager = $itemManager;
25 | }
26 |
27 | public static function getSubscribedEvents(): array
28 | {
29 | return [
30 | AuthorEvent::DELETED => 'removeAuthor',
31 | CategoryEvent::DELETED => 'removeCategory',
32 | FeedEvent::DELETED => 'removeFeed',
33 | ItemEvent::DELETED => 'removeItem',
34 | ];
35 | }
36 |
37 | public function removeAuthor(AuthorEvent $authorEvent): void
38 | {
39 | $path = '/'.$this->searchManager->getIndex().'_author/doc/'.$authorEvent->getAuthor()->getId();
40 | $this->searchManager->query('DELETE', $path);
41 | }
42 |
43 | public function removeCategory(CategoryEvent $categoryEvent): void
44 | {
45 | $path = '/'.$this->searchManager->getIndex().'_category/doc/'.$categoryEvent->getCategory()->getId();
46 | $this->searchManager->query('DELETE', $path);
47 | }
48 |
49 | public function removeFeed(FeedEvent $feedEvent): void
50 | {
51 | $parameters = [];
52 | $parameters['feed'] = (int) $feedEvent->getFeed()->getId();
53 | $parameters['sortField'] = 'itm.id';
54 | $parameters['sortDirection'] = 'ASC';
55 | foreach ($this->itemManager->getList($parameters)->getResult() as $item) {
56 | $path = '/'.$this->searchManager->getIndex().'_item/doc/'.$item['id'];
57 | $this->searchManager->query('DELETE', $path);
58 | }
59 |
60 | $path = '/'.$this->searchManager->getIndex().'_feed/doc/'.$feedEvent->getFeed()->getId();
61 | $this->searchManager->query('DELETE', $path);
62 | }
63 |
64 | public function removeItem(ItemEvent $itemEvent): void
65 | {
66 | $path = '/'.$this->searchManager->getIndex().'_item/doc/'.$itemEvent->getItem()->getId();
67 | $this->searchManager->query('DELETE', $path);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/EventSubscriber/SubscribeSubscriber.php:
--------------------------------------------------------------------------------
1 | memberManager = $memberManager;
18 | }
19 |
20 | public static function getSubscribedEvents(): array
21 | {
22 | return [
23 | ActionFeedEvent::CREATED => 'unread',
24 | ActionFeedEvent::UPDATED => 'unread',
25 | ActionFeedEvent::DELETED => 'unread',
26 | ];
27 | }
28 |
29 | public function unread(ActionFeedEvent $actionFeedEvent): void
30 | {
31 | $actionFeed = $actionFeedEvent->getActionFeed();
32 |
33 | if ($actionFeed->getAction() && $actionFeed->getAction()->getTitle() == 'subscribe') {
34 | if ($member = $actionFeed->getMember()) {
35 | $this->memberManager->syncUnread($member->getid());
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Form/Type/AuthorType.php:
--------------------------------------------------------------------------------
1 | add('title', TextType::class, [
19 | 'constraints' => [
20 | new NotBlank(),
21 | ],
22 | ]);
23 | }
24 |
25 | public function configureOptions(OptionsResolver $resolver): void
26 | {
27 | $resolver->setDefaults([
28 | 'data_class' => Author::class,
29 | 'csrf_protection' => false,
30 | ]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Form/Type/CategoryType.php:
--------------------------------------------------------------------------------
1 | add('title', TextType::class, [
19 | 'constraints' => [
20 | new NotBlank(),
21 | ],
22 | ]);
23 | }
24 |
25 | public function configureOptions(OptionsResolver $resolver): void
26 | {
27 | $resolver->setDefaults([
28 | 'data_class' => Category::class,
29 | 'csrf_protection' => false,
30 | ]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Form/Type/FeedType.php:
--------------------------------------------------------------------------------
1 | add('title', TextType::class, [
20 | 'constraints' => [
21 | new NotBlank(),
22 | ],
23 | ]);
24 |
25 | $builder->add('link', TextType::class, [
26 | 'constraints' => [
27 | new NotBlank(),
28 | ],
29 | ]);
30 |
31 | $builder->add('website', TextType::class);
32 |
33 | $builder->add('language', TextType::class);
34 |
35 | $builder->add('description', TextareaType::class);
36 | }
37 |
38 | public function configureOptions(OptionsResolver $resolver): void
39 | {
40 | $resolver->setDefaults([
41 | 'data_class' => Feed::class,
42 | 'csrf_protection' => false,
43 | ]);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Form/Type/ImportOpmlType.php:
--------------------------------------------------------------------------------
1 | add('file', FileType::class);
18 | }
19 |
20 | public function configureOptions(OptionsResolver $resolver): void
21 | {
22 | $resolver->setDefaults([
23 | 'data_class' => ImportOpmlModel::class,
24 | 'csrf_protection' => false,
25 | ]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Form/Type/LoginType.php:
--------------------------------------------------------------------------------
1 | add('email', EmailType::class, [
21 | 'constraints' => [
22 | new NotBlank(),
23 | new Email(),
24 | ],
25 | ]);
26 |
27 | $builder->add('password', PasswordType::class, [
28 | 'constraints' => [
29 | new NotBlank(),
30 | ],
31 | ]);
32 | }
33 |
34 | public function configureOptions(OptionsResolver $resolver): void
35 | {
36 | $resolver->setDefaults([
37 | 'data_class' => LoginModel::class,
38 | 'csrf_protection' => false,
39 | ]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Form/Type/MemberPasskeyType.php:
--------------------------------------------------------------------------------
1 | add('title', TextType::class, [
19 | 'constraints' => [
20 | new NotBlank(),
21 | ],
22 | ]);
23 | }
24 |
25 | public function configureOptions(OptionsResolver $resolver): void
26 | {
27 | $resolver->setDefaults([
28 | 'data_class' => MemberPasskey::class,
29 | 'csrf_protection' => false,
30 | ]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Form/Type/MemberType.php:
--------------------------------------------------------------------------------
1 | add('email', EmailType::class, [
22 | 'constraints' => [
23 | new NotBlank(),
24 | new Email(),
25 | ],
26 | ]);
27 |
28 | if (Request::METHOD_POST === $options['request_method']) {
29 | $builder->add('plainPassword', PasswordType::class, [
30 | 'constraints' => [
31 | new NotBlank(),
32 | ],
33 | ]);
34 | }
35 |
36 | if (Request::METHOD_PUT === $options['request_method']) {
37 | $builder->add('plainPassword', PasswordType::class);
38 | }
39 | }
40 |
41 | public function configureOptions(OptionsResolver $resolver): void
42 | {
43 | $resolver->setDefaults([
44 | 'data_class' => Member::class,
45 | 'csrf_protection' => false,
46 | 'request_method' => null,
47 | ]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Form/Type/ProfileType.php:
--------------------------------------------------------------------------------
1 | add('email', EmailType::class, [
21 | 'constraints' => [
22 | new NotBlank(),
23 | new Email(),
24 | ],
25 | ]);
26 |
27 | $builder->add('password', PasswordType::class);
28 |
29 | $builder->add('passwordConfirm', PasswordType::class);
30 | }
31 |
32 | public function configureOptions(OptionsResolver $resolver): void
33 | {
34 | $resolver->setDefaults([
35 | 'data_class' => ProfileModel::class,
36 | 'csrf_protection' => false,
37 | ]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Form/Type/PushType.php:
--------------------------------------------------------------------------------
1 | add('endpoint', TextType::class, [
20 | 'property_path' => 'token',
21 | 'constraints' => [
22 | new NotBlank(),
23 | ],
24 | ]);
25 |
26 | $builder->add('publicKey', TextType::class, [
27 | 'property_path' => 'extra_fields[public_key]',
28 | 'constraints' => [
29 | new NotBlank(),
30 | ],
31 | ]);
32 |
33 | $builder->add('authenticationSecret', PasswordType::class, [
34 | 'property_path' => 'extra_fields[authentication_secret]',
35 | 'constraints' => [
36 | new NotBlank(),
37 | ],
38 | ]);
39 |
40 | $builder->add('contentEncoding', TextType::class, [
41 | 'property_path' => 'extra_fields[content_encoding]',
42 | 'constraints' => [
43 | new NotBlank(),
44 | ],
45 | ]);
46 | }
47 |
48 | public function configureOptions(OptionsResolver $resolver): void
49 | {
50 | $resolver->setDefaults([
51 | 'data_class' => Connection::class,
52 | 'csrf_protection' => false,
53 | ]);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Helper/DeviceDetectorHelper.php:
--------------------------------------------------------------------------------
1 | getClientIp()) {
16 | $deviceDetector->setIp($clientIp);
17 | if ($hostbyaddr = gethostbyaddr($clientIp)) {
18 | $deviceDetector->setHostname($hostbyaddr);
19 | }
20 | }
21 |
22 | if ($userAgent = $request->headers->get('User-Agent')) {
23 | $dd = new DeviceDetector($userAgent);
24 | $dd->skipBotDetection();
25 | $dd->parse();
26 |
27 | $client = $dd->getClient();
28 | $os = $dd->getOs();
29 | $device = $dd->getDeviceName();
30 | $brand = $dd->getBrandName();
31 | $model = $dd->getModel();
32 |
33 | if (true === is_array($client) && true === array_key_exists('name', $client) && true === array_key_exists('version', $client)) {
34 | $deviceDetector->setClient($client['name'].' '.$client['version']);
35 | }
36 | if (true === is_array($os) && true === array_key_exists('name', $os) && true === array_key_exists('version', $os)) {
37 | $deviceDetector->setOs($os['name'].' '.$os['version']);
38 | }
39 | $deviceDetector->setDevice($device);
40 | $deviceDetector->setBrand($brand);
41 | $deviceDetector->setModel($model);
42 | }
43 |
44 | return $deviceDetector;
45 | }
46 |
47 | /**
48 | * @return array
49 | */
50 | public static function asArray(Request $request): array
51 | {
52 | $deviceDetector = DeviceDetectorHelper::get($request);
53 |
54 | $extraFields = [];
55 | $extraFields['ip'] = $deviceDetector->getIp();
56 | $extraFields['hostname'] = $deviceDetector->getHostname();
57 | $extraFields['client'] = $deviceDetector->getClient();
58 | $extraFields['os'] = $deviceDetector->getOs();
59 | $extraFields['device'] = $deviceDetector->getDevice();
60 | $extraFields['brand'] = $deviceDetector->getBrand();
61 | $extraFields['model'] = $deviceDetector->getModel();
62 |
63 | return $extraFields;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Helper/ExtraFieldsHelper.php:
--------------------------------------------------------------------------------
1 | |null $fields
16 | */
17 | public static function getPath(string $path, ?array $fields = [], ?string $type = null): mixed
18 | {
19 | $elements = explode('.', $path);
20 |
21 | if (1 === count($elements)) {
22 | if ($fields && array_key_exists($path, $fields)) {
23 | return static::resolve($fields[$path], $type);
24 | }
25 | return null;
26 | } else {
27 | foreach ($elements as $element) {
28 | $fields = &$fields[$element];
29 | }
30 |
31 | return static::resolve($fields, $type);
32 | }
33 | }
34 |
35 | /**
36 | * @param array|null $fields
37 | */
38 | public static function setPath(string $path, mixed $value, ?array &$fields = []): void
39 | {
40 | $elements = explode('.', $path);
41 |
42 | foreach ($elements as &$element) {
43 | $element = '['.$element.']';
44 | }
45 |
46 | $path = implode('', $elements);
47 |
48 | if ($value instanceof \DateTime) {
49 | $value = (array) $value;
50 | }
51 |
52 | $propertyAccessor = PropertyAccess::createPropertyAccessor();
53 | $propertyAccessor->setValue($fields, $path, $value);
54 | }
55 |
56 | private static function resolve(mixed $value, ?string $type = null): mixed
57 | {
58 | switch ($type) {
59 | case null:
60 | return $value;
61 |
62 | case self::TYPE_BOOL:
63 | return (bool) $value;
64 |
65 | case self::TYPE_DATETIME:
66 | if (true === is_array($value)) {
67 | return new \DateTime($value['date'], new \DateTimeZone($value['timezone']));
68 | } elseif ($value instanceof \DateTime) {
69 | return $value;
70 | } elseif (true === is_string($value)) {
71 | return new \DateTime($value);
72 | }
73 |
74 | // no break
75 | case self::TYPE_FLOAT:
76 | return floatval($value);
77 |
78 | case self::TYPE_STRING:
79 | return strval($value);
80 |
81 | default:
82 | return null;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Helper/MaxmindHelper.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | public static function get(string $ip): array
13 | {
14 | $data = [];
15 |
16 | $file = __DIR__.'/../../maxmind/GeoLite2-City/GeoLite2-City.mmdb';
17 |
18 | if (file_exists($file)) {
19 | $reader = new Reader($file);
20 | $record = $reader->city($ip);
21 | $reader->close();
22 |
23 | if ($country = $record->country->name) {
24 | $data['country'] = $country;
25 | }
26 |
27 | if ($subdivision = $record->mostSpecificSubdivision->name) {
28 | $data['subdivision'] = $subdivision;
29 | }
30 |
31 | if ($city = $record->city->name) {
32 | $data['city'] = $city;
33 | }
34 |
35 | if ($latitude = $record->location->latitude) {
36 | $data['latitude'] = $latitude;
37 | }
38 |
39 | if ($longitude = $record->location->longitude) {
40 | $data['longitude'] = $longitude;
41 | }
42 | }
43 |
44 | return $data;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Kernel.php:
--------------------------------------------------------------------------------
1 | eventDispatcher = $eventDispatcher;
18 | }
19 |
20 | public function clearCache(): void
21 | {
22 | if (function_exists('apcu_clear_cache')) {
23 | apcu_clear_cache();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Manager/ActionManager.php:
--------------------------------------------------------------------------------
1 | actionRepository = $actionRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?Action
25 | {
26 | return $this->actionRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->actionRepository->getList($parameters);
35 | }
36 |
37 | public function persist(Action $action): void
38 | {
39 | if ($action->getId() === null) {
40 | $eventName = ActionEvent::CREATED;
41 | } else {
42 | $eventName = ActionEvent::UPDATED;
43 | }
44 |
45 | $this->actionRepository->persist($action);
46 |
47 | $event = new ActionEvent($action);
48 | $this->eventDispatcher->dispatch($event, $eventName);
49 |
50 | $this->clearCache();
51 | }
52 |
53 | public function remove(Action $action): void
54 | {
55 | $event = new ActionEvent($action);
56 | $this->eventDispatcher->dispatch($event, ActionEvent::DELETED);
57 |
58 | $this->actionRepository->remove($action);
59 |
60 | $this->clearCache();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Manager/AuthorManager.php:
--------------------------------------------------------------------------------
1 | authorRepository = $authorRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?Author
25 | {
26 | return $this->authorRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->authorRepository->getList($parameters);
35 | }
36 |
37 | public function persist(Author $author): void
38 | {
39 | if ($author->getId() === null) {
40 | $eventName = AuthorEvent::CREATED;
41 | } else {
42 | $eventName = AuthorEvent::UPDATED;
43 | }
44 |
45 | $this->authorRepository->persist($author);
46 |
47 | $event = new AuthorEvent($author);
48 | $this->eventDispatcher->dispatch($event, $eventName);
49 |
50 | $this->clearCache();
51 | }
52 |
53 | public function remove(Author $author): void
54 | {
55 | $event = new AuthorEvent($author);
56 | $this->eventDispatcher->dispatch($event, AuthorEvent::DELETED);
57 |
58 | $this->authorRepository->remove($author);
59 |
60 | $this->clearCache();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Manager/CategoryManager.php:
--------------------------------------------------------------------------------
1 | categoryRepository = $categoryRepository;
25 | $this->itemCategoryManager = $itemCategoryManager;
26 | $this->feedCategoryManager = $feedCategoryManager;
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getOne(array $parameters = []): ?Category
33 | {
34 | return $this->categoryRepository->getOne($parameters);
35 | }
36 |
37 | /**
38 | * @param array $parameters
39 | */
40 | public function getList(array $parameters = []): mixed
41 | {
42 | return $this->categoryRepository->getList($parameters);
43 | }
44 |
45 | public function persist(Category $category): void
46 | {
47 | if ($category->getId() === null) {
48 | $eventName = CategoryEvent::CREATED;
49 | } else {
50 | $eventName = CategoryEvent::UPDATED;
51 | }
52 |
53 | $this->categoryRepository->persist($category);
54 |
55 | $event = new CategoryEvent($category);
56 | $this->eventDispatcher->dispatch($event, $eventName);
57 |
58 | $this->clearCache();
59 | }
60 |
61 | public function remove(Category $category): void
62 | {
63 | $event = new CategoryEvent($category);
64 | $this->eventDispatcher->dispatch($event, CategoryEvent::DELETED);
65 |
66 | $this->categoryRepository->remove($category);
67 |
68 | $this->clearCache();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Manager/CollectionFeedManager.php:
--------------------------------------------------------------------------------
1 | collectionFeedRepository = $collectionFeedRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?CollectionFeed
25 | {
26 | return $this->collectionFeedRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->collectionFeedRepository->getList($parameters);
35 | }
36 |
37 | public function persist(CollectionFeed $collectionFeed): void
38 | {
39 | if ($collectionFeed->getId() === null) {
40 | $eventName = CollectionFeedEvent::CREATED;
41 | } else {
42 | $eventName = CollectionFeedEvent::UPDATED;
43 | }
44 |
45 | $this->collectionFeedRepository->persist($collectionFeed);
46 |
47 | $event = new CollectionFeedEvent($collectionFeed);
48 | $this->eventDispatcher->dispatch($event, $eventName);
49 |
50 | $this->clearCache();
51 | }
52 |
53 | public function remove(CollectionFeed $collectionFeed): void
54 | {
55 | $event = new CollectionFeedEvent($collectionFeed);
56 | $this->eventDispatcher->dispatch($event, CollectionFeedEvent::DELETED);
57 |
58 | $this->collectionFeedRepository->remove($collectionFeed);
59 |
60 | $this->clearCache();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Manager/ConnectionManager.php:
--------------------------------------------------------------------------------
1 | connectionRepository = $connectionRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?Connection
25 | {
26 | return $this->connectionRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->connectionRepository->getList($parameters);
35 | }
36 |
37 | public function persist(Connection $connection): void
38 | {
39 | if ($connection->getId() === null) {
40 | $eventName = ConnectionEvent::CREATED;
41 | } else {
42 | $eventName = ConnectionEvent::UPDATED;
43 | }
44 | $connection->setDateModified(new \Datetime());
45 |
46 | $this->connectionRepository->persist($connection);
47 |
48 | $event = new ConnectionEvent($connection);
49 | $this->eventDispatcher->dispatch($event, $eventName);
50 |
51 | $this->clearCache();
52 | }
53 |
54 | public function remove(Connection $connection): void
55 | {
56 | $event = new ConnectionEvent($connection);
57 | $this->eventDispatcher->dispatch($event, ConnectionEvent::DELETED);
58 |
59 | $this->connectionRepository->remove($connection);
60 |
61 | $this->clearCache();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Manager/EnclosureManager.php:
--------------------------------------------------------------------------------
1 | enclosureRepository = $enclosureRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?Enclosure
25 | {
26 | return $this->enclosureRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->enclosureRepository->getList($parameters);
35 | }
36 |
37 | public function persist(Enclosure $enclosure): void
38 | {
39 | if ($enclosure->getId() === null) {
40 | $eventName = EnclosureEvent::CREATED;
41 | } else {
42 | $eventName = EnclosureEvent::UPDATED;
43 | }
44 |
45 | $this->enclosureRepository->persist($enclosure);
46 |
47 | $event = new EnclosureEvent($enclosure);
48 | $this->eventDispatcher->dispatch($event, $eventName);
49 |
50 | $this->clearCache();
51 | }
52 |
53 | public function remove(Enclosure $enclosure): void
54 | {
55 | $event = new EnclosureEvent($enclosure);
56 | $this->eventDispatcher->dispatch($event, EnclosureEvent::DELETED);
57 |
58 | $this->enclosureRepository->remove($enclosure);
59 |
60 | $this->clearCache();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Manager/FeedCategoryManager.php:
--------------------------------------------------------------------------------
1 | feedCategoryRepository = $feedCategoryRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?FeedCategory
25 | {
26 | return $this->feedCategoryRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->feedCategoryRepository->getList($parameters);
35 | }
36 |
37 | public function persist(FeedCategory $feedCategory): void
38 | {
39 | if ($feedCategory->getId() === null) {
40 | $eventName = FeedCategoryEvent::CREATED;
41 | } else {
42 | $eventName = FeedCategoryEvent::UPDATED;
43 | }
44 |
45 | $this->feedCategoryRepository->persist($feedCategory);
46 |
47 | $event = new FeedCategoryEvent($feedCategory);
48 | $this->eventDispatcher->dispatch($event, $eventName);
49 |
50 | $this->clearCache();
51 | }
52 |
53 | public function remove(FeedCategory $feedCategory): void
54 | {
55 | $event = new FeedCategoryEvent($feedCategory);
56 | $this->eventDispatcher->dispatch($event, FeedCategoryEvent::DELETED);
57 |
58 | $this->feedCategoryRepository->remove($feedCategory);
59 |
60 | $this->clearCache();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Manager/ItemCategoryManager.php:
--------------------------------------------------------------------------------
1 | itemCategoryRepository = $itemCategoryRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?ItemCategory
25 | {
26 | return $this->itemCategoryRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->itemCategoryRepository->getList($parameters);
35 | }
36 |
37 | public function persist(ItemCategory $itemCategory): void
38 | {
39 | if ($itemCategory->getId() === null) {
40 | $eventName = ItemCategoryEvent::CREATED;
41 | } else {
42 | $eventName = ItemCategoryEvent::UPDATED;
43 | }
44 |
45 | $this->itemCategoryRepository->persist($itemCategory);
46 |
47 | $event = new ItemCategoryEvent($itemCategory);
48 | $this->eventDispatcher->dispatch($event, $eventName);
49 |
50 | $this->clearCache();
51 | }
52 |
53 | public function remove(ItemCategory $itemCategory): void
54 | {
55 | $event = new ItemCategoryEvent($itemCategory);
56 | $this->eventDispatcher->dispatch($event, ItemCategoryEvent::DELETED);
57 |
58 | $this->itemCategoryRepository->remove($itemCategory);
59 |
60 | $this->clearCache();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Manager/MemberManager.php:
--------------------------------------------------------------------------------
1 | memberRepository = $memberRepository;
19 | }
20 |
21 | /**
22 | * @param array $parameters
23 | */
24 | public function getOne(array $parameters = []): ?Member
25 | {
26 | return $this->memberRepository->getOne($parameters);
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | public function getList(array $parameters = []): mixed
33 | {
34 | return $this->memberRepository->getList($parameters);
35 | }
36 |
37 | public function persist(Member $member): void
38 | {
39 | if ($member->getId() === null) {
40 | $eventName = MemberEvent::CREATED;
41 | } else {
42 | $eventName = MemberEvent::UPDATED;
43 | }
44 | $member->setDateModified(new \Datetime());
45 |
46 | $this->memberRepository->persist($member);
47 |
48 | $event = new MemberEvent($member);
49 | $this->eventDispatcher->dispatch($event, $eventName);
50 |
51 | $this->clearCache();
52 | }
53 |
54 | public function remove(Member $member): void
55 | {
56 | $event = new MemberEvent($member);
57 | $this->eventDispatcher->dispatch($event, MemberEvent::DELETED);
58 |
59 | $this->memberRepository->remove($member);
60 |
61 | $this->clearCache();
62 | }
63 |
64 | public function syncUnread(?int $member_id): void
65 | {
66 | if ($member_id) {
67 | $this->memberRepository->syncUnread($member_id);
68 | }
69 | }
70 |
71 | public function countUnread(int $member_id): int
72 | {
73 | return $this->memberRepository->countUnread($member_id);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Manager/MemberPasskeyManager.php:
--------------------------------------------------------------------------------
1 | memberPasskeyRepository = $memberPasskeyRepository;
18 | }
19 |
20 | /**
21 | * @param array $parameters
22 | */
23 | public function getOne(array $parameters = []): ?MemberPasskey
24 | {
25 | return $this->memberPasskeyRepository->getOne($parameters);
26 | }
27 |
28 | /**
29 | * @param array $parameters
30 | */
31 | public function getList(array $parameters = []): mixed
32 | {
33 | return $this->memberPasskeyRepository->getList($parameters);
34 | }
35 |
36 | public function persist(MemberPasskey $memberPasskey): void
37 | {
38 | $this->memberPasskeyRepository->persist($memberPasskey);
39 |
40 | $this->clearCache();
41 | }
42 |
43 | public function remove(MemberPasskey $memberPasskey): void
44 | {
45 | $this->memberPasskeyRepository->remove($memberPasskey);
46 |
47 | $this->clearCache();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Model/DeviceDetectorModel.php:
--------------------------------------------------------------------------------
1 | ip;
12 | }
13 |
14 | public function setIp(?string $ip): self
15 | {
16 | $this->ip = $ip;
17 | return $this;
18 | }
19 |
20 | private ?string $hostname = null;
21 |
22 | public function getHostname(): ?string
23 | {
24 | return $this->hostname;
25 | }
26 |
27 | public function setHostname(?string $hostname): self
28 | {
29 | $this->hostname = $hostname;
30 | return $this;
31 | }
32 |
33 | private ?string $client = null;
34 |
35 | public function getClient(): ?string
36 | {
37 | return $this->client;
38 | }
39 |
40 | public function setClient(?string $client): self
41 | {
42 | $this->client = $client;
43 | return $this;
44 | }
45 |
46 | private ?string $os = null;
47 |
48 | public function getOs(): ?string
49 | {
50 | return $this->os;
51 | }
52 |
53 | public function setOs(?string $os): self
54 | {
55 | $this->os = $os;
56 | return $this;
57 | }
58 |
59 | private ?string $device = null;
60 |
61 | public function getDevice(): ?string
62 | {
63 | return $this->device;
64 | }
65 |
66 | public function setDevice(?string $device): self
67 | {
68 | $this->device = $device;
69 | return $this;
70 | }
71 |
72 | private ?string $brand = null;
73 |
74 | public function getBrand(): ?string
75 | {
76 | return $this->brand;
77 | }
78 |
79 | public function setBrand(?string $brand): self
80 | {
81 | $this->brand = $brand;
82 | return $this;
83 | }
84 |
85 | private ?string $model = null;
86 |
87 | public function getModel(): ?string
88 | {
89 | return $this->model;
90 | }
91 |
92 | public function setModel(?string $model): self
93 | {
94 | $this->model = $model;
95 | return $this;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Model/ImportOpmlModel.php:
--------------------------------------------------------------------------------
1 | file;
14 | }
15 |
16 | public function setFile(?string $file): self
17 | {
18 | $this->file = $file;
19 |
20 | return $this;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Model/LoginModel.php:
--------------------------------------------------------------------------------
1 | email;
16 | }
17 |
18 | public function setEmail(?string $email): self
19 | {
20 | $this->email = $email;
21 |
22 | return $this;
23 | }
24 |
25 | public function getPassword(): ?string
26 | {
27 | return $this->password;
28 | }
29 |
30 | public function setPassword(?string $password): self
31 | {
32 | $this->password = $password;
33 |
34 | return $this;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Model/ProfileModel.php:
--------------------------------------------------------------------------------
1 | email;
18 | }
19 |
20 | public function setEmail(?string $email): self
21 | {
22 | $this->email = $email;
23 |
24 | return $this;
25 | }
26 |
27 | public function getPassword(): ?string
28 | {
29 | return $this->password;
30 | }
31 |
32 | public function setPassword(?string $password): self
33 | {
34 | $this->password = $password;
35 |
36 | return $this;
37 | }
38 |
39 | public function getPasswordConfirm(): ?string
40 | {
41 | return $this->passwordConfirm;
42 | }
43 |
44 | public function setPasswordConfirm(?string $passwordConfirm): self
45 | {
46 | $this->passwordConfirm = $passwordConfirm;
47 |
48 | return $this;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Model/QueryParameterFilterModel.php:
--------------------------------------------------------------------------------
1 | $filters
11 | */
12 | private ?array $filters = null;
13 |
14 | /**
15 | * @param array|null $filters
16 | */
17 | public function __construct(?array $filters)
18 | {
19 | $this->filters = $filters;
20 | }
21 |
22 | public function get(string $key): mixed
23 | {
24 | return $this->filters[$key] ?? null;
25 | }
26 |
27 | public function getBool(string $key): mixed
28 | {
29 | return true === isset($this->filters[$key]) && true === in_array(strtolower($this->filters[$key]), ['true', '1']) ? true : false;
30 | }
31 |
32 | public function getInt(string $key): mixed
33 | {
34 | return true === isset($this->filters[$key]) ? intval($this->filters[$key]) : null;
35 | }
36 |
37 | /**
38 | * @return array
39 | */
40 | public function toArray(): array
41 | {
42 | return $this->filters ?? [];
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Model/QueryParameterPageModel.php:
--------------------------------------------------------------------------------
1 | $page
11 | */
12 | private ?array $page = null;
13 |
14 | /**
15 | * @param array|null $page
16 | */
17 | public function __construct(?array $page)
18 | {
19 | $this->page = $page;
20 | }
21 |
22 | public function getNumber(): int
23 | {
24 | return true === isset($this->page['number']) ? intval($this->page['number']) : 1;
25 | }
26 |
27 | public function getSize(): int
28 | {
29 | return true === isset($this->page['size']) ? intval($this->page['size']) : 20;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Model/QueryParameterSortModel.php:
--------------------------------------------------------------------------------
1 | sort = $sort;
14 | }
15 |
16 | public function raw(): ?string
17 | {
18 | return $this->sort;
19 | }
20 |
21 | /**
22 | * @return array
23 | */
24 | public function get(): ?array
25 | {
26 | if (null === $this->sort) {
27 | return null;
28 | }
29 |
30 | $criterias = [];
31 | $values = explode(',', urldecode($this->sort));
32 | foreach ($values as $value) {
33 | $criteria = [];
34 | $value = trim($value);
35 | if (str_starts_with($value, '-')) {
36 | $criteria['direction'] = 'DESC';
37 | $criteria['field'] = substr($value, 1);
38 | } else {
39 | $criteria['direction'] = 'ASC';
40 | $criteria['field'] = $value;
41 | }
42 | $criterias[] = $criteria;
43 | }
44 |
45 | return $criterias[0] ?? null;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Repository/AbstractRepository.php:
--------------------------------------------------------------------------------
1 | getEntityClass());
23 |
24 | $em = $this->getEntityManager();
25 | $this->connection = $em->getConnection();
26 | }
27 |
28 | public function getConnection(): Connection
29 | {
30 | return $this->connection;
31 | }
32 |
33 | /**
34 | * @param array $fields
35 | */
36 | public function insert(string $table, array $fields): int
37 | {
38 | $this->connection->insert($table, $fields);
39 | return intval($this->connection->lastInsertId());
40 | }
41 |
42 | /**
43 | * @param array $fields
44 | */
45 | public function update(string $table, array $fields, int $id): void
46 | {
47 | $this->connection->update($table, $fields, ['id' => $id]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Repository/ActionRepository.php:
--------------------------------------------------------------------------------
1 | $parameters
19 | */
20 | public function getOne(array $parameters = []): ?Action
21 | {
22 | $em = $this->getEntityManager();
23 |
24 | $query = $em->createQueryBuilder();
25 | $query->addSelect('act');
26 | $query->from(Action::class, 'act');
27 |
28 | if (true === isset($parameters['id'])) {
29 | $query->andWhere('act.id = :id');
30 | $query->setParameter(':id', $parameters['id']);
31 | }
32 |
33 | if (true === isset($parameters['title'])) {
34 | $query->andWhere('act.title = :title');
35 | $query->setParameter(':title', $parameters['title']);
36 | }
37 |
38 | $getQuery = $query->getQuery();
39 | $getQuery->setMaxResults(1);
40 |
41 | return $getQuery->getOneOrNullResult();
42 | }
43 |
44 | /**
45 | * @param array $parameters
46 | */
47 | public function getList(array $parameters = []): mixed
48 | {
49 | $em = $this->getEntityManager();
50 |
51 | $query = $em->createQueryBuilder();
52 | $query->addSelect('act');
53 | $query->from(Action::class, 'act');
54 |
55 | if (true === isset($parameters['id'])) {
56 | $query->andWhere('act.id = :id');
57 | $query->setParameter(':id', $parameters['id']);
58 | }
59 |
60 | $query->groupBy('act.id');
61 |
62 | $getQuery = $query->getQuery();
63 | return $getQuery;
64 | }
65 |
66 | public function persist(Action $action, bool $flush = true): void
67 | {
68 | $this->getEntityManager()->persist($action);
69 |
70 | if (true === $flush) {
71 | $this->getEntityManager()->flush();
72 | }
73 | }
74 |
75 | public function remove(Action $action, bool $flush = true): void
76 | {
77 | $this->getEntityManager()->remove($action);
78 |
79 | if (true === $flush) {
80 | $this->getEntityManager()->flush();
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Repository/CollectionRepository.php:
--------------------------------------------------------------------------------
1 | $parameters
19 | */
20 | public function getOne(array $parameters = []): ?Collection
21 | {
22 | $em = $this->getEntityManager();
23 |
24 | $query = $em->createQueryBuilder();
25 | $query->addSelect('col');
26 | $query->from(Collection::class, 'col');
27 |
28 | if (true === isset($parameters['id'])) {
29 | $query->andWhere('col.id = :id');
30 | $query->setParameter(':id', $parameters['id']);
31 | }
32 |
33 | $getQuery = $query->getQuery();
34 | $getQuery->setMaxResults(1);
35 |
36 | return $getQuery->getOneOrNullResult();
37 | }
38 |
39 | /**
40 | * @param array $parameters
41 | */
42 | public function getList(array $parameters = []): mixed
43 | {
44 | $em = $this->getEntityManager();
45 |
46 | $query = $em->createQueryBuilder();
47 | $query->addSelect('col');
48 | $query->from(Collection::class, 'col');
49 |
50 | if (true === isset($parameters['id'])) {
51 | $query->andWhere('col.id = :id');
52 | $query->setParameter(':id', $parameters['id']);
53 | }
54 |
55 | $query->addOrderBy('col.id', 'DESC');
56 | $query->groupBy('col.id');
57 |
58 | $getQuery = $query->getQuery();
59 | return $getQuery;
60 | }
61 |
62 | public function persist(Collection $collection, bool $flush = true): void
63 | {
64 | $this->getEntityManager()->persist($collection);
65 |
66 | if (true === $flush) {
67 | $this->getEntityManager()->flush();
68 | }
69 | }
70 |
71 | public function remove(Collection $collection, bool $flush = true): void
72 | {
73 | $this->getEntityManager()->remove($collection);
74 |
75 | if (true === $flush) {
76 | $this->getEntityManager()->flush();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Security/ApiAccessDeniedHandler.php:
--------------------------------------------------------------------------------
1 | connectionManager = $connectionManager;
19 | }
20 |
21 | public function getUserBadgeFrom(string $token): UserBadge
22 | {
23 | try {
24 | $payloadjwtPayloadModel = JwtHelper::getPayload($token);
25 | } catch (\Exception $e) {
26 | throw new BadCredentialsException('Invalid token.');
27 | }
28 |
29 | if (null === $payloadjwtPayloadModel) {
30 | throw new BadCredentialsException('Invalid token.');
31 | }
32 |
33 | $connection = $this->connectionManager->getOne(['type' => Connection::TYPE_LOGIN, 'token' => $payloadjwtPayloadModel->getJwtId()]);
34 |
35 | if (null === $connection) {
36 | throw new BadCredentialsException('Token not found.');
37 | }
38 |
39 | if (null === $connection->getMember() || null === $connection->getMember()->getEmail()) {
40 | throw new BadCredentialsException('Member not found.');
41 | }
42 |
43 | return new UserBadge($connection->getMember()->getEmail());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Security/Voter/AbstractVoter.php:
--------------------------------------------------------------------------------
1 | security = $security;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Security/Voter/AuthorVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
20 | if (false === $user instanceof Member) {
21 | return false;
22 | }
23 |
24 | switch ($attribute) {
25 | case 'DELETE':
26 | return $this->security->isGranted('ROLE_ADMIN');
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Security/Voter/CategoryVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
20 | if (false === $user instanceof Member) {
21 | return false;
22 | }
23 |
24 | switch ($attribute) {
25 | case 'DELETE':
26 | return $this->security->isGranted('ROLE_ADMIN');
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Security/Voter/ConnectionVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
20 | if (false === $user instanceof Member) {
21 | return false;
22 | }
23 |
24 | return $this->security->isGranted('ROLE_ADMIN');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Security/Voter/EnclosureVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
20 | if (false === $user instanceof Member) {
21 | return false;
22 | }
23 |
24 | switch ($attribute) {
25 | case 'DELETE':
26 | return $this->security->isGranted('ROLE_ADMIN');
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Security/Voter/FeedVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
20 | if (false === $user instanceof Member) {
21 | return false;
22 | }
23 |
24 | switch ($attribute) {
25 | case 'DELETE':
26 | return $this->security->isGranted('ROLE_ADMIN');
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Security/Voter/ItemVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
20 | if (false === $user instanceof Member) {
21 | return false;
22 | }
23 |
24 | switch ($attribute) {
25 | case 'DELETE':
26 | return $this->security->isGranted('ROLE_ADMIN');
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Security/Voter/MemberPasskeyVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
20 | if (false === $user instanceof Member) {
21 | return false;
22 | }
23 |
24 | switch ($attribute) {
25 | case 'DELETE':
26 | if ($user->getId() !== $subject->getMember()->getId()) {
27 | return false;
28 | }
29 | }
30 |
31 | return true;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Security/Voter/MemberVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
19 | if (false === $user instanceof Member) {
20 | return false;
21 | }
22 |
23 | switch ($attribute) {
24 | case 'DELETE':
25 | if ($user->getId() === $subject->getId() || true === $subject->getAdministrator()) {
26 | return false;
27 | }
28 | }
29 |
30 | return $this->security->isGranted('ROLE_ADMIN');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/templates/base.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Feed
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
48 |
49 |
56 |
57 | {% block javascripts %}
58 |
61 | {% block importmap %}{{ importmap('app') }}{% endblock %}
62 | {% endblock %}
63 |
64 |
65 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/AuthorControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/authors');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testCreate(): void
25 | {
26 | $this->client->request('POST', '/api/authors');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 |
31 | public function testRead(): void
32 | {
33 | $this->client->request('GET', '/api/author/0');
34 |
35 | $this->assertResponseStatusCodeSame(401);
36 | }
37 |
38 | public function testUpdate(): void
39 | {
40 | $this->client->request('PUT', '/api/author/0');
41 |
42 | $this->assertResponseStatusCodeSame(401);
43 | }
44 |
45 | public function testDelete(): void
46 | {
47 | $this->client->request('DELETE', '/api/author/0');
48 |
49 | $this->assertResponseStatusCodeSame(401);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/CategoryControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/categories');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testCreate(): void
25 | {
26 | $this->client->request('POST', '/api/categories');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 |
31 | public function testRead(): void
32 | {
33 | $this->client->request('GET', '/api/category/0');
34 |
35 | $this->assertResponseStatusCodeSame(401);
36 | }
37 |
38 | public function testUpdate(): void
39 | {
40 | $this->client->request('PUT', '/api/category/0');
41 |
42 | $this->assertResponseStatusCodeSame(401);
43 | }
44 |
45 | public function testDelete(): void
46 | {
47 | $this->client->request('DELETE', '/api/category/0');
48 |
49 | $this->assertResponseStatusCodeSame(401);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/ConnectionControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/connections');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testDelete(): void
25 | {
26 | $this->client->request('DELETE', '/api/connection/0');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/EnclosureControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/enclosures');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testRead(): void
25 | {
26 | $this->client->request('GET', '/api/enclosure/0');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 |
31 | public function testDelete(): void
32 | {
33 | $this->client->request('DELETE', '/api/enclosure/0');
34 |
35 | $this->assertResponseStatusCodeSame(401);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/FeedControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/feeds');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testCreate(): void
25 | {
26 | $this->client->request('POST', '/api/feeds');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 |
31 | public function testRead(): void
32 | {
33 | $this->client->request('GET', '/api/feed/0');
34 |
35 | $this->assertResponseStatusCodeSame(401);
36 | }
37 |
38 | public function testUpdate(): void
39 | {
40 | $this->client->request('PUT', '/api/feed/0');
41 |
42 | $this->assertResponseStatusCodeSame(401);
43 | }
44 |
45 | public function testDelete(): void
46 | {
47 | $this->client->request('DELETE', '/api/feed/0');
48 |
49 | $this->assertResponseStatusCodeSame(401);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/ItemControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/items');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testRead(): void
25 | {
26 | $this->client->request('GET', '/api/item/0');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 |
31 | public function testDelete(): void
32 | {
33 | $this->client->request('DELETE', '/api/item/0');
34 |
35 | $this->assertResponseStatusCodeSame(401);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/MemberPasskeyControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/passkeys');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testDelete(): void
25 | {
26 | $this->client->request('DELETE', '/api/passkey/0');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/ControllerAsAnonymous/ProfileControllerTest.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
15 | }
16 |
17 | public function testIndex(): void
18 | {
19 | $this->client->request('GET', '/api/profile');
20 |
21 | $this->assertResponseStatusCodeSame(401);
22 | }
23 |
24 | public function testConnections(): void
25 | {
26 | $this->client->request('GET', '/api/profile/connections');
27 |
28 | $this->assertResponseStatusCodeSame(401);
29 | }
30 |
31 | public function testPasskeys(): void
32 | {
33 | $this->client->request('GET', '/api/profile/passkeys');
34 |
35 | $this->assertResponseStatusCodeSame(401);
36 | }
37 |
38 | public function testUpdate(): void
39 | {
40 | $this->client->request('PUT', '/api/profile');
41 |
42 | $this->assertResponseStatusCodeSame(401);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/ControllerAsConnected/AbstractControllerTest.php:
--------------------------------------------------------------------------------
1 | get(ConnectionManager::class);
22 | $testConnection = $connectionManager->getOne([]);
23 |
24 | $headers = [
25 | 'CONTENT_TYPE' => 'application/json',
26 | ];
27 |
28 | if ($testConnection) {
29 | $jwtPayloadModel = new JwtPayloadModel();
30 | $jwtPayloadModel->setJwtId($testConnection->getToken());
31 | $jwtPayloadModel->setAudience(strval($testConnection->getMember()->getId()));
32 |
33 | $headers['HTTP_AUTHORIZATION'] = 'Bearer '.JwtHelper::createToken($jwtPayloadModel);
34 | }
35 |
36 | self::ensureKernelShutdown();
37 | $this->client = static::createClient([], $headers);
38 | }
39 |
40 | protected function isValidResponseString(string $json): bool
41 | {
42 | $isValidResponseString = Parser::isValidResponseString($json);
43 |
44 | if (false === $isValidResponseString) {
45 | try {
46 | // Use this if you have a response after calling a JSON API server
47 | Parser::parseResponseString($json);
48 | } catch (InputException $e) {
49 | // $jsonapiString is not valid JSON
50 | dump($e->getMessage());
51 | } catch (ValidationException $e) {
52 | // $jsonapiString is not valid JSON API
53 | dump($e->getMessage());
54 | }
55 | }
56 |
57 | return $isValidResponseString;
58 | }
59 |
60 | protected function retrieveOneId(string $path): ?int
61 | {
62 | $this->client->request('GET', $path);
63 | $json = $this->client->getResponse()->getContent();
64 | $content = json_decode($json, true);
65 |
66 | if (true === isset($content['data']) && 0 < count($content['data'])) {
67 | return intval($content['data'][0]['id']);
68 | }
69 |
70 | return null;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/ControllerAsConnected/EnclosureControllerTest.php:
--------------------------------------------------------------------------------
1 | client->request('GET', '/api/enclosures');
12 | $json = $this->client->getResponse()->getContent();
13 | $isValidResponseString = $this->isValidResponseString($json);
14 |
15 | $this->assertTrue($isValidResponseString);
16 | $this->assertResponseStatusCodeSame(200);
17 | }
18 |
19 | public function testRead404(): void
20 | {
21 | $this->client->request('GET', '/api/enclosure/0');
22 | $json = $this->client->getResponse()->getContent();
23 | $isValidResponseString = $this->isValidResponseString($json);
24 | $content = json_decode($json, true);
25 |
26 | $this->assertTrue($isValidResponseString);
27 | $this->assertResponseStatusCodeSame(404);
28 | $this->assertEquals('404', $content['errors'][0]['status']);
29 | $this->assertEquals('Not Found', $content['errors'][0]['title']);
30 | }
31 |
32 | public function testRead(): void
33 | {
34 | if ($id = $this->retrieveOneId('/api/enclosures')) {
35 | $this->client->request('GET', '/api/enclosure/'.$id);
36 | $json = $this->client->getResponse()->getContent();
37 | $isValidResponseString = $this->isValidResponseString($json);
38 | $content = json_decode($json, true);
39 |
40 | $this->assertTrue($isValidResponseString);
41 | $this->assertResponseStatusCodeSame(200);
42 | $this->assertEquals('enclosure', $content['data']['type']);
43 | $this->assertEquals($id, $content['data']['id']);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/ControllerAsConnected/ItemControllerTest.php:
--------------------------------------------------------------------------------
1 | client->request('GET', '/api/items');
12 | $json = $this->client->getResponse()->getContent();
13 | $isValidResponseString = $this->isValidResponseString($json);
14 |
15 | $this->assertTrue($isValidResponseString);
16 | $this->assertResponseStatusCodeSame(200);
17 | }
18 |
19 | public function testRead404(): void
20 | {
21 | $this->client->request('GET', '/api/item/0');
22 | $json = $this->client->getResponse()->getContent();
23 | $isValidResponseString = $this->isValidResponseString($json);
24 | $content = json_decode($json, true);
25 |
26 | $this->assertTrue($isValidResponseString);
27 | $this->assertResponseStatusCodeSame(404);
28 | $this->assertEquals('404', $content['errors'][0]['status']);
29 | $this->assertEquals('Not Found', $content['errors'][0]['title']);
30 | }
31 |
32 | public function testRead(): void
33 | {
34 | if ($id = $this->retrieveOneId('/api/items')) {
35 | $this->client->request('GET', '/api/item/'.$id);
36 | $json = $this->client->getResponse()->getContent();
37 | $isValidResponseString = $this->isValidResponseString($json);
38 | $content = json_decode($json, true);
39 |
40 | $this->assertTrue($isValidResponseString);
41 | $this->assertResponseStatusCodeSame(200);
42 | $this->assertEquals('item', $content['data']['type']);
43 | $this->assertEquals($id, $content['data']['id']);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Entity/ActionTest.php:
--------------------------------------------------------------------------------
1 | entityManager = $kernel->getContainer()->get('doctrine')->getManager();
18 | }
19 |
20 | public function testToString()
21 | {
22 | $this->assertTrue(method_exists(new Action(), '__toString'), 'method __toString missing');
23 | }
24 |
25 | public function testPersist()
26 | {
27 | $value = 'test-'.uniqid();
28 |
29 | //add action
30 | $action = new Action();
31 | $action->setTitle($value);
32 |
33 | $this->entityManager->persist($action);
34 | $this->entityManager->flush();
35 |
36 | //remove
37 | $this->entityManager->remove($action);
38 | $this->entityManager->flush();
39 |
40 | $this->assertTrue(true);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Entity/AuthorTest.php:
--------------------------------------------------------------------------------
1 | entityManager = $kernel->getContainer()->get('doctrine')->getManager();
20 | }
21 |
22 | public function testToString()
23 | {
24 | $this->assertTrue(method_exists(new Author(), '__toString'), 'method __toString missing');
25 | }
26 |
27 | public function testPersist()
28 | {
29 | $value = 'test-'.uniqid();
30 |
31 | //add action
32 | $action = new Action();
33 | $action->setTitle($value);
34 |
35 | $this->entityManager->persist($action);
36 | $this->entityManager->flush();
37 |
38 | //add author
39 | $author = new Author();
40 | $author->setTitle($value);
41 |
42 | //add action author (cascade persist)
43 | $actionAuthor = new ActionAuthor();
44 | $actionAuthor->setAction($action);
45 | $author->addAction($actionAuthor);
46 | $this->assertTrue($author->hasAction($actionAuthor));
47 |
48 | $this->entityManager->persist($author);
49 | $this->entityManager->flush();
50 |
51 | //remove (orphan removal)
52 | $author->removeAction($actionAuthor);
53 | $this->assertFalse($author->hasAction($actionAuthor));
54 |
55 | $this->entityManager->persist($author);
56 | $this->entityManager->flush();
57 |
58 | //remove
59 | $this->entityManager->remove($author);
60 | $this->entityManager->remove($action);
61 | $this->entityManager->flush();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Entity/CategoryTest.php:
--------------------------------------------------------------------------------
1 | entityManager = $kernel->getContainer()->get('doctrine')->getManager();
20 | }
21 |
22 | public function testToString()
23 | {
24 | $this->assertTrue(method_exists(new Category(), '__toString'), 'method __toString missing');
25 | }
26 |
27 | public function testPersist()
28 | {
29 | $value = 'test-'.uniqid();
30 |
31 | //add action
32 | $action = new Action();
33 | $action->setTitle($value);
34 |
35 | $this->entityManager->persist($action);
36 | $this->entityManager->flush();
37 |
38 | //add category
39 | $category = new Category();
40 | $category->setTitle($value);
41 |
42 | //add action category (cascade persist)
43 | $actionCategory = new ActionCategory();
44 | $actionCategory->setAction($action);
45 | $category->addAction($actionCategory);
46 | $this->assertTrue($category->hasAction($actionCategory));
47 |
48 | $this->entityManager->persist($category);
49 | $this->entityManager->flush();
50 |
51 | //remove (orphan removal)
52 | $category->removeAction($actionCategory);
53 | $this->assertFalse($category->hasAction($actionCategory));
54 |
55 | $this->entityManager->persist($category);
56 | $this->entityManager->flush();
57 |
58 | //remove
59 | $this->entityManager->remove($category);
60 | $this->entityManager->remove($action);
61 | $this->entityManager->flush();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Entity/FeedTest.php:
--------------------------------------------------------------------------------
1 | entityManager = $kernel->getContainer()->get('doctrine')->getManager();
22 | }
23 |
24 | public function testToString()
25 | {
26 | $this->assertTrue(method_exists(new Feed(), '__toString'), 'method __toString missing');
27 | }
28 |
29 | public function testPersist()
30 | {
31 | $value = 'test-'.uniqid();
32 |
33 | //add action
34 | $action = new Action();
35 | $action->setTitle($value);
36 |
37 | $this->entityManager->persist($action);
38 | $this->entityManager->flush();
39 |
40 | //add category
41 | $category = new Category();
42 | $category->setTitle($value);
43 |
44 | $this->entityManager->persist($category);
45 | $this->entityManager->flush();
46 |
47 | //add feed
48 | $feed = new Feed();
49 | $feed->setTitle($value);
50 | $feed->setLink($value);
51 |
52 | //add action feed (cascade persist)
53 | $actionFeed = new ActionFeed();
54 | $actionFeed->setAction($action);
55 | $feed->addAction($actionFeed);
56 | $this->assertTrue($feed->hasAction($actionFeed));
57 |
58 | //add feed category (cascade persist)
59 | $feedCategory = new FeedCategory();
60 | $feedCategory->setCategory($category);
61 | $feed->addCategory($feedCategory);
62 | $this->assertTrue($feed->hasCategory($feedCategory));
63 |
64 | $this->entityManager->persist($feed);
65 | $this->entityManager->flush();
66 |
67 | //remove (orphan removal)
68 | $feed->removeAction($actionFeed);
69 | $this->assertFalse($feed->hasAction($actionFeed));
70 |
71 | $feed->removeCategory($feedCategory);
72 | $this->assertFalse($feed->hasCategory($feedCategory));
73 |
74 | $this->entityManager->persist($feed);
75 | $this->entityManager->flush();
76 |
77 | //remove
78 | $this->entityManager->remove($feed);
79 | $this->entityManager->remove($action);
80 | $this->entityManager->remove($category);
81 | $this->entityManager->flush();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tests/Manager/ActionAuthorManagerTest.php:
--------------------------------------------------------------------------------
1 | actionManager = static::getContainer()->get('App\Manager\ActionManager');
28 |
29 | $this->authorManager = static::getContainer()->get('App\Manager\AuthorManager');
30 |
31 | $this->actionAuthorManager = static::getContainer()->get('App\Manager\ActionAuthorManager');
32 | }
33 |
34 | public function testPersist(): void
35 | {
36 | $action = new Action();
37 | $action->setTitle(uniqid('phpunit-'));
38 |
39 | $this->actionManager->persist($action);
40 |
41 | $author = new Author();
42 | $author->setTitle(uniqid('phpunit-'));
43 |
44 | $this->authorManager->persist($author);
45 |
46 | $actionAuthor = new ActionAuthor();
47 | $actionAuthor->setAction($action);
48 | $actionAuthor->setAuthor($author);
49 |
50 | $this->actionAuthorManager->persist($actionAuthor);
51 |
52 | $this->assertIsInt($actionAuthor->getId());
53 |
54 | $this->actionAuthorManager->remove($actionAuthor);
55 |
56 | $this->authorManager->remove($author);
57 |
58 | $this->actionManager->remove($action);
59 | }
60 |
61 | public function testGetOne(): void
62 | {
63 | $test = $this->actionAuthorManager->getOne(['id' => 0]);
64 | $this->assertNull($test);
65 | }
66 |
67 | public function testGetList(): void
68 | {
69 | $test = $this->actionAuthorManager->getList(['id' => 0])->getResult();
70 | $this->assertIsArray($test);
71 | $this->assertCount(0, $test);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/Manager/ActionCategoryManagerTest.php:
--------------------------------------------------------------------------------
1 | actionManager = static::getContainer()->get('App\Manager\ActionManager');
28 |
29 | $this->categoryManager = static::getContainer()->get('App\Manager\CategoryManager');
30 |
31 | $this->actionCategoryManager = static::getContainer()->get('App\Manager\ActionCategoryManager');
32 | }
33 |
34 | public function testPersist(): void
35 | {
36 | $action = new Action();
37 | $action->setTitle(uniqid('phpunit-'));
38 |
39 | $this->actionManager->persist($action);
40 |
41 | $category = new Category();
42 | $category->setTitle(uniqid('phpunit-'));
43 |
44 | $this->categoryManager->persist($category);
45 |
46 | $actionCategory = new ActionCategory();
47 | $actionCategory->setAction($action);
48 | $actionCategory->setCategory($category);
49 |
50 | $this->actionCategoryManager->persist($actionCategory);
51 |
52 | $this->assertIsInt($actionCategory->getId());
53 |
54 | $this->actionCategoryManager->remove($actionCategory);
55 |
56 | $this->categoryManager->remove($category);
57 |
58 | $this->actionManager->remove($action);
59 | }
60 |
61 | public function testGetOne(): void
62 | {
63 | $test = $this->actionCategoryManager->getOne(['id' => 0]);
64 | $this->assertNull($test);
65 | }
66 |
67 | public function testGetList(): void
68 | {
69 | $test = $this->actionCategoryManager->getList(['id' => 0])->getResult();
70 | $this->assertIsArray($test);
71 | $this->assertCount(0, $test);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/Manager/ActionFeedManagerTest.php:
--------------------------------------------------------------------------------
1 | actionManager = static::getContainer()->get('App\Manager\ActionManager');
28 |
29 | $this->feedManager = static::getContainer()->get('App\Manager\FeedManager');
30 |
31 | $this->actionFeedManager = static::getContainer()->get('App\Manager\ActionFeedManager');
32 | }
33 |
34 | public function testPersist(): void
35 | {
36 | $action = new Action();
37 | $action->setTitle(uniqid('phpunit-'));
38 |
39 | $this->actionManager->persist($action);
40 |
41 | $feed = new Feed();
42 | $feed->setTitle(uniqid('phpunit-'));
43 | $feed->setLink(uniqid('phpunit-'));
44 |
45 | $this->feedManager->persist($feed);
46 |
47 | $actionFeed = new ActionFeed();
48 | $actionFeed->setAction($action);
49 | $actionFeed->setFeed($feed);
50 |
51 | $this->actionFeedManager->persist($actionFeed);
52 |
53 | $this->assertIsInt($actionFeed->getId());
54 |
55 | $this->actionFeedManager->remove($actionFeed);
56 |
57 | $this->feedManager->remove($feed);
58 |
59 | $this->actionManager->remove($action);
60 | }
61 |
62 | public function testGetOne(): void
63 | {
64 | $test = $this->actionFeedManager->getOne(['id' => 0]);
65 | $this->assertNull($test);
66 | }
67 |
68 | public function testGetList(): void
69 | {
70 | $test = $this->actionFeedManager->getList(['id' => 0])->getResult();
71 | $this->assertIsArray($test);
72 | $this->assertCount(0, $test);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/Manager/ActionManagerTest.php:
--------------------------------------------------------------------------------
1 | actionManager = static::getContainer()->get('App\Manager\ActionManager');
20 | }
21 |
22 | public function testPersist(): void
23 | {
24 | $action = new Action();
25 | $action->setTitle(uniqid('phpunit-'));
26 |
27 | $this->actionManager->persist($action);
28 |
29 | $this->assertIsInt($action->getId());
30 |
31 | $this->actionManager->remove($action);
32 | }
33 |
34 | public function testGetOne(): void
35 | {
36 | $test = $this->actionManager->getOne(['id' => 0]);
37 | $this->assertNull($test);
38 | }
39 |
40 | public function testGetList(): void
41 | {
42 | $test = $this->actionManager->getList(['id' => 0])->getResult();
43 | $this->assertIsArray($test);
44 | $this->assertCount(0, $test);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Manager/AuthorManagerTest.php:
--------------------------------------------------------------------------------
1 | authorManager = static::getContainer()->get('App\Manager\AuthorManager');
20 | }
21 |
22 | public function testPersist(): void
23 | {
24 | $author = new Author();
25 | $author->setTitle(uniqid('phpunit-'));
26 |
27 | $this->authorManager->persist($author);
28 |
29 | $this->assertIsInt($author->getId());
30 |
31 | $this->authorManager->remove($author);
32 | }
33 |
34 | public function testGetOne(): void
35 | {
36 | $test = $this->authorManager->getOne(['id' => 0]);
37 | $this->assertNull($test);
38 | }
39 |
40 | public function testGetList(): void
41 | {
42 | $test = $this->authorManager->getList(['id' => 0, 'sortField' => 'aut.id', 'sortDirection' => 'ASC'])->getResult();
43 | $this->assertIsArray($test);
44 | $this->assertCount(0, $test);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Manager/CategoryManagerTest.php:
--------------------------------------------------------------------------------
1 | categoryManager = static::getContainer()->get('App\Manager\CategoryManager');
20 | }
21 |
22 | public function testPersist(): void
23 | {
24 | $category = new Category();
25 | $category->setTitle(uniqid('phpunit-'));
26 |
27 | $this->categoryManager->persist($category);
28 |
29 | $this->assertIsInt($category->getId());
30 |
31 | $this->categoryManager->remove($category);
32 | }
33 |
34 | public function testGetOne(): void
35 | {
36 | $test = $this->categoryManager->getOne(['id' => 0]);
37 | $this->assertNull($test);
38 | }
39 |
40 | public function testGetList(): void
41 | {
42 | $test = $this->categoryManager->getList(['id' => 0, 'sortField' => 'cat.id', 'sortDirection' => 'ASC'])->getResult();
43 | $this->assertIsArray($test);
44 | $this->assertCount(0, $test);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Manager/CollectionFeedManagerTest.php:
--------------------------------------------------------------------------------
1 | collectionManager = static::getContainer()->get('App\Manager\CollectionManager');
28 |
29 | $this->feedManager = static::getContainer()->get('App\Manager\FeedManager');
30 |
31 | $this->collectionFeedManager = static::getContainer()->get('App\Manager\CollectionFeedManager');
32 | }
33 |
34 | public function testPersist(): void
35 | {
36 | $collection = new Collection();
37 |
38 | $this->collectionManager->persist($collection);
39 |
40 | $feed = new Feed();
41 | $feed->setTitle(uniqid('phpunit-'));
42 | $feed->setLink(uniqid('phpunit-'));
43 |
44 | $this->feedManager->persist($feed);
45 |
46 | $collectionFeed = new CollectionFeed();
47 | $collectionFeed->setCollection($collection);
48 | $collectionFeed->setFeed($feed);
49 |
50 | $this->collectionFeedManager->persist($collectionFeed);
51 |
52 | $this->assertIsInt($collectionFeed->getId());
53 |
54 | $this->collectionFeedManager->remove($collectionFeed);
55 |
56 | $this->feedManager->remove($feed);
57 |
58 | $this->collectionManager->remove($collection);
59 | }
60 |
61 | public function testGetOne(): void
62 | {
63 | $test = $this->collectionFeedManager->getOne(['id' => 0]);
64 | $this->assertNull($test);
65 | }
66 |
67 | public function testGetList(): void
68 | {
69 | $test = $this->collectionFeedManager->getList(['id' => 0])->getResult();
70 | $this->assertIsArray($test);
71 | $this->assertCount(0, $test);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/Manager/CollectionManagerTest.php:
--------------------------------------------------------------------------------
1 | collectionManager = static::getContainer()->get('App\Manager\CollectionManager');
20 | }
21 |
22 | public function testPersist(): void
23 | {
24 | $collection = new Collection();
25 |
26 | $this->collectionManager->persist($collection);
27 |
28 | $this->assertIsInt($collection->getId());
29 |
30 | $this->collectionManager->remove($collection);
31 | }
32 |
33 | public function testGetOne(): void
34 | {
35 | $test = $this->collectionManager->getOne(['id' => 0]);
36 | $this->assertNull($test);
37 | }
38 |
39 | public function testGetList(): void
40 | {
41 | $test = $this->collectionManager->getList(['id' => 0])->getResult();
42 | $this->assertIsArray($test);
43 | $this->assertCount(0, $test);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Manager/ConnectionManagerTest.php:
--------------------------------------------------------------------------------
1 | memberManager = static::getContainer()->get('App\Manager\MemberManager');
24 |
25 | $this->connectionManager = static::getContainer()->get('App\Manager\ConnectionManager');
26 | }
27 |
28 | public function testPersist(): void
29 | {
30 | $member = new Member();
31 | $member->setEmail(uniqid('phpunit-'));
32 | $member->setPassword(uniqid('phpunit-'));
33 |
34 | $this->memberManager->persist($member);
35 |
36 | $connection = new Connection();
37 | $connection->setMember($member);
38 | $connection->setType(uniqid('phpunit-'));
39 | $connection->setToken(uniqid('phpunit-'));
40 |
41 | $this->connectionManager->persist($connection);
42 |
43 | $this->assertIsInt($connection->getId());
44 |
45 | $this->connectionManager->remove($connection);
46 |
47 | $this->memberManager->remove($member);
48 | }
49 |
50 | public function testGetOne(): void
51 | {
52 | $test = $this->connectionManager->getOne(['id' => 0]);
53 | $this->assertNull($test);
54 | }
55 |
56 | public function testGetList(): void
57 | {
58 | $test = $this->connectionManager->getList(['id' => 0])->getResult();
59 | $this->assertIsArray($test);
60 | $this->assertCount(0, $test);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Manager/EnclosureManagerTest.php:
--------------------------------------------------------------------------------
1 | feedManager = static::getContainer()->get('App\Manager\FeedManager');
28 |
29 | $this->itemManager = static::getContainer()->get('App\Manager\ItemManager');
30 |
31 | $this->enclosureManager = static::getContainer()->get('App\Manager\EnclosureManager');
32 | }
33 |
34 | public function testPersist(): void
35 | {
36 | $feed = new Feed();
37 | $feed->setTitle(uniqid('phpunit-'));
38 | $feed->setLink(uniqid('phpunit-'));
39 |
40 | $this->feedManager->persist($feed);
41 |
42 | $item = new Item();
43 | $item->setFeed($feed);
44 | $item->setTitle(uniqid('phpunit-'));
45 | $item->setLink(uniqid('phpunit-'));
46 | $item->setDate(new \Datetime());
47 |
48 | $this->itemManager->persist($item);
49 |
50 | $enclosure = new Enclosure();
51 | $enclosure->setItem($item);
52 | $enclosure->setLink(uniqid('phpunit-'));
53 | $enclosure->setType(uniqid('phpunit-'));
54 |
55 | $this->enclosureManager->persist($enclosure);
56 |
57 | $this->assertIsInt($enclosure->getId());
58 |
59 | $this->enclosureManager->remove($enclosure);
60 |
61 | $this->itemManager->remove($item);
62 |
63 | $this->feedManager->remove($feed);
64 | }
65 |
66 | public function testGetOne(): void
67 | {
68 | $test = $this->enclosureManager->getOne(['id' => 0]);
69 | $this->assertNull($test);
70 | }
71 |
72 | public function testGetList(): void
73 | {
74 | $test = $this->enclosureManager->getList(['id' => 0])->getResult();
75 | $this->assertIsArray($test);
76 | $this->assertCount(0, $test);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/Manager/FeedCategoryManagerTest.php:
--------------------------------------------------------------------------------
1 | categoryManager = static::getContainer()->get('App\Manager\CategoryManager');
28 |
29 | $this->feedManager = static::getContainer()->get('App\Manager\FeedManager');
30 |
31 | $this->feedCategoryManager = static::getContainer()->get('App\Manager\FeedCategoryManager');
32 | }
33 |
34 | public function testPersist(): void
35 | {
36 | $category = new Category();
37 | $category->setTitle(uniqid('phpunit-'));
38 |
39 | $this->categoryManager->persist($category);
40 |
41 | $feed = new Feed();
42 | $feed->setTitle(uniqid('phpunit-'));
43 | $feed->setLink(uniqid('phpunit-'));
44 |
45 | $this->feedManager->persist($feed);
46 |
47 | $feedCategory = new FeedCategory();
48 | $feedCategory->setCategory($category);
49 | $feedCategory->setFeed($feed);
50 |
51 | $this->feedCategoryManager->persist($feedCategory);
52 |
53 | $this->assertIsInt($feedCategory->getId());
54 |
55 | $this->feedCategoryManager->remove($feedCategory);
56 |
57 | $this->feedManager->remove($feed);
58 |
59 | $this->categoryManager->remove($category);
60 | }
61 |
62 | public function testGetOne(): void
63 | {
64 | $test = $this->feedCategoryManager->getOne(['id' => 0]);
65 | $this->assertNull($test);
66 | }
67 |
68 | public function testGetList(): void
69 | {
70 | $test = $this->feedCategoryManager->getList(['id' => 0])->getResult();
71 | $this->assertIsArray($test);
72 | $this->assertCount(0, $test);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/Manager/FeedManagerTest.php:
--------------------------------------------------------------------------------
1 | feedManager = static::getContainer()->get('App\Manager\FeedManager');
20 | }
21 |
22 | public function testPersist(): void
23 | {
24 | $feed = new Feed();
25 | $feed->setTitle(uniqid('phpunit-'));
26 | $feed->setLink(uniqid('phpunit-'));
27 |
28 | $this->feedManager->persist($feed);
29 |
30 | $this->assertIsInt($feed->getId());
31 |
32 | $this->feedManager->remove($feed);
33 | }
34 |
35 | public function testGetOne(): void
36 | {
37 | $test = $this->feedManager->getOne(['id' => 0]);
38 | $this->assertNull($test);
39 | }
40 |
41 | public function testGetList(): void
42 | {
43 | $test = $this->feedManager->getList(['id' => 0, 'sortField' => 'fed.id', 'sortDirection' => 'ASC'])->getResult();
44 | $this->assertIsArray($test);
45 | $this->assertCount(0, $test);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Manager/ItemManagerTest.php:
--------------------------------------------------------------------------------
1 | feedManager = static::getContainer()->get('App\Manager\FeedManager');
24 |
25 | $this->itemManager = static::getContainer()->get('App\Manager\ItemManager');
26 | }
27 |
28 | public function testPersist(): void
29 | {
30 | $feed = new Feed();
31 | $feed->setTitle(uniqid('phpunit-'));
32 | $feed->setLink(uniqid('phpunit-'));
33 |
34 | $this->feedManager->persist($feed);
35 |
36 | $item = new Item();
37 | $item->setFeed($feed);
38 | $item->setTitle(uniqid('phpunit-'));
39 | $item->setLink(uniqid('phpunit-'));
40 | $item->setDate(new \Datetime());
41 |
42 | $this->itemManager->persist($item);
43 |
44 | $this->assertIsInt($item->getId());
45 |
46 | $this->itemManager->remove($item);
47 |
48 | $this->feedManager->remove($feed);
49 | }
50 |
51 | public function testGetOne(): void
52 | {
53 | $test = $this->itemManager->getOne(['id' => 0]);
54 | $this->assertNull($test);
55 | }
56 |
57 | public function testGetList(): void
58 | {
59 | $test = $this->itemManager->getList(['id' => 0, 'sortField' => 'itm.id', 'sortDirection' => 'ASC'])->getResult();
60 | $this->assertIsArray($test);
61 | $this->assertCount(0, $test);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Manager/MemberManagerTest.php:
--------------------------------------------------------------------------------
1 | memberManager = static::getContainer()->get('App\Manager\MemberManager');
20 | }
21 |
22 | public function testPersist(): void
23 | {
24 | $member = new Member();
25 | $member->setEmail(uniqid('phpunit-'));
26 | $member->setPassword(uniqid('phpunit-'));
27 |
28 | $this->memberManager->persist($member);
29 |
30 | $this->assertIsInt($member->getId());
31 |
32 | $this->memberManager->remove($member);
33 | }
34 |
35 | public function testGetOne(): void
36 | {
37 | $test = $this->memberManager->getOne(['id' => 0]);
38 | $this->assertNull($test);
39 | }
40 |
41 | public function testGetList(): void
42 | {
43 | $test = $this->memberManager->getList(['id' => 0])->getResult();
44 | $this->assertIsArray($test);
45 | $this->assertCount(0, $test);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Manager/MemberPasskeyManagerTest.php:
--------------------------------------------------------------------------------
1 | memberManager = static::getContainer()->get('App\Manager\MemberManager');
24 |
25 | $this->memberPasskeyManager = static::getContainer()->get('App\Manager\MemberPasskeyManager');
26 | }
27 |
28 | public function testPersist(): void
29 | {
30 | $member = new Member();
31 | $member->setEmail(uniqid('phpunit-'));
32 | $member->setPassword(uniqid('phpunit-'));
33 |
34 | $this->memberManager->persist($member);
35 |
36 | $memberPasskey = new MemberPasskey();
37 | $memberPasskey->setTitle(uniqid('phpunit-'));
38 | $memberPasskey->setCredentialId(uniqid('phpunit-'));
39 | $memberPasskey->setPublicKey(uniqid('phpunit-'));
40 | $memberPasskey->setMember($member);
41 |
42 | $this->memberPasskeyManager->persist($memberPasskey);
43 |
44 | $this->assertIsInt($memberPasskey->getId());
45 |
46 | $this->memberPasskeyManager->remove($memberPasskey);
47 |
48 | $this->memberManager->remove($member);
49 | }
50 |
51 | public function testGetOne(): void
52 | {
53 | $test = $this->memberPasskeyManager->getOne(['id' => 0]);
54 | $this->assertNull($test);
55 | }
56 |
57 | public function testGetList(): void
58 | {
59 | $test = $this->memberPasskeyManager->getList(['id' => 0])->getResult();
60 | $this->assertIsArray($test);
61 | $this->assertCount(0, $test);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | bootEnv(dirname(__DIR__).'/.env');
9 | }
10 |
--------------------------------------------------------------------------------
/translations/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephanediondev/feed/2f61d1922279d3d989f7b485e769b2b7da7275b2/translations/.gitignore
--------------------------------------------------------------------------------
/update.sh:
--------------------------------------------------------------------------------
1 | git fetch origin
2 | git reset --hard origin/main
3 |
4 | composer install
5 | bin/console doctrine:migrations:migrate -n
6 |
7 | bin/console asset-map:compile
8 |
9 | sed -i "s/VERSION =.*/VERSION = '"$(date +%Y-%m-%d.%H-%M-%S)"';/g" public/serviceworker.js
10 |
--------------------------------------------------------------------------------