Compare commits
5 Commits
version-ma
...
acp-cleanu
| Author | SHA1 | Date | |
|---|---|---|---|
| fe1015bff1 | |||
|
|
8604cdf95d | ||
| f83748cc76 | |||
| 63bd166a65 | |||
|
|
bdfbe3ffd6 |
@@ -1,5 +1,3 @@
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
@@ -10,8 +8,11 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{compose.yaml,compose.*.yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[compose.yaml]
|
||||
indent_size = 4
|
||||
65
.env.example
Normal file
65
.env.example
Normal file
@@ -0,0 +1,65 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
# PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
37
.gitignore
vendored
37
.gitignore
vendored
@@ -1,9 +1,28 @@
|
||||
.idea/
|
||||
node_modules/
|
||||
frontend/node_modules/
|
||||
frontend/dist/
|
||||
api/var/
|
||||
api/vendor/
|
||||
api/public/app/
|
||||
api/.env.local
|
||||
api/.env.*.local
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.env.*.local
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/.zed
|
||||
/.phpstorm.meta.php
|
||||
/_ide_helper.php
|
||||
/_ide_helper_models.php
|
||||
/auth.json
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -21,3 +21,38 @@
|
||||
- Added system font stack to remove external font requests.
|
||||
- Improved ACP drag-and-drop hover reordering and visual drop target feedback.
|
||||
- Hardened ACP access so admin tools require authentication.
|
||||
- Updated the home page to render the forum tree with ACP-style rows and icons.
|
||||
|
||||
## 2025-12-26
|
||||
- Replaced the Symfony backend with a fresh Laravel app in `api/`.
|
||||
- Added Fortify + Sanctum, API routes, and controllers for forums/threads/posts/settings/version.
|
||||
- Added Laravel migrations for forums, threads, posts, and settings (seeded with version/build/accent).
|
||||
- Added Laravel i18n endpoint backed by JSON translation files.
|
||||
- Updated frontend auth/register flow to work with Laravel tokens.
|
||||
|
||||
## 2025-12-29
|
||||
- Merged the React app into the Laravel codebase under `resources/js`.
|
||||
- Moved the Laravel app to the repo root and removed the separate `api/` and `frontend/` folders.
|
||||
- Serve the SPA shell via Blade with Vite-managed React assets.
|
||||
- Dropped Tailwind tooling to keep the frontend Bootstrap-only.
|
||||
- Replaced the default Laravel README with a forum placeholder.
|
||||
- Updated ACP forum tools with accent-tinted buttons and larger action spacing.
|
||||
- Defaulted the ACP forum tree to collapsed on page load.
|
||||
- Improved ACP create/edit dialog copy based on forum vs category and hid type selection during creation.
|
||||
|
||||
## 2025-12-30
|
||||
- Added soft deletes with audit metadata (deleted_at/deleted_by) for forums, threads, and posts.
|
||||
- Ensured API listings and ACP forum tree omit soft-deleted records by default.
|
||||
- Added thread seeding for forum test data.
|
||||
- Enforced category-only roots for forums (API validation, UI, and database constraint).
|
||||
- Added portal header with phpBB-style breadcrumb + quick links, plus notifications/messages + user menu.
|
||||
- Replaced the home page with a portal-style layout and latest posts list.
|
||||
- Added a dedicated board index page with phpBB-like sections and per-category collapse toggles.
|
||||
- Persisted board index collapse state per user via user_settings (DB + API + client cache).
|
||||
- Added category view rendering for subcategories in ForumView.
|
||||
- Updated thread list UI (icons, spacing) and New topic button styling in ForumView.
|
||||
- Added ACP per-category quick-create buttons for child categories and forums.
|
||||
- Removed the legacy navbar and cleaned up related styling.
|
||||
- Added ACP general settings for forum name, theme, accents, and logo (no reload required).
|
||||
- Added admin-only upload endpoints and ACP UI for logos and favicons.
|
||||
- Applied forum branding, theme defaults, accents, logos, and favicon links in the SPA header.
|
||||
|
||||
7
README.md
Normal file
7
README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# SpeedBB Forum
|
||||
|
||||
Placeholder README for the forum application.
|
||||
|
||||
## Status
|
||||
|
||||
Work in progress.
|
||||
35
api/.env
35
api/.env
@@ -1,35 +0,0 @@
|
||||
# In all environments, the following files are loaded if they exist,
|
||||
# the latter taking precedence over the former:
|
||||
#
|
||||
# * .env contains default values for the environment variables needed by the app
|
||||
# * .env.local uncommitted file with local overrides
|
||||
# * .env.$APP_ENV committed environment-specific defaults
|
||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||
#
|
||||
# Real environment variables win over .env files.
|
||||
#
|
||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||
# https://symfony.com/doc/current/configuration/secrets.html
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=
|
||||
APP_SHARE_DIR=var/share
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> symfony/routing ###
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
DEFAULT_URI=http://localhost
|
||||
###< symfony/routing ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||
#
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/speedbb?serverVersion=8.0.32&charset=utf8mb4"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_SECRET=7944fa8dd76a206ea3e7e16b9276029c
|
||||
###< symfony/framework-bundle ###
|
||||
14
api/.gitignore
vendored
14
api/.gitignore
vendored
@@ -1,14 +0,0 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> lexik/jwt-authentication-bundle ###
|
||||
/config/jwt/*.pem
|
||||
###< lexik/jwt-authentication-bundle ###
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||
}
|
||||
|
||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
|
||||
return new Application($kernel);
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
ports:
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
image: postgres:${POSTGRES_VERSION:-16}-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-app}
|
||||
# You should definitely change the password in production
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
volumes:
|
||||
- database_data:/var/lib/postgresql/data:rw
|
||||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
||||
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
volumes:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database_data:
|
||||
###< doctrine/doctrine-bundle ###
|
||||
@@ -1,91 +0,0 @@
|
||||
{
|
||||
"type": "project",
|
||||
"version": "25.00.1",
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"api-platform/doctrine-orm": "^4.2",
|
||||
"api-platform/symfony": "^4.2",
|
||||
"doctrine/doctrine-bundle": "^3.2",
|
||||
"doctrine/doctrine-migrations-bundle": "^4.0",
|
||||
"doctrine/orm": "^3.6",
|
||||
"lexik/jwt-authentication-bundle": "^3.2",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.3",
|
||||
"symfony/apache-pack": "*",
|
||||
"symfony/asset": "8.0.*",
|
||||
"symfony/console": "8.0.*",
|
||||
"symfony/dotenv": "8.0.*",
|
||||
"symfony/expression-language": "8.0.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "8.0.*",
|
||||
"symfony/property-access": "8.0.*",
|
||||
"symfony/property-info": "8.0.*",
|
||||
"symfony/runtime": "8.0.*",
|
||||
"symfony/security-bundle": "8.0.*",
|
||||
"symfony/serializer": "8.0.*",
|
||||
"symfony/translation": "8.0.*",
|
||||
"symfony/twig-bundle": "8.0.*",
|
||||
"symfony/validator": "8.0.*",
|
||||
"symfony/yaml": "8.0.*"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"bump-after-update": true,
|
||||
"sort-packages": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php82": "*",
|
||||
"symfony/polyfill-php83": "*",
|
||||
"symfony/polyfill-php84": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||
},
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
]
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"speedbb": {
|
||||
"build": 3
|
||||
},
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "8.0.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
7418
api/composer.lock
generated
7418
api/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
|
||||
];
|
||||
@@ -1,10 +0,0 @@
|
||||
api_platform:
|
||||
title: speedBB API
|
||||
version: 1.0.0
|
||||
formats:
|
||||
json: ['application/json']
|
||||
jsonld: ['application/ld+json']
|
||||
defaults:
|
||||
stateless: true
|
||||
cache_headers:
|
||||
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||
@@ -1,19 +0,0 @@
|
||||
framework:
|
||||
cache:
|
||||
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||
#prefix_seed: your_vendor_name/app_name
|
||||
|
||||
# The "app" cache stores to the filesystem by default.
|
||||
# The data in this cache should persist between deploys.
|
||||
# Other options include:
|
||||
|
||||
# Redis
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://localhost
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
# Namespaced pools use the above "app" backend by default
|
||||
#pools:
|
||||
#my.dedicated.cache: null
|
||||
@@ -1,46 +0,0 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '16'
|
||||
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
orm:
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
controller_resolver:
|
||||
auto_mapping: false
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
||||
@@ -1,6 +0,0 @@
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: false
|
||||
@@ -1,18 +0,0 @@
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
|
||||
serializer:
|
||||
enabled: true
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_factory_id: session.storage.factory.mock_file
|
||||
@@ -1,5 +0,0 @@
|
||||
lexik_jwt_authentication:
|
||||
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
|
||||
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
|
||||
pass_phrase: '%env(JWT_PASSPHRASE)%'
|
||||
token_ttl: 86400
|
||||
@@ -1,3 +0,0 @@
|
||||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
@@ -1,10 +0,0 @@
|
||||
framework:
|
||||
router:
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
default_uri: '%env(DEFAULT_URI)%'
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
router:
|
||||
strict_requirements: null
|
||||
@@ -1,55 +0,0 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: email
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
# Ensure dev tools and static assets are always allowed
|
||||
pattern: ^/(_profiler|_wdt|assets|build)/
|
||||
security: false
|
||||
login:
|
||||
pattern: ^/api/login
|
||||
stateless: true
|
||||
provider: app_user_provider
|
||||
json_login:
|
||||
check_path: /api/login
|
||||
username_path: email
|
||||
password_path: password
|
||||
success_handler: lexik_jwt_authentication.handler.authentication_success
|
||||
failure_handler: lexik_jwt_authentication.handler.authentication_failure
|
||||
main:
|
||||
pattern: ^/api
|
||||
lazy: true
|
||||
stateless: true
|
||||
provider: app_user_provider
|
||||
jwt: ~
|
||||
|
||||
# Activate different ways to authenticate:
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||
# switch_user: true
|
||||
|
||||
# Note: Only the *first* matching rule is applied
|
||||
access_control:
|
||||
- { path: ^/api/login, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/api, roles: PUBLIC_ACCESS }
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
# Password hashers are resource-intensive by design to ensure security.
|
||||
# In tests, it's safe to reduce their cost to improve performance.
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||
algorithm: auto
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
@@ -1,5 +0,0 @@
|
||||
framework:
|
||||
default_locale: en
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
providers:
|
||||
@@ -1,6 +0,0 @@
|
||||
twig:
|
||||
file_name_pattern: '*.twig'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
@@ -1,11 +0,0 @@
|
||||
framework:
|
||||
validation:
|
||||
# Enables validator auto-mapping support.
|
||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||
#auto_mapping:
|
||||
# App\Entity\: []
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
validation:
|
||||
not_compromised_password: false
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
|
||||
|
||||
# This file is the entry point to configure the routes of your app.
|
||||
# Methods with the #[Route] attribute are automatically imported.
|
||||
# See also https://symfony.com/doc/current/routing.html
|
||||
|
||||
# To list all registered routes, run the following command:
|
||||
# bin/console debug:router
|
||||
|
||||
controllers:
|
||||
resource: routing.controllers
|
||||
|
||||
api_login:
|
||||
path: /api/login
|
||||
methods: [POST]
|
||||
controller: lexik_jwt_authentication.controller.authentication
|
||||
@@ -1,4 +0,0 @@
|
||||
api_platform:
|
||||
resource: .
|
||||
type: api_platform
|
||||
prefix: /api
|
||||
@@ -1,4 +0,0 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /_error
|
||||
@@ -1,3 +0,0 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
@@ -1,23 +0,0 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
|
||||
|
||||
# This file is the entry point to configure your own services.
|
||||
# Files in the packages/ subdirectory configure your dependencies.
|
||||
# See also https://symfony.com/doc/current/service_container/import.html
|
||||
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251224115331 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Initial schema (executed previously).';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20251224120510 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE forum (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(100) NOT NULL, description LONGTEXT DEFAULT NULL, type VARCHAR(20) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, parent_id INT DEFAULT NULL, INDEX IDX_852BBECD727ACA70 (parent_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
|
||||
$this->addSql('ALTER TABLE forum ADD CONSTRAINT FK_852BBECD727ACA70 FOREIGN KEY (parent_id) REFERENCES forum (id)');
|
||||
$this->addSql('ALTER TABLE thread DROP FOREIGN KEY `FK_31204C8312469DE2`');
|
||||
$this->addSql('DROP INDEX IDX_31204C8312469DE2 ON thread');
|
||||
$this->addSql('INSERT INTO forum (id, name, description, type, created_at, updated_at) SELECT id, name, description, \'forum\', created_at, updated_at FROM category');
|
||||
$this->addSql('ALTER TABLE thread CHANGE category_id forum_id INT NOT NULL');
|
||||
$this->addSql('ALTER TABLE thread ADD CONSTRAINT FK_31204C8329CCBAD0 FOREIGN KEY (forum_id) REFERENCES forum (id)');
|
||||
$this->addSql('CREATE INDEX IDX_31204C8329CCBAD0 ON thread (forum_id)');
|
||||
$this->addSql('DROP TABLE category');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE category (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_general_ci`, description LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_general_ci`, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_general_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||
$this->addSql('ALTER TABLE forum DROP FOREIGN KEY FK_852BBECD727ACA70');
|
||||
$this->addSql('DROP TABLE forum');
|
||||
$this->addSql('ALTER TABLE thread DROP FOREIGN KEY FK_31204C8329CCBAD0');
|
||||
$this->addSql('DROP INDEX IDX_31204C8329CCBAD0 ON thread');
|
||||
$this->addSql('ALTER TABLE thread CHANGE forum_id category_id INT NOT NULL');
|
||||
$this->addSql('ALTER TABLE thread ADD CONSTRAINT `FK_31204C8312469DE2` FOREIGN KEY (category_id) REFERENCES category (id)');
|
||||
$this->addSql('CREATE INDEX IDX_31204C8312469DE2 ON thread (category_id)');
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251224154500 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add forum position for per-parent ordering.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE forum ADD position INT NOT NULL');
|
||||
$this->addSql('UPDATE forum SET position = id');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE forum DROP position');
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251224184500 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add settings table with version and build metadata.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE settings (id INT AUTO_INCREMENT NOT NULL, version VARCHAR(20) NOT NULL, build INT NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql("INSERT INTO settings (id, version, build) VALUES (1, '25.00.1', 3)");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE settings');
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251224191500 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Convert settings table to key/value rows for version/build.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE settings_new (id INT AUTO_INCREMENT NOT NULL, `key` VARCHAR(100) NOT NULL, value LONGTEXT NOT NULL, UNIQUE INDEX UNIQ_SETTINGS_KEY (`key`), PRIMARY KEY(id))');
|
||||
$this->addSql("INSERT INTO settings_new (`key`, value) SELECT 'version', version FROM settings LIMIT 1");
|
||||
$this->addSql("INSERT INTO settings_new (`key`, value) SELECT 'build', CAST(build AS CHAR) FROM settings LIMIT 1");
|
||||
$this->addSql('DROP TABLE settings');
|
||||
$this->addSql('RENAME TABLE settings_new TO settings');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE settings_old (id INT AUTO_INCREMENT NOT NULL, version VARCHAR(20) NOT NULL, build INT NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql("INSERT INTO settings_old (id, version, build) VALUES (1, (SELECT value FROM settings WHERE `key` = 'version' LIMIT 1), CAST((SELECT value FROM settings WHERE `key` = 'build' LIMIT 1) AS UNSIGNED))");
|
||||
$this->addSql('DROP TABLE settings');
|
||||
$this->addSql('RENAME TABLE settings_old TO settings');
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251224193000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add indexes for forum parent ordering and type filters.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE INDEX idx_forum_parent_position ON forum (parent_id, position)');
|
||||
$this->addSql('CREATE INDEX idx_forum_type ON forum (type)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP INDEX idx_forum_parent_position ON forum');
|
||||
$this->addSql('DROP INDEX idx_forum_type ON forum');
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251224194000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add accent color setting.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql("INSERT IGNORE INTO settings (`key`, value) VALUES ('accent_color', '#f29b3f')");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql("DELETE FROM settings WHERE `key` = 'accent_color'");
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} -f
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_setenvif.c>
|
||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||
</IfModule>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
};
|
||||
0
api/src/ApiResource/.gitignore
vendored
0
api/src/ApiResource/.gitignore
vendored
0
api/src/Controller/.gitignore
vendored
0
api/src/Controller/.gitignore
vendored
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Forum;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class ForumReorderController
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private Security $security
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/api/forums/reorder', name: 'api_forums_reorder', methods: ['POST'])]
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||
return new JsonResponse(['message' => 'Forbidden'], JsonResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$payload = json_decode($request->getContent(), true);
|
||||
$orderedIds = $payload['orderedIds'] ?? null;
|
||||
|
||||
if (!is_array($orderedIds) || $orderedIds === []) {
|
||||
return new JsonResponse(['message' => 'orderedIds must be a non-empty array.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$parentId = $payload['parentId'] ?? null;
|
||||
$parent = null;
|
||||
if (null !== $parentId) {
|
||||
$parent = $this->entityManager->getRepository(Forum::class)->find($parentId);
|
||||
if (!$parent instanceof Forum) {
|
||||
return new JsonResponse(['message' => 'Parent not found.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
$forums = $this->entityManager->getRepository(Forum::class)
|
||||
->findBy(['id' => $orderedIds]);
|
||||
|
||||
if (count($forums) !== count($orderedIds)) {
|
||||
return new JsonResponse(['message' => 'Some forums were not found.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$forumsById = [];
|
||||
foreach ($forums as $forum) {
|
||||
$forumsById[(string) $forum->getId()] = $forum;
|
||||
}
|
||||
|
||||
foreach ($orderedIds as $id) {
|
||||
if (!isset($forumsById[(string) $id])) {
|
||||
return new JsonResponse(['message' => 'Invalid forum list.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$forum = $forumsById[(string) $id];
|
||||
$forumParent = $forum->getParent();
|
||||
if (($parent === null && $forumParent !== null) || ($parent !== null && $forumParent?->getId() !== $parent->getId())) {
|
||||
return new JsonResponse(['message' => 'Forums must share the same parent.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
$position = 1;
|
||||
foreach ($orderedIds as $id) {
|
||||
$forumsById[(string) $id]->setPosition($position);
|
||||
$position++;
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(['status' => 'ok']);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class FrontendController
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire('%kernel.project_dir%')]
|
||||
private string $projectDir
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/', name: 'frontend_index')]
|
||||
#[Route('/{path}', name: 'frontend_spa', requirements: ['path' => '^(?!api|app|_profiler|_wdt|bundles).+'])]
|
||||
public function __invoke(): Response
|
||||
{
|
||||
$indexPath = $this->projectDir . '/public/app/index.html';
|
||||
|
||||
if (!is_file($indexPath)) {
|
||||
return new Response(
|
||||
'Frontend build not found. Run `npm run build` in the frontend folder.',
|
||||
Response::HTTP_INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(file_get_contents($indexPath), Response::HTTP_OK, [
|
||||
'Content-Type' => 'text/html; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class I18nController
|
||||
{
|
||||
public function __construct(
|
||||
private TranslatorInterface $translator,
|
||||
#[Autowire('%kernel.default_locale%')]
|
||||
private string $defaultLocale
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route(
|
||||
'/api/i18n/{locale}',
|
||||
name: 'api_i18n',
|
||||
methods: ['GET'],
|
||||
requirements: ['locale' => '[A-Za-z0-9_-]+']
|
||||
)]
|
||||
public function __invoke(string $locale): JsonResponse
|
||||
{
|
||||
$messages = $this->getMessagesForLocale($locale);
|
||||
|
||||
if (!$messages && $locale !== $this->defaultLocale) {
|
||||
$messages = $this->getMessagesForLocale($this->defaultLocale);
|
||||
}
|
||||
|
||||
return new JsonResponse($messages);
|
||||
}
|
||||
|
||||
private function getMessagesForLocale(string $locale): array
|
||||
{
|
||||
$catalogue = $this->translator->getCatalogue($locale);
|
||||
$messages = $catalogue->all('messages');
|
||||
|
||||
$fallback = $catalogue->getFallbackCatalogue();
|
||||
while ($fallback) {
|
||||
foreach ($fallback->all('messages') as $key => $value) {
|
||||
if (!array_key_exists($key, $messages)) {
|
||||
$messages[$key] = $value;
|
||||
}
|
||||
}
|
||||
$fallback = $fallback->getFallbackCatalogue();
|
||||
}
|
||||
|
||||
ksort($messages);
|
||||
|
||||
return $messages;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Settings;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
final class VersionController
|
||||
{
|
||||
#[Route('/api/version', name: 'api_version', methods: ['GET'])]
|
||||
public function __invoke(EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$repository = $entityManager->getRepository(Settings::class);
|
||||
$version = $repository->findOneBy(['key' => 'version']);
|
||||
$build = $repository->findOneBy(['key' => 'build']);
|
||||
|
||||
return new JsonResponse([
|
||||
'version' => $version?->getValue(),
|
||||
'build' => $build ? (int) $build->getValue() : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
0
api/src/Entity/.gitignore
vendored
0
api/src/Entity/.gitignore
vendored
@@ -1,205 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\State\ForumPositionProcessor;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
//use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\Table(indexes: [
|
||||
new ORM\Index(name: 'idx_forum_parent_position', columns: ['parent_id', 'position']),
|
||||
new ORM\Index(name: 'idx_forum_type', columns: ['type']),
|
||||
])]
|
||||
#[ApiFilter(SearchFilter::class, properties: ['parent' => 'exact', 'type' => 'exact'])]
|
||||
#[ApiFilter(ExistsFilter::class, properties: ['parent'])]
|
||||
#[ApiResource(
|
||||
operations : [
|
||||
new Get(),
|
||||
new GetCollection(),
|
||||
new Post(security: "is_granted('ROLE_ADMIN')", processor: ForumPositionProcessor::class),
|
||||
new Patch(security: "is_granted('ROLE_ADMIN')", processor: ForumPositionProcessor::class),
|
||||
new Delete(security: "is_granted('ROLE_ADMIN')")
|
||||
],
|
||||
normalizationContext : ['groups' => ['forum:read']],
|
||||
denormalizationContext: ['groups' => ['forum:write']],
|
||||
order : ['position' => 'ASC']
|
||||
)]
|
||||
class Forum
|
||||
{
|
||||
public const TYPE_CATEGORY = 'category';
|
||||
public const TYPE_FORUM = 'forum';
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
#[Groups(['forum:read', 'thread:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['forum:read', 'forum:write', 'thread:read'])]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
#[Groups(['forum:read', 'forum:write'])]
|
||||
private ?string $description = null;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
#[Assert\Choice(choices: [self::TYPE_CATEGORY, self::TYPE_FORUM])]
|
||||
#[Groups(['forum:read', 'forum:write'])]
|
||||
private string $type = self::TYPE_CATEGORY;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
|
||||
#[Assert\Expression(
|
||||
"this.getParent() === null or this.getParent().isCategory()",
|
||||
message: "Parent must be a category."
|
||||
)]
|
||||
#[Groups(['forum:read', 'forum:write'])]
|
||||
private ?self $parent = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
|
||||
#[Groups(['forum:read'])]
|
||||
private Collection $children;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'forum', targetEntity: Thread::class)]
|
||||
#[Groups(['forum:read'])]
|
||||
private Collection $threads;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['forum:read'])]
|
||||
private int $position = 0;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['forum:read'])]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['forum:read'])]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
$this->threads = new ArrayCollection();
|
||||
}
|
||||
|
||||
#[ORM\PrePersist]
|
||||
public function onCreate(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$this->createdAt = $now;
|
||||
$this->updatedAt = $now;
|
||||
}
|
||||
|
||||
#[ORM\PreUpdate]
|
||||
public function onUpdate(): void
|
||||
{
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(?string $description): self
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(?self $parent): self
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Forum>
|
||||
*/
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function getPosition(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function setPosition(int $position): self
|
||||
{
|
||||
$this->position = $position;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Thread>
|
||||
*/
|
||||
public function getThreads(): Collection
|
||||
{
|
||||
return $this->threads;
|
||||
}
|
||||
|
||||
public function isCategory(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_CATEGORY;
|
||||
}
|
||||
|
||||
public function isForum(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_FORUM;
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post as PostOperation;
|
||||
use App\State\PostOwnerProcessor;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ApiFilter(SearchFilter::class, properties: ['thread' => 'exact'])]
|
||||
#[ApiResource(
|
||||
normalizationContext: ['groups' => ['post:read']],
|
||||
denormalizationContext: ['groups' => ['post:write']],
|
||||
operations: [
|
||||
new Get(),
|
||||
new GetCollection(),
|
||||
new PostOperation(
|
||||
security: "is_granted('ROLE_USER')",
|
||||
processor: PostOwnerProcessor::class
|
||||
),
|
||||
new Patch(security: "is_granted('ROLE_ADMIN') or object.getAuthor() == user"),
|
||||
new Delete(security: "is_granted('ROLE_ADMIN') or object.getAuthor() == user")
|
||||
]
|
||||
)]
|
||||
class Post
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
#[Groups(['post:read', 'thread:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['post:read', 'post:write', 'thread:read'])]
|
||||
private ?string $body = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Thread::class, inversedBy: 'posts')]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
#[Assert\NotNull]
|
||||
#[Groups(['post:read', 'post:write'])]
|
||||
private ?Thread $thread = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'posts')]
|
||||
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
||||
#[Groups(['post:read'])]
|
||||
private ?User $author = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['post:read'])]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['post:read'])]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
#[ORM\PrePersist]
|
||||
public function onCreate(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$this->createdAt = $now;
|
||||
$this->updatedAt = $now;
|
||||
}
|
||||
|
||||
#[ORM\PreUpdate]
|
||||
public function onUpdate(): void
|
||||
{
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getBody(): ?string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function setBody(string $body): self
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getThread(): ?Thread
|
||||
{
|
||||
return $this->thread;
|
||||
}
|
||||
|
||||
public function setThread(?Thread $thread): self
|
||||
{
|
||||
$this->thread = $thread;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAuthor(): ?User
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function setAuthor(?User $author): self
|
||||
{
|
||||
$this->author = $author;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ApiFilter(SearchFilter::class, properties: ['key' => 'exact'])]
|
||||
#[ApiResource(
|
||||
operations : [
|
||||
new Get(),
|
||||
new GetCollection(),
|
||||
new Patch(security: "is_granted('ROLE_ADMIN')")
|
||||
],
|
||||
normalizationContext : ['groups' => ['settings:read']],
|
||||
denormalizationContext: ['groups' => ['settings:write']]
|
||||
)]
|
||||
class Settings
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
#[Groups(['settings:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 100, unique: true)]
|
||||
#[Groups(['settings:read', 'settings:write'])]
|
||||
private string $key = '';
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
#[Groups(['settings:read', 'settings:write'])]
|
||||
private string $value = '';
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setKey(string $key): self
|
||||
{
|
||||
$this->key = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(string $value): self
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post as PostOperation;
|
||||
use App\State\ThreadOwnerProcessor;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ApiFilter(SearchFilter::class, properties: ['forum' => 'exact'])]
|
||||
#[ApiResource(
|
||||
normalizationContext: ['groups' => ['thread:read']],
|
||||
denormalizationContext: ['groups' => ['thread:write']],
|
||||
operations: [
|
||||
new Get(),
|
||||
new GetCollection(),
|
||||
new PostOperation(
|
||||
security: "is_granted('ROLE_USER')",
|
||||
processor: ThreadOwnerProcessor::class
|
||||
),
|
||||
new Patch(security: "is_granted('ROLE_ADMIN') or object.getAuthor() == user"),
|
||||
new Delete(security: "is_granted('ROLE_ADMIN') or object.getAuthor() == user")
|
||||
]
|
||||
)]
|
||||
class Thread
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
#[Groups(['thread:read', 'forum:read', 'post:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 200)]
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['thread:read', 'thread:write', 'forum:read', 'post:read'])]
|
||||
private ?string $title = null;
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['thread:read', 'thread:write'])]
|
||||
private ?string $body = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Forum::class, inversedBy: 'threads')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Assert\NotNull]
|
||||
#[Assert\Expression("this.getForum() and this.getForum().isForum()", message: "Thread must belong to a forum.")]
|
||||
#[Groups(['thread:read', 'thread:write'])]
|
||||
private ?Forum $forum = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'threads')]
|
||||
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
||||
#[Groups(['thread:read'])]
|
||||
private ?User $author = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['thread:read'])]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['thread:read'])]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'thread', targetEntity: Post::class)]
|
||||
#[Groups(['thread:read'])]
|
||||
private Collection $posts;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->posts = new ArrayCollection();
|
||||
}
|
||||
|
||||
#[ORM\PrePersist]
|
||||
public function onCreate(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$this->createdAt = $now;
|
||||
$this->updatedAt = $now;
|
||||
}
|
||||
|
||||
#[ORM\PreUpdate]
|
||||
public function onUpdate(): void
|
||||
{
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): self
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody(): ?string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function setBody(string $body): self
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getForum(): ?Forum
|
||||
{
|
||||
return $this->forum;
|
||||
}
|
||||
|
||||
public function setForum(?Forum $forum): self
|
||||
{
|
||||
$this->forum = $forum;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAuthor(): ?User
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function setAuthor(?User $author): self
|
||||
{
|
||||
$this->author = $author;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Post>
|
||||
*/
|
||||
public function getPosts(): Collection
|
||||
{
|
||||
return $this->posts;
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post as PostOperation;
|
||||
use App\State\UserPasswordHasherProcessor;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'users')]
|
||||
#[UniqueEntity(fields: ['email'])]
|
||||
#[UniqueEntity(fields: ['username'])]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ApiResource(
|
||||
normalizationContext: ['groups' => ['user:read']],
|
||||
denormalizationContext: ['groups' => ['user:write']],
|
||||
operations: [
|
||||
new Get(security: "is_granted('ROLE_ADMIN')"),
|
||||
new GetCollection(security: "is_granted('ROLE_ADMIN')"),
|
||||
new PostOperation(
|
||||
security: "is_granted('PUBLIC_ACCESS')",
|
||||
processor: UserPasswordHasherProcessor::class,
|
||||
validationContext: ['groups' => ['Default', 'user:create']]
|
||||
),
|
||||
new Patch(
|
||||
security: "is_granted('ROLE_ADMIN') or object == user",
|
||||
processor: UserPasswordHasherProcessor::class
|
||||
),
|
||||
new Delete(security: "is_granted('ROLE_ADMIN')")
|
||||
]
|
||||
)]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
#[Groups(['user:read', 'thread:read', 'post:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 180, unique: true)]
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Email]
|
||||
#[Groups(['user:read', 'user:write', 'thread:read', 'post:read'])]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(length: 50, unique: true)]
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(min: 3, max: 50)]
|
||||
#[Groups(['user:read', 'user:write', 'thread:read', 'post:read'])]
|
||||
private ?string $username = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private array $roles = [];
|
||||
|
||||
#[ORM\Column]
|
||||
private ?string $password = null;
|
||||
|
||||
#[Assert\NotBlank(groups: ['user:create'])]
|
||||
#[Assert\Length(min: 8)]
|
||||
#[Groups(['user:write'])]
|
||||
private ?string $plainPassword = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['user:read'])]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['user:read'])]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'author', targetEntity: Thread::class)]
|
||||
private Collection $threads;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'author', targetEntity: Post::class)]
|
||||
private Collection $posts;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->threads = new ArrayCollection();
|
||||
$this->posts = new ArrayCollection();
|
||||
}
|
||||
|
||||
#[ORM\PrePersist]
|
||||
public function onCreate(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$this->createdAt = $now;
|
||||
$this->updatedAt = $now;
|
||||
}
|
||||
|
||||
#[ORM\PreUpdate]
|
||||
public function onUpdate(): void
|
||||
{
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): self
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function setUsername(string $username): self
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return (string) $this->email;
|
||||
}
|
||||
|
||||
public function getRoles(): array
|
||||
{
|
||||
$roles = $this->roles;
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
public function setRoles(array $roles): self
|
||||
{
|
||||
$this->roles = $roles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(string $password): self
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPlainPassword(): ?string
|
||||
{
|
||||
return $this->plainPassword;
|
||||
}
|
||||
|
||||
public function setPlainPassword(?string $plainPassword): self
|
||||
{
|
||||
$this->plainPassword = $plainPassword;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
$this->plainPassword = null;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Thread>
|
||||
*/
|
||||
public function getThreads(): Collection
|
||||
{
|
||||
return $this->threads;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Post>
|
||||
*/
|
||||
public function getPosts(): Collection
|
||||
{
|
||||
return $this->posts;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use App\Entity\User;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
|
||||
#[AsEventListener(event: 'lexik_jwt_authentication.on_jwt_created')]
|
||||
class JwtCreatedSubscriber
|
||||
{
|
||||
public function __invoke(JWTCreatedEvent $event): void
|
||||
{
|
||||
$user = $event->getUser();
|
||||
if (!$user instanceof User) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = $event->getData();
|
||||
$payload['user_id'] = $user->getId();
|
||||
$payload['username'] = $user->getEmail();
|
||||
$payload['display_name'] = $user->getUsername();
|
||||
$event->setData($payload);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||
|
||||
class Kernel extends BaseKernel
|
||||
{
|
||||
use MicroKernelTrait;
|
||||
}
|
||||
0
api/src/Repository/.gitignore
vendored
0
api/src/Repository/.gitignore
vendored
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Forum;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
class ForumPositionProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
||||
private ProcessorInterface $persistProcessor
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
||||
{
|
||||
if (!$data instanceof Forum) {
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
|
||||
$previous = $context['previous_data'] ?? null;
|
||||
$parentChanged = $previous instanceof Forum && $previous->getParent()?->getId() !== $data->getParent()?->getId();
|
||||
|
||||
if ($data->getPosition() === 0 || $parentChanged) {
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
$qb->select('COALESCE(MAX(f.position), 0)')
|
||||
->from(Forum::class, 'f');
|
||||
|
||||
if ($data->getParent()) {
|
||||
$qb->andWhere('f.parent = :parent')
|
||||
->setParameter('parent', $data->getParent());
|
||||
} else {
|
||||
$qb->andWhere('f.parent IS NULL');
|
||||
}
|
||||
|
||||
$maxPosition = (int) $qb->getQuery()->getSingleScalarResult();
|
||||
$data->setPosition($maxPosition + 1);
|
||||
}
|
||||
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Post;
|
||||
use App\Entity\User;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
class PostOwnerProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
||||
private ProcessorInterface $persistProcessor
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
||||
{
|
||||
if ($data instanceof Post && null === $data->getAuthor()) {
|
||||
$user = $this->security->getUser();
|
||||
if ($user instanceof User) {
|
||||
$data->setAuthor($user);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Thread;
|
||||
use App\Entity\User;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
class ThreadOwnerProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
||||
private ProcessorInterface $persistProcessor
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
||||
{
|
||||
if ($data instanceof Thread && null === $data->getAuthor()) {
|
||||
$user = $this->security->getUser();
|
||||
if ($user instanceof User) {
|
||||
$data->setAuthor($user);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
class UserPasswordHasherProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private UserPasswordHasherInterface $passwordHasher,
|
||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
||||
private ProcessorInterface $persistProcessor
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
||||
{
|
||||
if ($data instanceof User && $data->getPlainPassword()) {
|
||||
$data->setPassword(
|
||||
$this->passwordHasher->hashPassword($data, $data->getPlainPassword())
|
||||
);
|
||||
$data->eraseCredentials();
|
||||
}
|
||||
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
}
|
||||
203
api/symfony.lock
203
api/symfony.lock
@@ -1,203 +0,0 @@
|
||||
{
|
||||
"api-platform/symfony": {
|
||||
"version": "4.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "4.0",
|
||||
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/api_platform.yaml",
|
||||
"config/routes/api_platform.yaml",
|
||||
"src/ApiResource/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/deprecations": {
|
||||
"version": "1.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
||||
}
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "3.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.0",
|
||||
"ref": "18ee08e513ba0303fd09a01fc1c934870af06ffa"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine.yaml",
|
||||
"src/Entity/.gitignore",
|
||||
"src/Repository/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-migrations-bundle": {
|
||||
"version": "4.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.1",
|
||||
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine_migrations.yaml",
|
||||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"lexik/jwt-authentication-bundle": {
|
||||
"version": "3.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.5",
|
||||
"ref": "e9481b233a11ef7e15fe055a2b21fd3ac1aa2bb7"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/lexik_jwt_authentication.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/apache-pack": {
|
||||
"version": "1.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "5d454ec6cc4c700ed3d963f3803e1d427d9669fb"
|
||||
}
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461"
|
||||
},
|
||||
"files": [
|
||||
"bin/console"
|
||||
]
|
||||
},
|
||||
"symfony/flex": {
|
||||
"version": "2.10",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.4",
|
||||
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
|
||||
},
|
||||
"files": [
|
||||
".env",
|
||||
".env.dev"
|
||||
]
|
||||
},
|
||||
"symfony/framework-bundle": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.4",
|
||||
"ref": "09f6e081c763a206802674ce0cb34a022f0ffc6d"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/cache.yaml",
|
||||
"config/packages/framework.yaml",
|
||||
"config/preload.php",
|
||||
"config/routes/framework.yaml",
|
||||
"config/services.yaml",
|
||||
"public/index.php",
|
||||
"src/Controller/.gitignore",
|
||||
"src/Kernel.php",
|
||||
".editorconfig"
|
||||
]
|
||||
},
|
||||
"symfony/property-info": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.3",
|
||||
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/property_info.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.4",
|
||||
"ref": "bc94c4fd86f393f3ab3947c18b830ea343e51ded"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/routing.yaml",
|
||||
"config/routes.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/security-bundle": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.4",
|
||||
"ref": "c42fee7802181cdd50f61b8622715829f5d2335c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/security.yaml",
|
||||
"config/routes/security.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/translation": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "620a1b84865ceb2ba304c8f8bf2a185fbf32a843"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/translation.yaml",
|
||||
"translations/.gitignore"
|
||||
]
|
||||
},
|
||||
"symfony/twig-bundle": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.4",
|
||||
"ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/twig.yaml",
|
||||
"templates/base.html.twig"
|
||||
]
|
||||
},
|
||||
"symfony/uid": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
|
||||
}
|
||||
},
|
||||
"symfony/validator": {
|
||||
"version": "8.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/validator.yaml"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
|
||||
{% block stylesheets %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
0
api/translations/.gitignore
vendored
0
api/translations/.gitignore
vendored
@@ -1,283 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: de\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
msgid "app.brand"
|
||||
msgstr "speedBB"
|
||||
|
||||
msgid "nav.forums"
|
||||
msgstr "Foren"
|
||||
|
||||
msgid "nav.login"
|
||||
msgstr "Anmelden"
|
||||
|
||||
msgid "nav.register"
|
||||
msgstr "Registrieren"
|
||||
|
||||
msgid "nav.logout"
|
||||
msgstr "Abmelden"
|
||||
|
||||
msgid "nav.language"
|
||||
msgstr "Sprache"
|
||||
|
||||
msgid "nav.theme"
|
||||
msgstr "Design"
|
||||
|
||||
msgid "nav.theme_auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "nav.theme_light"
|
||||
msgstr "Hell"
|
||||
|
||||
msgid "nav.theme_dark"
|
||||
msgstr "Dunkel"
|
||||
|
||||
msgid "home.hero_title"
|
||||
msgstr "Foren"
|
||||
|
||||
msgid "home.hero_body"
|
||||
msgstr "Entdecke Diskussionen, stelle Fragen und teile Ideen in Kategorien und Foren."
|
||||
|
||||
msgid "home.browse"
|
||||
msgstr "Foren durchsuchen"
|
||||
|
||||
msgid "home.loading"
|
||||
msgstr "Foren werden geladen..."
|
||||
|
||||
msgid "home.empty"
|
||||
msgstr "Noch keine Foren vorhanden. Lege das erste Forum in der API an."
|
||||
|
||||
msgid "forum.threads"
|
||||
msgstr "Threads"
|
||||
|
||||
msgid "forum.start_thread"
|
||||
msgstr "Thread starten"
|
||||
|
||||
msgid "forum.loading"
|
||||
msgstr "Forum wird geladen..."
|
||||
|
||||
msgid "forum.type_category"
|
||||
msgstr "Kategorie"
|
||||
|
||||
msgid "forum.type_forum"
|
||||
msgstr "Forum"
|
||||
|
||||
msgid "forum.no_description"
|
||||
msgstr "Noch keine Beschreibung vorhanden."
|
||||
|
||||
msgid "forum.empty_threads"
|
||||
msgstr "Noch keine Threads vorhanden. Starte unten einen."
|
||||
|
||||
msgid "forum.login_hint"
|
||||
msgstr "Melde dich an, um einen neuen Thread zu erstellen."
|
||||
|
||||
msgid "forum.open"
|
||||
msgstr "Forum öffnen"
|
||||
|
||||
msgid "forum.children"
|
||||
msgstr "Unterforen"
|
||||
|
||||
msgid "forum.empty_children"
|
||||
msgstr "Noch keine Unterforen vorhanden."
|
||||
|
||||
msgid "forum.only_forums"
|
||||
msgstr "Threads können nur in Foren erstellt werden."
|
||||
|
||||
msgid "thread.replies"
|
||||
msgstr "Antworten"
|
||||
|
||||
msgid "thread.reply"
|
||||
msgstr "Antworten"
|
||||
|
||||
msgid "thread.loading"
|
||||
msgstr "Thread wird geladen..."
|
||||
|
||||
msgid "thread.label"
|
||||
msgstr "Thread"
|
||||
|
||||
msgid "thread.category"
|
||||
msgstr "Forum:"
|
||||
|
||||
msgid "thread.back_to_category"
|
||||
msgstr "Zurück zum Forum"
|
||||
|
||||
msgid "thread.empty"
|
||||
msgstr "Sei die erste Person, die antwortet."
|
||||
|
||||
msgid "thread.anonymous"
|
||||
msgstr "Anonym"
|
||||
|
||||
msgid "thread.login_hint"
|
||||
msgstr "Melde dich an, um auf diesen Thread zu antworten."
|
||||
|
||||
msgid "thread.view"
|
||||
msgstr "Thread ansehen"
|
||||
|
||||
msgid "auth.login_title"
|
||||
msgstr "Anmelden"
|
||||
|
||||
msgid "auth.login_hint"
|
||||
msgstr "Melde dich an, um neue Threads zu starten und zu antworten."
|
||||
|
||||
msgid "auth.register_title"
|
||||
msgstr "Konto erstellen"
|
||||
|
||||
msgid "auth.register_hint"
|
||||
msgstr "Registriere dich mit E-Mail und einem eindeutigen Benutzernamen."
|
||||
|
||||
msgid "footer.copy"
|
||||
msgstr "speedBB"
|
||||
|
||||
msgid "form.title"
|
||||
msgstr "Titel"
|
||||
|
||||
msgid "form.body"
|
||||
msgstr "Inhalt"
|
||||
|
||||
msgid "form.message"
|
||||
msgstr "Nachricht"
|
||||
|
||||
msgid "form.email"
|
||||
msgstr "E-Mail"
|
||||
|
||||
msgid "form.username"
|
||||
msgstr "Benutzername"
|
||||
|
||||
msgid "form.password"
|
||||
msgstr "Passwort"
|
||||
|
||||
msgid "form.thread_title_placeholder"
|
||||
msgstr "Thema"
|
||||
|
||||
msgid "form.thread_body_placeholder"
|
||||
msgstr "Teile den Kontext und deine Frage."
|
||||
|
||||
msgid "form.reply_placeholder"
|
||||
msgstr "Schreibe deine Antwort."
|
||||
|
||||
msgid "form.posting"
|
||||
msgstr "Wird gesendet..."
|
||||
|
||||
msgid "form.create_thread"
|
||||
msgstr "Thread erstellen"
|
||||
|
||||
msgid "form.post_reply"
|
||||
msgstr "Antwort posten"
|
||||
|
||||
msgid "form.signing_in"
|
||||
msgstr "Anmeldung läuft..."
|
||||
|
||||
msgid "form.sign_in"
|
||||
msgstr "Anmelden"
|
||||
|
||||
msgid "form.registering"
|
||||
msgstr "Registrierung läuft..."
|
||||
|
||||
msgid "form.create_account"
|
||||
msgstr "Konto erstellen"
|
||||
|
||||
msgid "nav.acp"
|
||||
msgstr "ACP"
|
||||
|
||||
msgid "acp.title"
|
||||
msgstr "Administrationsbereich"
|
||||
|
||||
msgid "acp.no_access"
|
||||
msgstr "Du hast keinen Zugriff auf diesen Bereich."
|
||||
|
||||
msgid "acp.general"
|
||||
msgstr "Allgemein"
|
||||
|
||||
msgid "acp.general_hint"
|
||||
msgstr "Globale Einstellungen und Board-Konfiguration erscheinen hier."
|
||||
|
||||
msgid "acp.forums"
|
||||
msgstr "Foren"
|
||||
|
||||
msgid "acp.forums_hint"
|
||||
msgstr "Kategorien und Foren in einer Baumansicht verwalten."
|
||||
|
||||
msgid "acp.users"
|
||||
msgstr "Benutzer"
|
||||
|
||||
msgid "acp.users_hint"
|
||||
msgstr "Werkzeuge zur Benutzerverwaltung erscheinen hier."
|
||||
|
||||
msgid "acp.forums_tree"
|
||||
msgstr "Forenbaum"
|
||||
|
||||
msgid "acp.forums_empty"
|
||||
msgstr "Noch keine Foren vorhanden. Lege rechts das erste an."
|
||||
|
||||
msgid "acp.forums_create_title"
|
||||
msgstr "Forum oder Kategorie erstellen"
|
||||
|
||||
msgid "acp.forums_edit_title"
|
||||
msgstr "Forum bearbeiten"
|
||||
|
||||
msgid "acp.forums_type"
|
||||
msgstr "Typ"
|
||||
|
||||
msgid "acp.forums_parent"
|
||||
msgstr "Übergeordnete Kategorie"
|
||||
|
||||
msgid "acp.forums_parent_root"
|
||||
msgstr "Wurzel (kein Parent)"
|
||||
|
||||
msgid "acp.forums_confirm_delete"
|
||||
msgstr "Dieses Forum löschen? Das kann nicht rückgängig gemacht werden."
|
||||
|
||||
msgid "acp.loading"
|
||||
msgstr "Laden..."
|
||||
|
||||
msgid "acp.refresh"
|
||||
msgstr "Aktualisieren"
|
||||
|
||||
msgid "acp.create"
|
||||
msgstr "Erstellen"
|
||||
|
||||
msgid "acp.save"
|
||||
msgstr "Speichern"
|
||||
|
||||
msgid "acp.reset"
|
||||
msgstr "Zurücksetzen"
|
||||
|
||||
msgid "acp.edit"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
msgid "acp.delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
msgid "form.description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
msgid "acp.new_category"
|
||||
msgstr "Neue Kategorie"
|
||||
|
||||
msgid "acp.new_forum"
|
||||
msgstr "Neues Forum"
|
||||
|
||||
msgid "acp.forums_form_hint"
|
||||
msgstr "Erstelle ein neues Forum oder bearbeite das ausgewählte. Kategorien können Foren und andere Kategorien enthalten."
|
||||
|
||||
msgid "acp.forums_name_required"
|
||||
msgstr "Bitte zuerst einen Namen eingeben."
|
||||
|
||||
msgid "acp.drag_handle"
|
||||
msgstr "Zum Sortieren ziehen"
|
||||
|
||||
msgid "acp.expand_all"
|
||||
msgstr "Alle ausklappen"
|
||||
|
||||
msgid "acp.collapse_all"
|
||||
msgstr "Alle einklappen"
|
||||
|
||||
msgid "acp.forums_form_empty_title"
|
||||
msgstr "Keine Auswahl"
|
||||
|
||||
msgid "acp.forums_form_empty_hint"
|
||||
msgstr "Wähle ein Forum zum Bearbeiten oder klicke auf Neue Kategorie / Neues Forum."
|
||||
|
||||
msgid "acp.cancel"
|
||||
msgstr "Abbrechen"
|
||||
@@ -1,283 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
msgid "app.brand"
|
||||
msgstr "speedBB"
|
||||
|
||||
msgid "nav.forums"
|
||||
msgstr "Forums"
|
||||
|
||||
msgid "nav.login"
|
||||
msgstr "Login"
|
||||
|
||||
msgid "nav.register"
|
||||
msgstr "Register"
|
||||
|
||||
msgid "nav.logout"
|
||||
msgstr "Logout"
|
||||
|
||||
msgid "nav.language"
|
||||
msgstr "Language"
|
||||
|
||||
msgid "nav.theme"
|
||||
msgstr "Theme"
|
||||
|
||||
msgid "nav.theme_auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "nav.theme_light"
|
||||
msgstr "Light"
|
||||
|
||||
msgid "nav.theme_dark"
|
||||
msgstr "Dark"
|
||||
|
||||
msgid "home.hero_title"
|
||||
msgstr "Forums"
|
||||
|
||||
msgid "home.hero_body"
|
||||
msgstr "Explore conversations, ask questions, and share ideas across categories and forums."
|
||||
|
||||
msgid "home.browse"
|
||||
msgstr "Browse forums"
|
||||
|
||||
msgid "home.loading"
|
||||
msgstr "Loading forums..."
|
||||
|
||||
msgid "home.empty"
|
||||
msgstr "No forums yet. Create the first one in the API."
|
||||
|
||||
msgid "forum.threads"
|
||||
msgstr "Threads"
|
||||
|
||||
msgid "forum.start_thread"
|
||||
msgstr "Start a thread"
|
||||
|
||||
msgid "forum.loading"
|
||||
msgstr "Loading forum..."
|
||||
|
||||
msgid "forum.type_category"
|
||||
msgstr "Category"
|
||||
|
||||
msgid "forum.type_forum"
|
||||
msgstr "Forum"
|
||||
|
||||
msgid "forum.no_description"
|
||||
msgstr "No description added yet."
|
||||
|
||||
msgid "forum.empty_threads"
|
||||
msgstr "No threads here yet. Start one below."
|
||||
|
||||
msgid "forum.login_hint"
|
||||
msgstr "Log in to create a new thread."
|
||||
|
||||
msgid "forum.open"
|
||||
msgstr "Open forum"
|
||||
|
||||
msgid "forum.children"
|
||||
msgstr "Sub-forums"
|
||||
|
||||
msgid "forum.empty_children"
|
||||
msgstr "No sub-forums yet."
|
||||
|
||||
msgid "forum.only_forums"
|
||||
msgstr "Threads can only be created in forums."
|
||||
|
||||
msgid "thread.replies"
|
||||
msgstr "Replies"
|
||||
|
||||
msgid "thread.reply"
|
||||
msgstr "Reply"
|
||||
|
||||
msgid "thread.loading"
|
||||
msgstr "Loading thread..."
|
||||
|
||||
msgid "thread.label"
|
||||
msgstr "Thread"
|
||||
|
||||
msgid "thread.category"
|
||||
msgstr "Forum:"
|
||||
|
||||
msgid "thread.back_to_category"
|
||||
msgstr "Back to forum"
|
||||
|
||||
msgid "thread.empty"
|
||||
msgstr "Be the first to reply."
|
||||
|
||||
msgid "thread.anonymous"
|
||||
msgstr "Anonymous"
|
||||
|
||||
msgid "thread.login_hint"
|
||||
msgstr "Log in to reply to this thread."
|
||||
|
||||
msgid "thread.view"
|
||||
msgstr "View thread"
|
||||
|
||||
msgid "auth.login_title"
|
||||
msgstr "Log in"
|
||||
|
||||
msgid "auth.login_hint"
|
||||
msgstr "Access your account to start new threads and reply."
|
||||
|
||||
msgid "auth.register_title"
|
||||
msgstr "Create account"
|
||||
|
||||
msgid "auth.register_hint"
|
||||
msgstr "Register with an email and a unique username."
|
||||
|
||||
msgid "footer.copy"
|
||||
msgstr "speedBB"
|
||||
|
||||
msgid "form.title"
|
||||
msgstr "Title"
|
||||
|
||||
msgid "form.body"
|
||||
msgstr "Body"
|
||||
|
||||
msgid "form.message"
|
||||
msgstr "Message"
|
||||
|
||||
msgid "form.email"
|
||||
msgstr "Email"
|
||||
|
||||
msgid "form.username"
|
||||
msgstr "Username"
|
||||
|
||||
msgid "form.password"
|
||||
msgstr "Password"
|
||||
|
||||
msgid "form.thread_title_placeholder"
|
||||
msgstr "Topic headline"
|
||||
|
||||
msgid "form.thread_body_placeholder"
|
||||
msgstr "Share the context and your question."
|
||||
|
||||
msgid "form.reply_placeholder"
|
||||
msgstr "Share your reply."
|
||||
|
||||
msgid "form.posting"
|
||||
msgstr "Posting..."
|
||||
|
||||
msgid "form.create_thread"
|
||||
msgstr "Create thread"
|
||||
|
||||
msgid "form.post_reply"
|
||||
msgstr "Post reply"
|
||||
|
||||
msgid "form.signing_in"
|
||||
msgstr "Signing in..."
|
||||
|
||||
msgid "form.sign_in"
|
||||
msgstr "Sign in"
|
||||
|
||||
msgid "form.registering"
|
||||
msgstr "Registering..."
|
||||
|
||||
msgid "form.create_account"
|
||||
msgstr "Create account"
|
||||
|
||||
msgid "nav.acp"
|
||||
msgstr "ACP"
|
||||
|
||||
msgid "acp.title"
|
||||
msgstr "Admin control panel"
|
||||
|
||||
msgid "acp.no_access"
|
||||
msgstr "You do not have access to this area."
|
||||
|
||||
msgid "acp.general"
|
||||
msgstr "General"
|
||||
|
||||
msgid "acp.general_hint"
|
||||
msgstr "Global settings and board configuration will appear here."
|
||||
|
||||
msgid "acp.forums"
|
||||
msgstr "Forums"
|
||||
|
||||
msgid "acp.forums_hint"
|
||||
msgstr "Manage categories and forums from a tree view."
|
||||
|
||||
msgid "acp.users"
|
||||
msgstr "Users"
|
||||
|
||||
msgid "acp.users_hint"
|
||||
msgstr "User management tools will appear here."
|
||||
|
||||
msgid "acp.forums_tree"
|
||||
msgstr "Forum tree"
|
||||
|
||||
msgid "acp.forums_empty"
|
||||
msgstr "No forums yet. Create the first one on the right."
|
||||
|
||||
msgid "acp.forums_create_title"
|
||||
msgstr "Create forum or category"
|
||||
|
||||
msgid "acp.forums_edit_title"
|
||||
msgstr "Edit forum"
|
||||
|
||||
msgid "acp.forums_type"
|
||||
msgstr "Type"
|
||||
|
||||
msgid "acp.forums_parent"
|
||||
msgstr "Parent category"
|
||||
|
||||
msgid "acp.forums_parent_root"
|
||||
msgstr "Root (no parent)"
|
||||
|
||||
msgid "acp.forums_confirm_delete"
|
||||
msgstr "Delete this forum? This cannot be undone."
|
||||
|
||||
msgid "acp.loading"
|
||||
msgstr "Loading..."
|
||||
|
||||
msgid "acp.refresh"
|
||||
msgstr "Refresh"
|
||||
|
||||
msgid "acp.create"
|
||||
msgstr "Create"
|
||||
|
||||
msgid "acp.save"
|
||||
msgstr "Save"
|
||||
|
||||
msgid "acp.reset"
|
||||
msgstr "Reset"
|
||||
|
||||
msgid "acp.edit"
|
||||
msgstr "Edit"
|
||||
|
||||
msgid "acp.delete"
|
||||
msgstr "Delete"
|
||||
|
||||
msgid "form.description"
|
||||
msgstr "Description"
|
||||
|
||||
msgid "acp.new_category"
|
||||
msgstr "New category"
|
||||
|
||||
msgid "acp.new_forum"
|
||||
msgstr "New forum"
|
||||
|
||||
msgid "acp.forums_form_hint"
|
||||
msgstr "Create a new forum or edit the selected one. Categories can contain forums and other categories."
|
||||
|
||||
msgid "acp.forums_name_required"
|
||||
msgstr "Please enter a name before saving."
|
||||
|
||||
msgid "acp.drag_handle"
|
||||
msgstr "Drag to reorder"
|
||||
|
||||
msgid "acp.expand_all"
|
||||
msgstr "Expand all"
|
||||
|
||||
msgid "acp.collapse_all"
|
||||
msgstr "Collapse all"
|
||||
|
||||
msgid "acp.forums_form_empty_title"
|
||||
msgstr "No selection"
|
||||
|
||||
msgid "acp.forums_form_empty_hint"
|
||||
msgstr "Choose a forum to edit or click New category / New forum to create one."
|
||||
|
||||
msgid "acp.cancel"
|
||||
msgstr "Cancel"
|
||||
40
app/Actions/Fortify/CreateNewUser.php
Normal file
40
app/Actions/Fortify/CreateNewUser.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
|
||||
class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function create(array $input): User
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class),
|
||||
],
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
return User::create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
18
app/Actions/Fortify/PasswordValidationRules.php
Normal file
18
app/Actions/Fortify/PasswordValidationRules.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
trait PasswordValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate passwords.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||
*/
|
||||
protected function passwordRules(): array
|
||||
{
|
||||
return ['required', 'string', Password::default(), 'confirmed'];
|
||||
}
|
||||
}
|
||||
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||
|
||||
class ResetUserPassword implements ResetsUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||
|
||||
class UpdateUserPassword implements UpdatesUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'current_password' => ['required', 'string', 'current_password:web'],
|
||||
'password' => $this->passwordRules(),
|
||||
], [
|
||||
'current_password.current_password' => __('The provided password does not match your current password.'),
|
||||
])->validateWithBag('updatePassword');
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
58
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
58
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
|
||||
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
{
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($user->id),
|
||||
],
|
||||
])->validateWithBag('updateProfileInformation');
|
||||
|
||||
if ($input['email'] !== $user->email &&
|
||||
$user instanceof MustVerifyEmail) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given verified user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
protected function updateVerifiedUser(User $user, array $input): void
|
||||
{
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'email_verified_at' => null,
|
||||
])->save();
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
}
|
||||
71
app/Http/Controllers/AuthController.php
Normal file
71
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Fortify\CreateNewUser;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function register(Request $request, CreateNewUser $creator): JsonResponse
|
||||
{
|
||||
$input = [
|
||||
'name' => $request->input('name') ?? $request->input('username'),
|
||||
'email' => $request->input('email'),
|
||||
'password' => $request->input('password') ?? $request->input('plainPassword'),
|
||||
'password_confirmation' => $request->input('password_confirmation') ?? $request->input('plainPassword'),
|
||||
];
|
||||
|
||||
$user = $creator->create($input);
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
|
||||
return response()->json([
|
||||
'user_id' => $user->id,
|
||||
'email' => $user->email,
|
||||
'message' => 'Verification email sent.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
|
||||
if (!$user || !Hash::check($request->input('password'), $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => ['Invalid credentials.'],
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$user->hasVerifiedEmail()) {
|
||||
return response()->json([
|
||||
'message' => 'Email not verified.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$token = $user->createToken('api')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'token' => $token,
|
||||
'user_id' => $user->id,
|
||||
'email' => $user->email,
|
||||
'roles' => $user->roles()->pluck('name')->values(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$request->user()?->currentAccessToken()?->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
191
app/Http/Controllers/ForumController.php
Normal file
191
app/Http/Controllers/ForumController.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Forum;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ForumController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = Forum::query()->withoutTrashed();
|
||||
|
||||
$parentParam = $request->query('parent');
|
||||
if (is_array($parentParam) && array_key_exists('exists', $parentParam)) {
|
||||
$exists = filter_var($parentParam['exists'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
if ($exists === false) {
|
||||
$query->whereNull('parent_id');
|
||||
} elseif ($exists === true) {
|
||||
$query->whereNotNull('parent_id');
|
||||
}
|
||||
} elseif (is_string($parentParam)) {
|
||||
$parentId = $this->parseIriId($parentParam);
|
||||
if ($parentId !== null) {
|
||||
$query->where('parent_id', $parentId);
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->filled('type')) {
|
||||
$query->where('type', $request->query('type'));
|
||||
}
|
||||
|
||||
$forums = $query
|
||||
->orderBy('position')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (Forum $forum) => $this->serializeForum($forum));
|
||||
|
||||
return response()->json($forums);
|
||||
}
|
||||
|
||||
public function show(Forum $forum): JsonResponse
|
||||
{
|
||||
return response()->json($this->serializeForum($forum));
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'type' => ['required', Rule::in(['category', 'forum'])],
|
||||
'parent' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$parentId = $this->parseIriId($data['parent'] ?? null);
|
||||
|
||||
if ($data['type'] === 'forum' && !$parentId) {
|
||||
return response()->json(['message' => 'Forums must belong to a category.'], 422);
|
||||
}
|
||||
|
||||
if ($parentId) {
|
||||
$parent = Forum::findOrFail($parentId);
|
||||
if ($parent->type !== 'category') {
|
||||
return response()->json(['message' => 'Parent must be a category.'], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$position = Forum::where('parent_id', $parentId)->max('position');
|
||||
|
||||
$forum = Forum::create([
|
||||
'name' => $data['name'],
|
||||
'description' => $data['description'] ?? null,
|
||||
'type' => $data['type'],
|
||||
'parent_id' => $parentId,
|
||||
'position' => ($position ?? 0) + 1,
|
||||
]);
|
||||
|
||||
return response()->json($this->serializeForum($forum), 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, Forum $forum): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => ['sometimes', 'required', 'string', 'max:100'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'type' => ['sometimes', Rule::in(['category', 'forum'])],
|
||||
'parent' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$parentId = $this->parseIriId($data['parent'] ?? null);
|
||||
$nextType = $data['type'] ?? $forum->type;
|
||||
$nextParentId = array_key_exists('parent', $data) ? $parentId : $forum->parent_id;
|
||||
|
||||
if ($nextType === 'forum' && !$nextParentId) {
|
||||
return response()->json(['message' => 'Forums must belong to a category.'], 422);
|
||||
}
|
||||
|
||||
if (array_key_exists('parent', $data)) {
|
||||
if ($parentId) {
|
||||
$parent = Forum::findOrFail($parentId);
|
||||
if ($parent->type !== 'category') {
|
||||
return response()->json(['message' => 'Parent must be a category.'], 422);
|
||||
}
|
||||
}
|
||||
$forum->parent_id = $parentId;
|
||||
}
|
||||
|
||||
if (array_key_exists('name', $data)) {
|
||||
$forum->name = $data['name'];
|
||||
}
|
||||
|
||||
if (array_key_exists('description', $data)) {
|
||||
$forum->description = $data['description'];
|
||||
}
|
||||
|
||||
if (array_key_exists('type', $data)) {
|
||||
$forum->type = $data['type'];
|
||||
}
|
||||
|
||||
$forum->save();
|
||||
|
||||
return response()->json($this->serializeForum($forum));
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Forum $forum): JsonResponse
|
||||
{
|
||||
$forum->deleted_by = $request->user()?->id;
|
||||
$forum->save();
|
||||
$forum->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
public function reorder(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'parentId' => ['nullable'],
|
||||
'orderedIds' => ['required', 'array'],
|
||||
'orderedIds.*' => ['integer'],
|
||||
]);
|
||||
|
||||
$parentId = $data['parentId'] ?? null;
|
||||
if ($parentId === '' || $parentId === 'null') {
|
||||
$parentId = null;
|
||||
} elseif ($parentId !== null) {
|
||||
$parentId = (int) $parentId;
|
||||
}
|
||||
|
||||
foreach ($data['orderedIds'] as $index => $forumId) {
|
||||
Forum::where('id', $forumId)
|
||||
->where('parent_id', $parentId)
|
||||
->update(['position' => $index + 1]);
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'ok']);
|
||||
}
|
||||
|
||||
private function parseIriId(?string $value): ?int
|
||||
{
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('#/forums/(\d+)$#', $value, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function serializeForum(Forum $forum): array
|
||||
{
|
||||
return [
|
||||
'id' => $forum->id,
|
||||
'name' => $forum->name,
|
||||
'description' => $forum->description,
|
||||
'type' => $forum->type,
|
||||
'parent' => $forum->parent_id ? "/api/forums/{$forum->parent_id}" : null,
|
||||
'position' => $forum->position,
|
||||
'created_at' => $forum->created_at?->toIso8601String(),
|
||||
'updated_at' => $forum->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
22
app/Http/Controllers/I18nController.php
Normal file
22
app/Http/Controllers/I18nController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class I18nController extends Controller
|
||||
{
|
||||
public function __invoke(string $locale): JsonResponse
|
||||
{
|
||||
$path = resource_path("lang/{$locale}.json");
|
||||
|
||||
if (!File::exists($path)) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
$contents = File::get($path);
|
||||
|
||||
return response()->json(json_decode($contents, true, 512, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
}
|
||||
88
app/Http/Controllers/PostController.php
Normal file
88
app/Http/Controllers/PostController.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
use App\Models\Thread;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PostController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = Post::query()->withoutTrashed();
|
||||
|
||||
$threadParam = $request->query('thread');
|
||||
if (is_string($threadParam)) {
|
||||
$threadId = $this->parseIriId($threadParam);
|
||||
if ($threadId !== null) {
|
||||
$query->where('thread_id', $threadId);
|
||||
}
|
||||
}
|
||||
|
||||
$posts = $query
|
||||
->oldest('created_at')
|
||||
->get()
|
||||
->map(fn (Post $post) => $this->serializePost($post));
|
||||
|
||||
return response()->json($posts);
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'body' => ['required', 'string'],
|
||||
'thread' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
$threadId = $this->parseIriId($data['thread']);
|
||||
$thread = Thread::findOrFail($threadId);
|
||||
|
||||
$post = Post::create([
|
||||
'thread_id' => $thread->id,
|
||||
'user_id' => $request->user()?->id,
|
||||
'body' => $data['body'],
|
||||
]);
|
||||
|
||||
return response()->json($this->serializePost($post), 201);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Post $post): JsonResponse
|
||||
{
|
||||
$post->deleted_by = $request->user()?->id;
|
||||
$post->save();
|
||||
$post->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
private function parseIriId(?string $value): ?int
|
||||
{
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('#/threads/(\d+)$#', $value, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function serializePost(Post $post): array
|
||||
{
|
||||
return [
|
||||
'id' => $post->id,
|
||||
'body' => $post->body,
|
||||
'thread' => "/api/threads/{$post->thread_id}",
|
||||
'user_id' => $post->user_id,
|
||||
'created_at' => $post->created_at?->toIso8601String(),
|
||||
'updated_at' => $post->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
53
app/Http/Controllers/SettingController.php
Normal file
53
app/Http/Controllers/SettingController.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SettingController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = Setting::query();
|
||||
|
||||
if ($request->filled('key')) {
|
||||
$query->where('key', $request->query('key'));
|
||||
}
|
||||
|
||||
$settings = $query->get()->map(fn (Setting $setting) => [
|
||||
'id' => $setting->id,
|
||||
'key' => $setting->key,
|
||||
'value' => $setting->value,
|
||||
]);
|
||||
|
||||
return response()->json($settings);
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) {
|
||||
return response()->json(['message' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'key' => ['required', 'string', 'max:191'],
|
||||
'value' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$value = $data['value'] ?? '';
|
||||
|
||||
$setting = Setting::updateOrCreate(
|
||||
['key' => $data['key']],
|
||||
['value' => $value]
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'id' => $setting->id,
|
||||
'key' => $setting->key,
|
||||
'value' => $setting->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
102
app/Http/Controllers/ThreadController.php
Normal file
102
app/Http/Controllers/ThreadController.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Forum;
|
||||
use App\Models\Thread;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ThreadController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = Thread::query()->withoutTrashed()->with('user');
|
||||
|
||||
$forumParam = $request->query('forum');
|
||||
if (is_string($forumParam)) {
|
||||
$forumId = $this->parseIriId($forumParam);
|
||||
if ($forumId !== null) {
|
||||
$query->where('forum_id', $forumId);
|
||||
}
|
||||
}
|
||||
|
||||
$threads = $query
|
||||
->latest('created_at')
|
||||
->get()
|
||||
->map(fn (Thread $thread) => $this->serializeThread($thread));
|
||||
|
||||
return response()->json($threads);
|
||||
}
|
||||
|
||||
public function show(Thread $thread): JsonResponse
|
||||
{
|
||||
$thread->loadMissing('user');
|
||||
return response()->json($this->serializeThread($thread));
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'title' => ['required', 'string'],
|
||||
'body' => ['required', 'string'],
|
||||
'forum' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
$forumId = $this->parseIriId($data['forum']);
|
||||
$forum = Forum::findOrFail($forumId);
|
||||
|
||||
if ($forum->type !== 'forum') {
|
||||
return response()->json(['message' => 'Threads can only be created inside forums.'], 422);
|
||||
}
|
||||
|
||||
$thread = Thread::create([
|
||||
'forum_id' => $forum->id,
|
||||
'user_id' => $request->user()?->id,
|
||||
'title' => $data['title'],
|
||||
'body' => $data['body'],
|
||||
]);
|
||||
|
||||
return response()->json($this->serializeThread($thread), 201);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Thread $thread): JsonResponse
|
||||
{
|
||||
$thread->deleted_by = $request->user()?->id;
|
||||
$thread->save();
|
||||
$thread->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
private function parseIriId(?string $value): ?int
|
||||
{
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('#/forums/(\d+)$#', $value, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function serializeThread(Thread $thread): array
|
||||
{
|
||||
return [
|
||||
'id' => $thread->id,
|
||||
'title' => $thread->title,
|
||||
'body' => $thread->body,
|
||||
'forum' => "/api/forums/{$thread->forum_id}",
|
||||
'user_id' => $thread->user_id,
|
||||
'user_name' => $thread->user?->name,
|
||||
'created_at' => $thread->created_at?->toIso8601String(),
|
||||
'updated_at' => $thread->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
48
app/Http/Controllers/UploadController.php
Normal file
48
app/Http/Controllers/UploadController.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class UploadController extends Controller
|
||||
{
|
||||
public function storeLogo(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) {
|
||||
return response()->json(['message' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'file' => ['required', 'file', 'mimes:jpg,jpeg,png,gif,webp,svg,ico', 'max:5120'],
|
||||
]);
|
||||
|
||||
$path = $data['file']->store('logos', 'public');
|
||||
|
||||
return response()->json([
|
||||
'path' => $path,
|
||||
'url' => Storage::url($path),
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeFavicon(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) {
|
||||
return response()->json(['message' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'file' => ['required', 'file', 'mimes:png,ico', 'max:2048'],
|
||||
]);
|
||||
|
||||
$path = $data['file']->store('favicons', 'public');
|
||||
|
||||
return response()->json([
|
||||
'path' => $path,
|
||||
'url' => Storage::url($path),
|
||||
]);
|
||||
}
|
||||
}
|
||||
25
app/Http/Controllers/UserController.php
Normal file
25
app/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$users = User::query()
|
||||
->with('roles')
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->map(fn (User $user) => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'roles' => $user->roles->pluck('name')->values(),
|
||||
]);
|
||||
|
||||
return response()->json($users);
|
||||
}
|
||||
}
|
||||
47
app/Http/Controllers/UserSettingController.php
Normal file
47
app/Http/Controllers/UserSettingController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\UserSetting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserSettingController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$query = UserSetting::query()->where('user_id', $user->id);
|
||||
|
||||
if ($request->filled('key')) {
|
||||
$query->where('key', $request->query('key'));
|
||||
}
|
||||
|
||||
$settings = $query->get()->map(fn (UserSetting $setting) => [
|
||||
'id' => $setting->id,
|
||||
'key' => $setting->key,
|
||||
'value' => $setting->value,
|
||||
]);
|
||||
|
||||
return response()->json($settings);
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'key' => ['required', 'string', 'max:191'],
|
||||
'value' => ['nullable', 'array'],
|
||||
]);
|
||||
|
||||
$setting = UserSetting::updateOrCreate(
|
||||
['user_id' => $request->user()->id, 'key' => $data['key']],
|
||||
['value' => $data['value'] ?? []]
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'id' => $setting->id,
|
||||
'key' => $setting->key,
|
||||
'value' => $setting->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
20
app/Http/Controllers/VersionController.php
Normal file
20
app/Http/Controllers/VersionController.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class VersionController extends Controller
|
||||
{
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
$version = Setting::where('key', 'version')->value('value');
|
||||
$build = Setting::where('key', 'build')->value('value');
|
||||
|
||||
return response()->json([
|
||||
'version' => $version,
|
||||
'build' => $build !== null ? (int) $build : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
63
app/Models/Forum.php
Normal file
63
app/Models/Forum.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property string $type
|
||||
* @property int|null $parent_id
|
||||
* @property int $position
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Forum> $children
|
||||
* @property-read int|null $children_count
|
||||
* @property-read Forum|null $parent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Thread> $threads
|
||||
* @property-read int|null $threads_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereParentId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum wherePosition($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Forum extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'type',
|
||||
'parent_id',
|
||||
'position',
|
||||
];
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id')->orderBy('position');
|
||||
}
|
||||
|
||||
public function threads(): HasMany
|
||||
{
|
||||
return $this->hasMany(Thread::class);
|
||||
}
|
||||
}
|
||||
48
app/Models/Post.php
Normal file
48
app/Models/Post.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $thread_id
|
||||
* @property int|null $user_id
|
||||
* @property string $body
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Thread $thread
|
||||
* @property-read \App\Models\User|null $user
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereBody($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereThreadId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereUserId($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Post extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'thread_id',
|
||||
'user_id',
|
||||
'body',
|
||||
];
|
||||
|
||||
public function thread(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Thread::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
34
app/Models/Role.php
Normal file
34
app/Models/Role.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
|
||||
* @property-read int|null $users_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Role extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
];
|
||||
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
}
|
||||
29
app/Models/Setting.php
Normal file
29
app/Models/Setting.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $key
|
||||
* @property string $value
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereKey($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereValue($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Setting extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'value',
|
||||
];
|
||||
}
|
||||
59
app/Models/Thread.php
Normal file
59
app/Models/Thread.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $forum_id
|
||||
* @property int|null $user_id
|
||||
* @property string $title
|
||||
* @property string $body
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Forum $forum
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Post> $posts
|
||||
* @property-read int|null $posts_count
|
||||
* @property-read \App\Models\User|null $user
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereBody($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereForumId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereTitle($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereUserId($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Thread extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'forum_id',
|
||||
'user_id',
|
||||
'title',
|
||||
'body',
|
||||
];
|
||||
|
||||
public function forum(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Forum::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function posts(): HasMany
|
||||
{
|
||||
return $this->hasMany(Post::class);
|
||||
}
|
||||
}
|
||||
90
app/Models/User.php
Normal file
90
app/Models/User.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property \Illuminate\Support\Carbon|null $email_verified_at
|
||||
* @property string $password
|
||||
* @property string|null $two_factor_secret
|
||||
* @property string|null $two_factor_recovery_codes
|
||||
* @property string|null $two_factor_confirmed_at
|
||||
* @property string|null $remember_token
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Role> $roles
|
||||
* @property-read int|null $roles_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Laravel\Sanctum\PersonalAccessToken> $tokens
|
||||
* @property-read int|null $tokens_count
|
||||
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerifiedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePassword($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRememberToken($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereTwoFactorConfirmedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereTwoFactorRecoveryCodes($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereTwoFactorSecret($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Role::class);
|
||||
}
|
||||
}
|
||||
18
app/Models/UserSetting.php
Normal file
18
app/Models/UserSetting.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserSetting extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'key',
|
||||
'value',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'value' => 'array',
|
||||
];
|
||||
}
|
||||
25
app/Providers/AppServiceProvider.php
Normal file
25
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Model::preventLazyLoading(true);
|
||||
}
|
||||
}
|
||||
48
app/Providers/FortifyServiceProvider.php
Normal file
48
app/Providers/FortifyServiceProvider.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Fortify\CreateNewUser;
|
||||
use App\Actions\Fortify\ResetUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserProfileInformation;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
|
||||
use Laravel\Fortify\Fortify;
|
||||
|
||||
class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Fortify::createUsersUsing(CreateNewUser::class);
|
||||
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
|
||||
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
|
||||
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
||||
Fortify::redirectUserForTwoFactorAuthenticationUsing(RedirectIfTwoFactorAuthenticatable::class);
|
||||
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
$throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
|
||||
|
||||
return Limit::perMinute(5)->by($throttleKey);
|
||||
});
|
||||
|
||||
RateLimiter::for('two-factor', function (Request $request) {
|
||||
return Limit::perMinute(5)->by($request->session()->get('login.id'));
|
||||
});
|
||||
}
|
||||
}
|
||||
18
artisan
Normal file
18
artisan
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the command...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
$status = $app->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
19
bootstrap/app.php
Normal file
19
bootstrap/app.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
//
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
})->create();
|
||||
65
bootstrap/cache/packages.php
vendored
Normal file
65
bootstrap/cache/packages.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php return array (
|
||||
'barryvdh/laravel-ide-helper' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/fortify' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Fortify\\FortifyServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/pail' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/sail' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Sail\\SailServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/sanctum' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/tinker' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
),
|
||||
),
|
||||
'nesbot/carbon' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
),
|
||||
),
|
||||
'nunomaduro/collision' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
),
|
||||
),
|
||||
'nunomaduro/termwind' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
),
|
||||
),
|
||||
);
|
||||
275
bootstrap/cache/services.php
vendored
Normal file
275
bootstrap/cache/services.php
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
<?php return array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Illuminate\\Auth\\AuthServiceProvider',
|
||||
1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
|
||||
2 => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
3 => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
4 => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
5 => 'Illuminate\\Concurrency\\ConcurrencyServiceProvider',
|
||||
6 => 'Illuminate\\Cookie\\CookieServiceProvider',
|
||||
7 => 'Illuminate\\Database\\DatabaseServiceProvider',
|
||||
8 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
|
||||
9 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
|
||||
10 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
|
||||
11 => 'Illuminate\\Hashing\\HashServiceProvider',
|
||||
12 => 'Illuminate\\Mail\\MailServiceProvider',
|
||||
13 => 'Illuminate\\Notifications\\NotificationServiceProvider',
|
||||
14 => 'Illuminate\\Pagination\\PaginationServiceProvider',
|
||||
15 => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
|
||||
16 => 'Illuminate\\Pipeline\\PipelineServiceProvider',
|
||||
17 => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
18 => 'Illuminate\\Redis\\RedisServiceProvider',
|
||||
19 => 'Illuminate\\Session\\SessionServiceProvider',
|
||||
20 => 'Illuminate\\Translation\\TranslationServiceProvider',
|
||||
21 => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
22 => 'Illuminate\\View\\ViewServiceProvider',
|
||||
23 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
24 => 'Laravel\\Fortify\\FortifyServiceProvider',
|
||||
25 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
26 => 'Laravel\\Sail\\SailServiceProvider',
|
||||
27 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
28 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
29 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
30 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
31 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
32 => 'App\\Providers\\AppServiceProvider',
|
||||
33 => 'App\\Providers\\FortifyServiceProvider',
|
||||
),
|
||||
'eager' =>
|
||||
array (
|
||||
0 => 'Illuminate\\Auth\\AuthServiceProvider',
|
||||
1 => 'Illuminate\\Cookie\\CookieServiceProvider',
|
||||
2 => 'Illuminate\\Database\\DatabaseServiceProvider',
|
||||
3 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
|
||||
4 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
|
||||
5 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
|
||||
6 => 'Illuminate\\Notifications\\NotificationServiceProvider',
|
||||
7 => 'Illuminate\\Pagination\\PaginationServiceProvider',
|
||||
8 => 'Illuminate\\Session\\SessionServiceProvider',
|
||||
9 => 'Illuminate\\View\\ViewServiceProvider',
|
||||
10 => 'Laravel\\Fortify\\FortifyServiceProvider',
|
||||
11 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
12 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
13 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
14 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
15 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
16 => 'App\\Providers\\AppServiceProvider',
|
||||
17 => 'App\\Providers\\FortifyServiceProvider',
|
||||
),
|
||||
'deferred' =>
|
||||
array (
|
||||
'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
|
||||
'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
|
||||
'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
|
||||
'Illuminate\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'Illuminate\\Contracts\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'Illuminate\\Bus\\BatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'Illuminate\\Bus\\DatabaseBatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'cache' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'cache.store' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'cache.psr6' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'memcached.connector' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'Illuminate\\Cache\\RateLimiter' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\AboutCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\ForgetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Auth\\Console\\ClearResetsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\DbCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\PruneCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\ShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\WipeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\DownCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EnvironmentCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EnvironmentDecryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EnvironmentEncryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Concurrency\\Console\\InvokeSerializedClosureCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\KeyGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\OptimizeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\OptimizeClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\PackageDiscoverCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\PruneStaleTagsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ListFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\FlushFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PauseCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RestartCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ResumeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\WorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ReloadCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\DumpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Seeds\\SeedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleFinishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleRunCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleClearCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleTestCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleWorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleInterruptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\ShowModelCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\StorageLinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\StorageUnlinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\UpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ViewCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ViewClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ApiInstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\BroadcastingInstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\CacheTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\CastMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ChannelListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ClassMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ComponentMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConsoleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Routing\\Console\\ControllerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\DocsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EnumMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ExceptionMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Factories\\FactoryMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\InterfaceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\JobMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\JobMiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\LangPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ListenerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\MailMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Routing\\Console\\MiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ModelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\NotificationMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Notifications\\Console\\NotificationTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ObserverMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\PolicyMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ProviderMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\FailedTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\BatchesTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RequestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ResourceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RuleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ScopeMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Seeds\\SeederMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Session\\Console\\SessionTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ServeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\StubPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\TestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\TraitMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\VendorPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ViewMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'migration.repository' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'migration.creator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Migrations\\Migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\MigrateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\FreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\InstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\RefreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\ResetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\RollbackCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\StatusCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\MigrateMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'composer' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Concurrency\\ConcurrencyManager' => 'Illuminate\\Concurrency\\ConcurrencyServiceProvider',
|
||||
'hash' => 'Illuminate\\Hashing\\HashServiceProvider',
|
||||
'hash.driver' => 'Illuminate\\Hashing\\HashServiceProvider',
|
||||
'mail.manager' => 'Illuminate\\Mail\\MailServiceProvider',
|
||||
'mailer' => 'Illuminate\\Mail\\MailServiceProvider',
|
||||
'Illuminate\\Mail\\Markdown' => 'Illuminate\\Mail\\MailServiceProvider',
|
||||
'auth.password' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
|
||||
'auth.password.broker' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
|
||||
'Illuminate\\Contracts\\Pipeline\\Hub' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
|
||||
'pipeline' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
|
||||
'queue' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
'queue.connection' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
'queue.failer' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
'queue.listener' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
'queue.worker' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
'redis' => 'Illuminate\\Redis\\RedisServiceProvider',
|
||||
'redis.connection' => 'Illuminate\\Redis\\RedisServiceProvider',
|
||||
'translator' => 'Illuminate\\Translation\\TranslationServiceProvider',
|
||||
'translation.loader' => 'Illuminate\\Translation\\TranslationServiceProvider',
|
||||
'validator' => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
'validation.presence' => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
'Barryvdh\\LaravelIdeHelper\\Console\\GeneratorCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Barryvdh\\LaravelIdeHelper\\Console\\ModelsCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Barryvdh\\LaravelIdeHelper\\Console\\MetaCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Barryvdh\\LaravelIdeHelper\\Console\\EloquentCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Laravel\\Sail\\Console\\InstallCommand' => 'Laravel\\Sail\\SailServiceProvider',
|
||||
'Laravel\\Sail\\Console\\PublishCommand' => 'Laravel\\Sail\\SailServiceProvider',
|
||||
'command.tinker' => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
),
|
||||
'when' =>
|
||||
array (
|
||||
'Illuminate\\Broadcasting\\BroadcastServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Bus\\BusServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Cache\\CacheServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Concurrency\\ConcurrencyServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Hashing\\HashServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Mail\\MailServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Pipeline\\PipelineServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Queue\\QueueServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Redis\\RedisServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Translation\\TranslationServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Illuminate\\Validation\\ValidationServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Laravel\\Sail\\SailServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Laravel\\Tinker\\TinkerServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
),
|
||||
);
|
||||
6
bootstrap/providers.php
Normal file
6
bootstrap/providers.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
];
|
||||
89
composer.json
Normal file
89
composer.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"$schema": "https://getcomposer.org/schema.json",
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/fortify": "*",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "*",
|
||||
"laravel/tinker": "^2.10.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.6",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"phpunit/phpunit": "^11.5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"setup": [
|
||||
"composer install",
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
|
||||
"@php artisan key:generate",
|
||||
"@php artisan migrate --force",
|
||||
"npm install",
|
||||
"npm run build"
|
||||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
|
||||
],
|
||||
"test": [
|
||||
"@php artisan config:clear --ansi",
|
||||
"@php artisan test"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi",
|
||||
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||
"@php artisan migrate --graceful --ansi"
|
||||
],
|
||||
"pre-package-uninstall": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
9019
composer.lock
generated
Normal file
9019
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
126
config/app.php
Normal file
126
config/app.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application, which will be used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| other UI elements where an application name needs to be displayed.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. The timezone
|
||||
| is set to "UTC" by default as it is suitable for most use cases.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by Laravel's translation / localization methods. This option can be
|
||||
| set to any locale for which you plan to have translation strings.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is utilized by Laravel's encryption services and should be set
|
||||
| to a random, 32 character string to ensure that all encrypted values
|
||||
| are secure. You should do this prior to deploying the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'previous_keys' => [
|
||||
...array_filter(
|
||||
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
|
||||
),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user