Merge branch 'main' into demo

This commit is contained in:
Christopher C. Wells 2021-04-24 14:21:29 -07:00
commit 76fb2c8b86
69 changed files with 1996 additions and 546 deletions

16
.dockerignore Normal file
View File

@ -0,0 +1,16 @@
*
!app
!bootstrap
!config
!database
!elastic
!public
!resources
!routes
!storage
!artisan
!ATTRIBUTIONS.md
!composer.json
!composer.lock
!LICENSE
!README.md

View File

@ -9,11 +9,13 @@ LOG_LEVEL=debug
DB_CONNECTION=sqlite
SCOUT_DRIVER=elastic
ELASTIC_HOST=localhost:9200
MEDIA_DISK=media
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
SCOUT_DRIVER=elastic
ELASTIC_HOST=localhost:9200

64
.env.example Normal file
View File

@ -0,0 +1,64 @@
#
# Kcal application configuration.
#
APP_NAME=kcal
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://127.0.0.1
APP_PORT=8080
APP_SERVICE=app
APP_TIMEZONE=UTC
#
# Databases configuration.
#
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=kcal
DB_USERNAME=kcal
DB_PASSWORD=kcal
REDIS_HOST=redis
REDIS_PORT=6379
#
# Search configuration.
#
#SCOUT_DRIVER=null
#SCOUT_DRIVER=algolia
#ALGOLIA_APP_ID=
#ALGOLIA_SECRET=
SCOUT_DRIVER=elastic
ELASTIC_HOST=elasticsearch:9200
ELASTIC_PORT=9200
#
# Media (image storage) configuration.
#
MEDIA_DISK=media
#MEDIA_DISK=s3-public
#AWS_ACCESS_KEY_ID=
#AWS_SECRET_ACCESS_KEY=
#AWS_DEFAULT_REGION=
#AWS_BUCKET=
#
# Misc. drivers and configuration.
#
BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
LOG_CHANNEL=stack
LOG_LEVEL=debug
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120

View File

@ -1,32 +0,0 @@
# Local env file assumes Sail is in use. See docker-compose.yml.
APP_NAME=kcal
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://127.0.0.1
APP_PORT=8080
APP_SERVICE=app
APP_TIMEZONE=UTC
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=kcal
DB_USERNAME=kcal
DB_PASSWORD=kcal
REDIS_HOST=redis
REDIS_PORT=6379
SCOUT_DRIVER=elastic
ELASTIC_HOST=elasticsearch:9200
ELASTIC_PORT=9200
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

View File

@ -1,37 +0,0 @@
APP_NAME=kcal
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=
APP_TIMEZONE=UTC
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=
DB_PORT=3306
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=
#REDIS_URL=
#REDIS_HOST=
#REDIS_PASSWORD=
#REDIS_PORT=6379
#REDIS_DB=
SCOUT_DRIVER=null
#SCOUT_DRIVER=algolia
#ALGOLIA_APP_ID=
#ALGOLIA_SECRET=
#SCOUT_DRIVER=elastic
#ELASTIC_HOST=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

View File

@ -56,7 +56,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -88,6 +88,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -200,7 +201,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -264,7 +265,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -296,6 +297,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -408,7 +410,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -472,7 +474,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -504,6 +506,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -616,7 +619,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -680,7 +683,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -712,6 +715,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -824,7 +828,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -888,7 +892,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -920,6 +924,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -1032,7 +1037,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -1096,7 +1101,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -1128,6 +1133,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -1240,7 +1246,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -1304,7 +1310,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -1336,6 +1342,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -1448,7 +1455,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -1512,7 +1519,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -1544,6 +1551,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -1656,7 +1664,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -1720,7 +1728,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -1752,6 +1760,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -1864,7 +1873,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -1928,7 +1937,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -1960,6 +1969,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -2072,7 +2082,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,
@ -2136,7 +2146,7 @@ namespace PHPSTORM_META {
'Illuminate\Console\Scheduling\ScheduleTestCommand' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class,
'Illuminate\Console\Scheduling\ScheduleWorkCommand' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class,
'Illuminate\Contracts\Auth\Access\Gate' => \Illuminate\Auth\Access\Gate::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\LogBroadcaster::class,
'Illuminate\Contracts\Broadcasting\Broadcaster' => \Illuminate\Broadcasting\Broadcasters\RedisBroadcaster::class,
'Illuminate\Contracts\Console\Kernel' => \App\Console\Kernel::class,
'Illuminate\Contracts\Debug\ExceptionHandler' => \NunoMaduro\Collision\Adapters\Laravel\ExceptionHandler::class,
'Illuminate\Contracts\Http\Kernel' => \App\Http\Kernel::class,
@ -2168,6 +2178,7 @@ namespace PHPSTORM_META {
'auth.password.broker' => \Illuminate\Auth\Passwords\PasswordBroker::class,
'blade.compiler' => \Illuminate\View\Compilers\BladeCompiler::class,
'cache' => \Illuminate\Cache\CacheManager::class,
'cache.dynamodb.client' => \Aws\DynamoDb\DynamoDbClient::class,
'cache.store' => \Illuminate\Cache\Repository::class,
'command.auth.resets.clear' => \Illuminate\Auth\Console\ClearResetsCommand::class,
'command.cache.clear' => \Illuminate\Cache\Console\ClearCommand::class,
@ -2280,7 +2291,7 @@ namespace PHPSTORM_META {
'migration.repository' => \Illuminate\Database\Migrations\DatabaseMigrationRepository::class,
'migrator' => \Illuminate\Database\Migrations\Migrator::class,
'queue' => \Illuminate\Queue\QueueManager::class,
'queue.connection' => \Illuminate\Queue\SyncQueue::class,
'queue.connection' => \Illuminate\Queue\RedisQueue::class,
'queue.failer' => \Illuminate\Queue\Failed\DatabaseUuidFailedJobProvider::class,
'queue.listener' => \Illuminate\Queue\Listener::class,
'queue.worker' => \Illuminate\Queue\Worker::class,

68
Dockerfile Normal file
View File

@ -0,0 +1,68 @@
ARG MEDIA_LIBRARY_DEPS="jpegoptim optipng pngquant gifsicle"
FROM php:8.0-fpm-alpine
ARG MEDIA_LIBRARY_DEPS
RUN apk add --no-cache --virtual \
.build-deps \
${PHPIZE_DEPS} \
${MEDIA_LIBRARY_DEPS} \
bash \
freetype-dev \
git \
icu-dev \
icu-libs \
libjpeg-turbo-dev \
libpng-dev \
libzip-dev \
mysql-client \
oniguruma-dev \
openssh-client \
openssl \
pcre-dev \
postgresql-dev \
rsync \
zlib-dev
# Configure php extensions.
RUN docker-php-ext-configure gd --with-freetype --with-jpeg
# Install php extensions.
RUN docker-php-ext-install \
bcmath \
calendar \
exif \
gd \
intl \
pdo_mysql \
pdo_pgsql \
pcntl \
zip
# Install PECL extensions.
RUN pecl install redis
RUN docker-php-ext-enable redis
# Install composer.
ENV COMPOSER_HOME /composer
ENV PATH ./vendor/bin:/composer/vendor/bin:$PATH
ENV COMPOSER_ALLOW_SUPERUSER 1
RUN curl -s https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer
# Add user and group.
RUN addgroup -S -g 1000 www
RUN adduser -S -u 1000 -s /bin/bash -G www www
# Setup working directory.
WORKDIR /app
COPY --chown=www:www . /app
# Install dependencies.
RUN composer install --optimize-autoloader --no-dev
# Change current user to www.
USER www
# Expose port 9000 and start php-fpm server.
EXPOSE 9000
CMD ["php-fpm"]

View File

@ -3,7 +3,7 @@
/**
* A helper file for Laravel, to provide autocomplete information to your IDE
* Generated for Laravel 8.36.2.
* Generated for Laravel 8.38.0.
*
* This file should not be included in your code, only analyzed by your IDE!
*
@ -3679,50 +3679,6 @@
{
/** @var \Illuminate\Cache\Repository $instance */
return $instance->macroCall($method, $parameters);
}
/**
* Remove all items from the cache.
*
* @return bool
* @static
*/
public static function flush()
{
/** @var \Illuminate\Cache\FileStore $instance */
return $instance->flush();
}
/**
* Get the Filesystem instance.
*
* @return \Illuminate\Filesystem\Filesystem
* @static
*/
public static function getFilesystem()
{
/** @var \Illuminate\Cache\FileStore $instance */
return $instance->getFilesystem();
}
/**
* Get the working directory of the cache.
*
* @return string
* @static
*/
public static function getDirectory()
{
/** @var \Illuminate\Cache\FileStore $instance */
return $instance->getDirectory();
}
/**
* Get the cache key prefix.
*
* @return string
* @static
*/
public static function getPrefix()
{
/** @var \Illuminate\Cache\FileStore $instance */
return $instance->getPrefix();
}
/**
* Get a lock instance.
@ -3735,7 +3691,7 @@
*/
public static function lock($name, $seconds = 0, $owner = null)
{
/** @var \Illuminate\Cache\FileStore $instance */
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->lock($name, $seconds, $owner);
}
/**
@ -3748,8 +3704,99 @@
*/
public static function restoreLock($name, $owner)
{
/** @var \Illuminate\Cache\FileStore $instance */
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->restoreLock($name, $owner);
}
/**
* Remove all items from the cache.
*
* @return bool
* @static
*/
public static function flush()
{
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->flush();
}
/**
* Get the Redis connection instance.
*
* @return \Illuminate\Redis\Connections\Connection
* @static
*/
public static function connection()
{
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->connection();
}
/**
* Get the Redis connection instance that should be used to manage locks.
*
* @return \Illuminate\Redis\Connections\Connection
* @static
*/
public static function lockConnection()
{
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->lockConnection();
}
/**
* Specify the name of the connection that should be used to store data.
*
* @param string $connection
* @return void
* @static
*/
public static function setConnection($connection)
{
/** @var \Illuminate\Cache\RedisStore $instance */
$instance->setConnection($connection);
}
/**
* Specify the name of the connection that should be used to manage locks.
*
* @param string $connection
* @return \Illuminate\Cache\RedisStore
* @static
*/
public static function setLockConnection($connection)
{
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->setLockConnection($connection);
}
/**
* Get the Redis database instance.
*
* @return \Illuminate\Contracts\Redis\Factory
* @static
*/
public static function getRedis()
{
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->getRedis();
}
/**
* Get the cache key prefix.
*
* @return string
* @static
*/
public static function getPrefix()
{
/** @var \Illuminate\Cache\RedisStore $instance */
return $instance->getPrefix();
}
/**
* Set the cache key prefix.
*
* @param string $prefix
* @return void
* @static
*/
public static function setPrefix($prefix)
{
/** @var \Illuminate\Cache\RedisStore $instance */
$instance->setPrefix($prefix);
}
}
@ -6509,6 +6556,8 @@
* @method static \Illuminate\Http\Client\PendingRequest withoutVerifying()
* @method static \Illuminate\Http\Client\PendingRequest dump()
* @method static \Illuminate\Http\Client\PendingRequest dd()
* @method static \Illuminate\Http\Client\PendingRequest async()
* @method static \Illuminate\Http\Client\Pool pool(callable $callback)
* @method static \Illuminate\Http\Client\Response delete(string $url, array $data = [])
* @method static \Illuminate\Http\Client\Response get(string $url, array $query = [])
* @method static \Illuminate\Http\Client\Response head(string $url, array $query = [])
@ -8368,6 +8417,92 @@
{
/** @var \Illuminate\Support\Testing\Fakes\QueueFake $instance */
return $instance->setConnectionName($name);
}
/**
* Migrate the delayed jobs that are ready to the regular queue.
*
* @param string $from
* @param string $to
* @return array
* @static
*/
public static function migrateExpiredJobs($from, $to)
{
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->migrateExpiredJobs($from, $to);
}
/**
* Delete a reserved job from the queue.
*
* @param string $queue
* @param \Illuminate\Queue\Jobs\RedisJob $job
* @return void
* @static
*/
public static function deleteReserved($queue, $job)
{
/** @var \Illuminate\Queue\RedisQueue $instance */
$instance->deleteReserved($queue, $job);
}
/**
* Delete a reserved job from the reserved queue and release it.
*
* @param string $queue
* @param \Illuminate\Queue\Jobs\RedisJob $job
* @param int $delay
* @return void
* @static
*/
public static function deleteAndRelease($queue, $job, $delay)
{
/** @var \Illuminate\Queue\RedisQueue $instance */
$instance->deleteAndRelease($queue, $job, $delay);
}
/**
* Delete all of the jobs from the queue.
*
* @param string $queue
* @return int
* @static
*/
public static function clear($queue)
{
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->clear($queue);
}
/**
* Get the queue or return the default.
*
* @param string|null $queue
* @return string
* @static
*/
public static function getQueue($queue)
{
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->getQueue($queue);
}
/**
* Get the connection for the queue.
*
* @return \Illuminate\Redis\Connections\Connection
* @static
*/
public static function getConnection()
{
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->getConnection();
}
/**
* Get the underlying Redis instance.
*
* @return \Illuminate\Contracts\Redis\Factory
* @static
*/
public static function getRedis()
{
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->getRedis();
}
/**
* Get the backoff for an object-based queue handler.
@ -8378,7 +8513,7 @@
*/
public static function getJobBackoff($job)
{ //Method inherited from \Illuminate\Queue\Queue
/** @var \Illuminate\Queue\SyncQueue $instance */
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->getJobBackoff($job);
}
/**
@ -8390,7 +8525,7 @@
*/
public static function getJobExpiration($job)
{ //Method inherited from \Illuminate\Queue\Queue
/** @var \Illuminate\Queue\SyncQueue $instance */
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->getJobExpiration($job);
}
/**
@ -8402,7 +8537,7 @@
*/
public static function createPayloadUsing($callback)
{ //Method inherited from \Illuminate\Queue\Queue
\Illuminate\Queue\SyncQueue::createPayloadUsing($callback);
\Illuminate\Queue\RedisQueue::createPayloadUsing($callback);
}
/**
* Get the container instance being used by the connection.
@ -8412,7 +8547,7 @@
*/
public static function getContainer()
{ //Method inherited from \Illuminate\Queue\Queue
/** @var \Illuminate\Queue\SyncQueue $instance */
/** @var \Illuminate\Queue\RedisQueue $instance */
return $instance->getContainer();
}
/**
@ -8424,7 +8559,7 @@
*/
public static function setContainer($container)
{ //Method inherited from \Illuminate\Queue\Queue
/** @var \Illuminate\Queue\SyncQueue $instance */
/** @var \Illuminate\Queue\RedisQueue $instance */
$instance->setContainer($container);
}
@ -10217,7 +10352,7 @@
*
* @param string|null $key
* @param string|array|null $default
* @return string|array
* @return string|array|null
* @static
*/
public static function old($key = null, $default = null)
@ -12368,6 +12503,18 @@
{
/** @var \Illuminate\Session\Store $instance */
return $instance->exists($key);
}
/**
* Determine if the given key is missing from the session data.
*
* @param string|array $key
* @return bool
* @static
*/
public static function missing($key)
{
/** @var \Illuminate\Session\Store $instance */
return $instance->missing($key);
}
/**
* Checks if a key is present and not null.
@ -16154,6 +16301,18 @@ namespace {
return \Illuminate\Database\Eloquent\Builder::hasGlobalMacro($name);
}
/**
* Clone the Eloquent query builder.
*
* @return static
* @static
*/
public static function clone()
{
/** @var \Illuminate\Database\Eloquent\Builder $instance */
return $instance->clone();
}
/**
* Add a relationship count / exists condition to the query.
*
@ -16714,7 +16873,7 @@ namespace {
/**
* Add a subselect expression to the query.
*
* @param \Closure|\Illuminate\Database\Query\Builder|string $query
* @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query
* @param string $as
* @return \Illuminate\Database\Query\Builder
* @throws \InvalidArgumentException
@ -18510,18 +18669,6 @@ namespace {
return $instance->useWritePdo();
}
/**
* Clone the query.
*
* @return static
* @static
*/
public static function clone()
{
/** @var \Illuminate\Database\Query\Builder $instance */
return $instance->clone();
}
/**
* Clone the query without the given properties.
*

View File

@ -38,10 +38,34 @@
"SCOUT_DRIVER": {
"description": "Search driver ('algolia', 'elastic', or 'null').",
"value": "null"
},
"MEDIA_DISK": {
"description": "Media disk. Set to 's3-public' for recipe/user image support.",
"value": "local"
},
"AWS_BUCKET": {
"description": "AWS bucket name for recipe/user image storage. Required when MEDIA_DISK is 's3-public'.",
"value": "",
"required": false
},
"AWS_DEFAULT_REGION": {
"description": "AWS region for AWS_BUCKET. Required when MEDIA_DISK is 's3-public'.",
"value": "",
"required": false
},
"AWS_ACCESS_KEY_ID": {
"description": "AWS access key ID for AWS_BUCKET. Required when MEDIA_DISK is 's3-public'.",
"value": "",
"required": false
},
"AWS_SECRET_ACCESS_KEY": {
"description": "AWS secret key ID for AWS_ACCESS_KEY_ID. Required when MEDIA_DISK is 's3-public'.",
"value": "",
"required": false
}
},
"scripts": {
"postdeploy": "php artisan migrate --force && php artisan user:add kcal kcal --name=Admin"
"postdeploy": "php artisan migrate --force && php artisan user:add kcal kcal --name=Admin --admin"
},
"success_url": "/"
}

View File

@ -15,7 +15,8 @@ class UserAdd extends Command
protected $signature = 'user:add
{username? : Username}
{password? : Password}
{--name= : User short name}';
{--name= : User short name};
{--admin : Makes the user an admin}';
/**
* @inheritdoc
@ -69,13 +70,14 @@ class UserAdd extends Command
$options = $this->options();
if (!$options['name']) {
$options['name'] = $this->ask('Enter a name for the user (optional)');
$options['name'] = $this->ask('Enter a name for the user', $arguments['username']);
}
User::create([
'username' => $arguments['username'],
'password' => Hash::make($arguments['password']),
'name' => $options['name'] ?: $arguments['username'],
'admin' => $options['admin'],
'remember_token' => Str::random(10),
])->save();

View File

@ -65,6 +65,7 @@ class FoodController extends Controller
['value' => 'tsp', 'label' => 'tsp.'],
['value' => 'tbsp', 'label' => 'tbsp.'],
['value' => 'cup', 'label' => 'cup'],
['value' => 'oz', 'label' => 'oz'],
]));
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Traits\UpdatesUser;
use App\Http\Requests\UpdateUserRequest;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
class ProfileController extends Controller
{
use UpdatesUser;
/**
* Display a user profile page.
*/
public function show(User $user): View
{
return view('profiles.show')->with('user', $user);
}
/**
* Show the form for editing a user's profile data.
*/
public function edit(User $user): View
{
return view('profiles.edit')->with('user', $user);
}
/**
* Update the user profile data.
*/
public function update(UpdateUserRequest $request, User $user): RedirectResponse
{
$this->updateUser($request, $user);
$user->refresh();
session()->flash('message', "Profile updated!");
return redirect()->route('profiles.show', $user);
}
}

View File

@ -201,11 +201,14 @@ class RecipeController extends Controller
'description_delta' => $input['description_delta'],
'servings' => (int) $input['servings'],
'weight' => $input['weight'],
'volume' => Number::floatFromString($input['volume']),
'volume' => $input['volume'],
'time_prep' => (int) $input['time_prep'],
'time_cook' => (int) $input['time_cook'],
'source' => $input['source'],
]);
if (!empty($input['volume'])) {
$recipe->volume = Number::floatFromString($input['volume']);
}
try {
DB::transaction(function () use ($input, $recipe, $request) {

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Traits;
use App\Http\Requests\UpdateUserRequest;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
trait UpdatesUser
{
/**
* Updates a user from a user update request.
*/
public function updateUser(UpdateUserRequest $request, User $user): void {
$input = $request->validated();
$input['remember_token'] = Str::random(10);
if (!empty($input['password'])) {
$input['password'] = Hash::make($input['password']);
}
else {
unset($input['password']);
}
// Maintain the existing value if it is not on the form.
$input['admin'] = $input['admin'] ?? ($user->exists && $user->admin);
$user->fill($input)->save();
// Handle image.
if (!empty($input['image'])) {
/** @var \Illuminate\Http\UploadedFile $file */
$file = $input['image'];
$user->clearMediaCollection();
$user
->addMediaFromRequest('image')
->usingName($user->username)
->usingFileName("{$user->slug}.{$file->extension()}")
->toMediaCollection();
}
elseif (isset($input['remove_image']) && $input['remove_image']) {
$user->clearMediaCollection();
}
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Traits\UpdatesUser;
use App\Http\Requests\UpdateUserRequest;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
class UserController extends Controller
{
use UpdatesUser;
/**
* Display a listing of the resource.
*/
public function index(): View
{
return view('users.index')->with('users', User::all());
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function create(): View
{
return $this->edit(new User());
}
/**
* Store a newly created resource in storage.
*/
public function store(UpdateUserRequest $request): RedirectResponse
{
return $this->update($request, new User());
}
/**
* Show the form for editing the specified resource.
*/
public function edit(User $user): View
{
return view('users.edit')->with('user', $user);
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateUserRequest $request, User $user): RedirectResponse
{
$this->updateUser($request, $user);
session()->flash('message', "User {$user->name} updated!");
return redirect()->route('users.index');
}
/**
* Confirm removal of specified resource.
*/
public function delete(User $user): View
{
$this->authorize('delete', $user);
return view('users.delete')->with('user', $user);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(User $user): RedirectResponse
{
$this->authorize('delete', $user);
$user->delete();
return redirect(route('users.index'))
->with('message', "User {$user->name} deleted!");
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdateUserRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$rules = [
'username' => ['required', 'string', Rule::unique('users')->ignore($this->user)],
'name' => ['required', 'string'],
'password' => ['nullable', 'string', 'confirmed'],
'password_confirmation' => ['nullable', 'string'],
'admin' => ['nullable', 'boolean'],
'image' => ['nullable', 'file', 'mimes:jpg,png,gif'],
'remove_image' => ['nullable', 'boolean'],
];
if (!$this->user) {
$rules['password'] = ['required', 'string', 'confirmed'];
$rules['password_confirmation'] = ['required', 'string'];
}
return $rules;
}
}

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Models\Traits\Sluggable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
@ -9,14 +10,19 @@ use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
/**
* App\Models\User
*
* @property int $id
* @property string $slug
* @property string $name
* @property string $username
* @property string $password
* @property bool $admin
* @property string|null $remember_token
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
@ -26,6 +32,8 @@ use Illuminate\Support\Facades\Auth;
* @property-read int|null $journal_entries_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
* @property-read int|null $notifications_count
* @property-read \Spatie\MediaLibrary\MediaCollections\Models\Collections\MediaCollection|Media[] $media
* @property-read int|null $media_count
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
@ -37,11 +45,18 @@ use Illuminate\Support\Facades\Auth;
* @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUsername($value)
* @method static \Illuminate\Database\Eloquent\Builder|User findSimilarSlugs(string $attribute, array $config, string $slug)
* @method static \Illuminate\Database\Eloquent\Builder|User whereAdmin($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereSlug($value)
* @method static \Illuminate\Database\Eloquent\Builder|User withUniqueSlugConstraints(\Illuminate\Database\Eloquent\Model $model, string $attribute, array $config, string $slug)
* @mixin \Eloquent
*/
final class User extends Authenticatable
final class User extends Authenticatable implements HasMedia
{
use HasFactory, Notifiable;
use HasFactory;
use InteractsWithMedia;
use Notifiable;
use Sluggable;
/**
* @inheritdoc
@ -50,6 +65,7 @@ final class User extends Authenticatable
'username',
'password',
'name',
'admin',
];
/**
@ -60,6 +76,21 @@ final class User extends Authenticatable
'remember_token',
];
/**
* @inheritdoc
*/
protected $casts = [
'admin' => 'bool',
];
/**
* @inheritdoc
*/
public function sluggable(): array
{
return ['slug' => ['source' => 'username']];
}
/**
* Get the User's goals.
*/
@ -100,4 +131,20 @@ final class User extends Authenticatable
});
return $goals;
}
/**
* Defines conversions for the User image.
*
* @throws \Spatie\Image\Exceptions\InvalidManipulation
*
* @see https://spatie.be/docs/laravel-medialibrary/v9/converting-images/defining-conversions
*/
public function registerMediaConversions(Media $media = null): void
{
$this->addMediaConversion('icon')
->width(300)
->height(300)
->sharpen(10)
->optimize();
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Policies;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can administer (the application).
*/
public function administer(User $user): bool {
return $user->admin;
}
/**
* Determine whether the user can edit a user via the "profile".
*/
public function editProfile(User $user, User $model): bool {
return $user->id === $model->id;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, User $model): bool
{
return $this->administer($user) && $user->id !== $model->id;
}
}

View File

@ -2,18 +2,18 @@
namespace App\Providers;
use App\Models\User;
use App\Policies\UserPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
* @inheritdoc
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class,
];
/**
@ -24,7 +24,5 @@ class AuthServiceProvider extends ServiceProvider
public function boot()
{
$this->registerPolicies();
//
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\View\Components\Inputs;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Model;
use Illuminate\View\Component;
class Image extends Component
{
public Model $model;
public ?string $previewName;
/**
* Image constructor.
*/
public function __construct(Model $model, string $previewName = 'preview') {
$this->model = $model;
$this->previewName = $previewName;
}
public function render(): View
{
return view('components.inputs.image')
->with('model', $this->model)
->with('previewName', $this->previewName);
}
}

View File

@ -24,6 +24,7 @@
"laravel/framework": "^8.12",
"laravel/scout": "^8.6",
"laravel/tinker": "^2.5",
"league/flysystem-aws-s3-v3": "~1.0",
"phospr/fraction": "^1.2",
"spatie/laravel-medialibrary": "^9.0.0",
"spatie/laravel-tags": "^3.0"

487
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4e6584f6ced86392415351d8c793c1ea",
"content-hash": "eaaae601a5f3874fc8274ca2df31cabf",
"packages": [
{
"name": "algolia/algoliasearch-client-php",
@ -218,17 +218,107 @@
"time": "2021-03-11T06:42:03+00:00"
},
{
"name": "babenkoivan/elastic-adapter",
"version": "v1.13.0",
"name": "aws/aws-sdk-php",
"version": "3.178.8",
"source": {
"type": "git",
"url": "https://github.com/babenkoivan/elastic-adapter.git",
"reference": "68e006c893e3fba4594e9788c6cccbe507bd8508"
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "15b27555b365053af712a01cbf99d6d7cd4323fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/babenkoivan/elastic-adapter/zipball/68e006c893e3fba4594e9788c6cccbe507bd8508",
"reference": "68e006c893e3fba4594e9788c6cccbe507bd8508",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/15b27555b365053af712a01cbf99d6d7cd4323fa",
"reference": "15b27555b365053af712a01cbf99d6d7cd4323fa",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0",
"guzzlehttp/promises": "^1.4.0",
"guzzlehttp/psr7": "^1.7.0",
"mtdowling/jmespath.php": "^2.6",
"php": ">=5.5"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-sockets": "*",
"nette/neon": "^2.3",
"paragonie/random_compat": ">= 2",
"phpunit/phpunit": "^4.8.35|^5.4.3",
"psr/cache": "^1.0",
"psr/simple-cache": "^1.0",
"sebastian/comparator": "^1.2.3"
},
"suggest": {
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
"doctrine/cache": "To use the DoctrineCacheAdapter",
"ext-curl": "To send requests using cURL",
"ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
"ext-sockets": "To use client-side monitoring"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Aws\\": "src/"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Amazon Web Services",
"homepage": "http://aws.amazon.com"
}
],
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
"homepage": "http://aws.amazon.com/sdkforphp",
"keywords": [
"amazon",
"aws",
"cloud",
"dynamodb",
"ec2",
"glacier",
"s3",
"sdk"
],
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.178.8"
},
"time": "2021-04-22T18:13:46+00:00"
},
{
"name": "babenkoivan/elastic-adapter",
"version": "v1.14.0",
"source": {
"type": "git",
"url": "https://github.com/babenkoivan/elastic-adapter.git",
"reference": "505049d185ce2de27b97372e738e7412ac2398da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/babenkoivan/elastic-adapter/zipball/505049d185ce2de27b97372e738e7412ac2398da",
"reference": "505049d185ce2de27b97372e738e7412ac2398da",
"shasum": ""
},
"require": {
@ -266,7 +356,7 @@
],
"support": {
"issues": "https://github.com/babenkoivan/elastic-adapter/issues",
"source": "https://github.com/babenkoivan/elastic-adapter/tree/v1.13.0"
"source": "https://github.com/babenkoivan/elastic-adapter/tree/v1.14.0"
},
"funding": [
{
@ -278,7 +368,7 @@
"type": "paypal"
}
],
"time": "2021-02-16T07:25:59+00:00"
"time": "2021-04-06T17:49:45+00:00"
},
{
"name": "babenkoivan/elastic-client",
@ -353,20 +443,20 @@
},
{
"name": "babenkoivan/elastic-migrations",
"version": "v1.4.0",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/babenkoivan/elastic-migrations.git",
"reference": "3e371dc6451751656eb3a0f788389f3c43b89bbb"
"reference": "8c8e1a16978a8fda3f8057b67bab81c308a79463"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/babenkoivan/elastic-migrations/zipball/3e371dc6451751656eb3a0f788389f3c43b89bbb",
"reference": "3e371dc6451751656eb3a0f788389f3c43b89bbb",
"url": "https://api.github.com/repos/babenkoivan/elastic-migrations/zipball/8c8e1a16978a8fda3f8057b67bab81c308a79463",
"reference": "8c8e1a16978a8fda3f8057b67bab81c308a79463",
"shasum": ""
},
"require": {
"babenkoivan/elastic-adapter": "^1.13",
"babenkoivan/elastic-adapter": "^1.14",
"babenkoivan/elastic-client": "^1.2",
"php": "^7.2 || ^8.0"
},
@ -412,7 +502,7 @@
],
"support": {
"issues": "https://github.com/babenkoivan/elastic-migrations/issues",
"source": "https://github.com/babenkoivan/elastic-migrations/tree/v1.4.0"
"source": "https://github.com/babenkoivan/elastic-migrations/tree/v1.5.0"
},
"funding": [
{
@ -424,7 +514,7 @@
"type": "paypal"
}
],
"time": "2021-02-25T12:10:19+00:00"
"time": "2021-04-12T17:01:18+00:00"
},
{
"name": "babenkoivan/elastic-scout-driver",
@ -939,43 +1029,6 @@
},
"time": "2021-02-28T20:03:09+00:00"
},
{
"name": "dnoegel/php-xdg-base-dir",
"version": "v0.1.1",
"source": {
"type": "git",
"url": "https://github.com/dnoegel/php-xdg-base-dir.git",
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35"
},
"type": "library",
"autoload": {
"psr-4": {
"XdgBaseDir\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "implementation of xdg base directory specification for php",
"support": {
"issues": "https://github.com/dnoegel/php-xdg-base-dir/issues",
"source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1"
},
"time": "2019-12-04T15:06:13+00:00"
},
{
"name": "doctrine/cache",
"version": "1.10.2",
@ -1078,33 +1131,35 @@
},
{
"name": "doctrine/dbal",
"version": "3.0.0",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c"
"reference": "5ba62e7e40df119424866064faf2cef66cb5232a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/ee6d1260d5cc20ec506455a585945d7bdb98662c",
"reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/5ba62e7e40df119424866064faf2cef66cb5232a",
"reference": "5ba62e7e40df119424866064faf2cef66cb5232a",
"shasum": ""
},
"require": {
"composer/package-versions-deprecated": "^1.11.99",
"doctrine/cache": "^1.0",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.0",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^8.1",
"jetbrains/phpstorm-stubs": "^2019.1",
"phpstan/phpstan": "^0.12.40",
"doctrine/coding-standard": "8.2.0",
"jetbrains/phpstorm-stubs": "2020.2",
"phpstan/phpstan": "0.12.81",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^9.4",
"psalm/plugin-phpunit": "^0.10.0",
"phpunit/phpunit": "9.5.0",
"psalm/plugin-phpunit": "0.13.0",
"squizlabs/php_codesniffer": "3.6.0",
"symfony/console": "^2.0.5|^3.0|^4.0|^5.0",
"vimeo/psalm": "^3.17.2"
"vimeo/psalm": "4.6.4"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
@ -1113,11 +1168,6 @@
"bin/doctrine-dbal"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\DBAL\\": "src"
@ -1169,7 +1219,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.0.0"
"source": "https://github.com/doctrine/dbal/tree/3.1.0"
},
"funding": [
{
@ -1185,7 +1235,50 @@
"type": "tidelift"
}
],
"time": "2020-11-15T18:20:41+00:00"
"time": "2021-04-19T17:51:23+00:00"
},
{
"name": "doctrine/deprecations",
"version": "v0.5.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "9504165960a1f83cc1480e2be1dd0a0478561314"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314",
"reference": "9504165960a1f83cc1480e2be1dd0a0478561314",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^6.0|^7.0|^8.0",
"phpunit/phpunit": "^7.0|^8.0|^9.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v0.5.3"
},
"time": "2021-03-21T12:59:47+00:00"
},
{
"name": "doctrine/event-manager",
@ -2336,16 +2429,16 @@
},
{
"name": "laravel/framework",
"version": "v8.36.2",
"version": "v8.38.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "0debd8ad6b5aa1f61ccc73910adf049af4ca0444"
"reference": "26a73532c54d2c090692bf2e3e64e449669053ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/0debd8ad6b5aa1f61ccc73910adf049af4ca0444",
"reference": "0debd8ad6b5aa1f61ccc73910adf049af4ca0444",
"url": "https://api.github.com/repos/laravel/framework/zipball/26a73532c54d2c090692bf2e3e64e449669053ba",
"reference": "26a73532c54d2c090692bf2e3e64e449669053ba",
"shasum": ""
},
"require": {
@ -2500,7 +2593,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2021-04-07T12:37:22+00:00"
"time": "2021-04-20T13:50:21+00:00"
},
{
"name": "laravel/scout",
@ -2835,6 +2928,57 @@
],
"time": "2020-08-23T07:39:11+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
"version": "1.0.29",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
"reference": "4e25cc0582a36a786c31115e419c6e40498f6972"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4e25cc0582a36a786c31115e419c6e40498f6972",
"reference": "4e25cc0582a36a786c31115e419c6e40498f6972",
"shasum": ""
},
"require": {
"aws/aws-sdk-php": "^3.20.0",
"league/flysystem": "^1.0.40",
"php": ">=5.5.0"
},
"require-dev": {
"henrikbjorn/phpspec-code-coverage": "~1.0.1",
"phpspec/phpspec": "^2.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\Flysystem\\AwsS3v3\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
}
],
"description": "Flysystem adapter for the AWS S3 SDK v3.x",
"support": {
"issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues",
"source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.29"
},
"time": "2020-10-08T18:58:37+00:00"
},
{
"name": "league/glide",
"version": "1.7.0",
@ -3123,6 +3267,67 @@
],
"time": "2020-12-14T13:15:25+00:00"
},
{
"name": "mtdowling/jmespath.php",
"version": "2.6.0",
"source": {
"type": "git",
"url": "https://github.com/jmespath/jmespath.php.git",
"reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/42dae2cbd13154083ca6d70099692fef8ca84bfb",
"reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb",
"shasum": ""
},
"require": {
"php": "^5.4 || ^7.0 || ^8.0",
"symfony/polyfill-mbstring": "^1.17"
},
"require-dev": {
"composer/xdebug-handler": "^1.4",
"phpunit/phpunit": "^4.8.36 || ^7.5.15"
},
"bin": [
"bin/jp.php"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-4": {
"JmesPath\\": "src/"
},
"files": [
"src/JmesPath.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Declaratively specify how to extract elements from a JSON document",
"keywords": [
"json",
"jsonpath"
],
"support": {
"issues": "https://github.com/jmespath/jmespath.php/issues",
"source": "https://github.com/jmespath/jmespath.php/tree/2.6.0"
},
"time": "2020-07-31T21:01:56+00:00"
},
{
"name": "myclabs/php-enum",
"version": "1.8.0",
@ -4060,20 +4265,19 @@
},
{
"name": "psy/psysh",
"version": "v0.10.7",
"version": "v0.10.8",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
"reference": "a395af46999a12006213c0c8346c9445eb31640c"
"reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/a395af46999a12006213c0c8346c9445eb31640c",
"reference": "a395af46999a12006213c0c8346c9445eb31640c",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3",
"reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3",
"shasum": ""
},
"require": {
"dnoegel/php-xdg-base-dir": "0.1.*",
"ext-json": "*",
"ext-tokenizer": "*",
"nikic/php-parser": "~4.0|~3.0|~2.0|~1.3",
@ -4130,9 +4334,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
"source": "https://github.com/bobthecow/psysh/tree/v0.10.7"
"source": "https://github.com/bobthecow/psysh/tree/v0.10.8"
},
"time": "2021-03-14T02:14:56+00:00"
"time": "2021-04-10T16:23:39+00:00"
},
{
"name": "ralouphie/getallheaders",
@ -4645,16 +4849,16 @@
},
{
"name": "spatie/laravel-medialibrary",
"version": "9.5.3",
"version": "9.5.6",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-medialibrary.git",
"reference": "ebbc996db457adecc778db6030d22ef72b495d59"
"reference": "f2c3ed9a6a7420c27d76c68892f93bca8b5c99d5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/ebbc996db457adecc778db6030d22ef72b495d59",
"reference": "ebbc996db457adecc778db6030d22ef72b495d59",
"url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/f2c3ed9a6a7420c27d76c68892f93bca8b5c99d5",
"reference": "f2c3ed9a6a7420c27d76c68892f93bca8b5c99d5",
"shasum": ""
},
"require": {
@ -4733,7 +4937,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-medialibrary/issues",
"source": "https://github.com/spatie/laravel-medialibrary/tree/9.5.3"
"source": "https://github.com/spatie/laravel-medialibrary/tree/9.5.6"
},
"funding": [
{
@ -4745,7 +4949,7 @@
"type": "github"
}
],
"time": "2021-04-08T08:27:49+00:00"
"time": "2021-04-20T06:17:46+00:00"
},
{
"name": "spatie/laravel-tags",
@ -5197,16 +5401,16 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.2.0",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665"
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665",
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"shasum": ""
},
"require": {
@ -5215,7 +5419,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -5244,7 +5448,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/master"
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
},
"funding": [
{
@ -5260,7 +5464,7 @@
"type": "tidelift"
}
],
"time": "2020-09-07T11:33:47+00:00"
"time": "2021-03-23T23:28:01+00:00"
},
{
"name": "symfony/error-handler",
@ -5418,16 +5622,16 @@
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.2.0",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "0ba7d54483095a198fa51781bc608d17e84dffa2"
"reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2",
"reference": "0ba7d54483095a198fa51781bc608d17e84dffa2",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11",
"reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11",
"shasum": ""
},
"require": {
@ -5440,7 +5644,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -5477,7 +5681,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0"
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0"
},
"funding": [
{
@ -5493,7 +5697,7 @@
"type": "tidelift"
}
],
"time": "2020-09-07T11:33:47+00:00"
"time": "2021-03-23T23:28:01+00:00"
},
{
"name": "symfony/finder",
@ -5558,16 +5762,16 @@
},
{
"name": "symfony/http-client-contracts",
"version": "v2.3.1",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client-contracts.git",
"reference": "41db680a15018f9c1d4b23516059633ce280ca33"
"reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41db680a15018f9c1d4b23516059633ce280ca33",
"reference": "41db680a15018f9c1d4b23516059633ce280ca33",
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
"reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
"shasum": ""
},
"require": {
@ -5578,9 +5782,8 @@
},
"type": "library",
"extra": {
"branch-version": "2.3",
"branch-alias": {
"dev-main": "2.3-dev"
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -5617,7 +5820,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/http-client-contracts/tree/v2.3.1"
"source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0"
},
"funding": [
{
@ -5633,7 +5836,7 @@
"type": "tidelift"
}
],
"time": "2020-10-14T17:08:19+00:00"
"time": "2021-04-11T23:07:08+00:00"
},
{
"name": "symfony/http-foundation",
@ -6874,21 +7077,21 @@
},
{
"name": "symfony/service-contracts",
"version": "v2.2.0",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0"
"psr/container": "^1.1"
},
"suggest": {
"symfony/service-implementation": ""
@ -6896,7 +7099,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -6933,7 +7136,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/master"
"source": "https://github.com/symfony/service-contracts/tree/v2.4.0"
},
"funding": [
{
@ -6949,7 +7152,7 @@
"type": "tidelift"
}
],
"time": "2020-09-07T11:33:47+00:00"
"time": "2021-04-01T10:43:52+00:00"
},
{
"name": "symfony/string",
@ -7129,16 +7332,16 @@
},
{
"name": "symfony/translation-contracts",
"version": "v2.3.0",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
"reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105"
"reference": "95c812666f3e91db75385749fe219c5e494c7f95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105",
"reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95",
"reference": "95c812666f3e91db75385749fe219c5e494c7f95",
"shasum": ""
},
"require": {
@ -7150,7 +7353,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -7187,7 +7390,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/translation-contracts/tree/v2.3.0"
"source": "https://github.com/symfony/translation-contracts/tree/v2.4.0"
},
"funding": [
{
@ -7203,7 +7406,7 @@
"type": "tidelift"
}
],
"time": "2020-09-28T13:05:58+00:00"
"time": "2021-03-23T23:28:01+00:00"
},
{
"name": "symfony/var-dumper",
@ -8307,16 +8510,16 @@
},
{
"name": "facade/flare-client-php",
"version": "1.6.1",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
"reference": "f2b0969f2d9594704be74dbeb25b201570a98098"
"reference": "6bf380035890cb0a09b9628c491ae3866b858522"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/f2b0969f2d9594704be74dbeb25b201570a98098",
"reference": "f2b0969f2d9594704be74dbeb25b201570a98098",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/6bf380035890cb0a09b9628c491ae3866b858522",
"reference": "6bf380035890cb0a09b9628c491ae3866b858522",
"shasum": ""
},
"require": {
@ -8360,7 +8563,7 @@
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
"source": "https://github.com/facade/flare-client-php/tree/1.6.1"
"source": "https://github.com/facade/flare-client-php/tree/1.7.0"
},
"funding": [
{
@ -8368,7 +8571,7 @@
"type": "github"
}
],
"time": "2021-04-08T08:50:01+00:00"
"time": "2021-04-12T09:30:36+00:00"
},
{
"name": "facade/ignition",
@ -8694,16 +8897,16 @@
},
{
"name": "laravel/breeze",
"version": "v1.1.4",
"version": "v1.1.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/breeze.git",
"reference": "36f678dc65bdb09b5dbbb46051ab07d1a6f19962"
"reference": "b6e804b6b8e6f87e510622ed6ab9771e8219e4c2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/breeze/zipball/36f678dc65bdb09b5dbbb46051ab07d1a6f19962",
"reference": "36f678dc65bdb09b5dbbb46051ab07d1a6f19962",
"url": "https://api.github.com/repos/laravel/breeze/zipball/b6e804b6b8e6f87e510622ed6ab9771e8219e4c2",
"reference": "b6e804b6b8e6f87e510622ed6ab9771e8219e4c2",
"shasum": ""
},
"require": {
@ -8746,7 +8949,7 @@
"issues": "https://github.com/laravel/breeze/issues",
"source": "https://github.com/laravel/breeze"
},
"time": "2021-03-23T17:01:27+00:00"
"time": "2021-04-13T14:54:44+00:00"
},
{
"name": "laravel/sail",
@ -9543,16 +9746,16 @@
},
{
"name": "phpstan/phpstan",
"version": "0.12.83",
"version": "0.12.84",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "4a967cec6efb46b500dd6d768657336a3ffe699f"
"reference": "9c43f15da8798c8f30a4b099e6a94530a558cfd5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/4a967cec6efb46b500dd6d768657336a3ffe699f",
"reference": "4a967cec6efb46b500dd6d768657336a3ffe699f",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9c43f15da8798c8f30a4b099e6a94530a558cfd5",
"reference": "9c43f15da8798c8f30a4b099e6a94530a558cfd5",
"shasum": ""
},
"require": {
@ -9583,7 +9786,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/0.12.83"
"source": "https://github.com/phpstan/phpstan/tree/0.12.84"
},
"funding": [
{
@ -9599,7 +9802,7 @@
"type": "tidelift"
}
],
"time": "2021-04-03T15:35:45+00:00"
"time": "2021-04-19T17:10:54+00:00"
},
{
"name": "phpunit/php-code-coverage",

View File

@ -48,7 +48,7 @@ return [
'url' => env('APP_URL').'/media',
],
's3' => [
's3-public' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
@ -56,6 +56,7 @@ return [
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'visibility' => 'public',
],
'seeder' => [

View File

@ -87,7 +87,7 @@ return [
* "similar" slugs. The closure should return the new unique
* suffix to append to the slug.
*/
'uniqueSuffix' => null,
/**
@ -135,7 +135,7 @@ return [
* is probably not a good idea from an SEO point of view.
* Only set this to true if you understand the possible consequences.
*/
'onUpdate' => false,
'onUpdate' => true,
];

View File

@ -27,7 +27,7 @@ class FoodFactory extends Factory
'source' => $this->faker->optional()->url,
'notes' => $this->faker->optional(0.25)->realText(),
'serving_size' => $this->faker->numberBetween(1, 3),
'serving_unit' => $this->faker->randomElement(['tsp', 'tbsp', 'cup']),
'serving_unit' => $this->faker->randomElement(['tsp', 'tbsp', 'cup', 'oz']),
'serving_weight' => $this->faker->numberBetween(5, 500),
'calories' => $this->faker->randomFloat(1, 0, 100),
'fat' => $this->faker->randomFloat(1, 0, 10),
@ -39,55 +39,4 @@ class FoodFactory extends Factory
];
}
/**
* Define a "tsp" serving unit.
*/
public function tspServingUnit()
{
return $this->state(function (array $attributes) {
return [
'serving_unit' => 'tsp',
'serving_size' => 1,
];
});
}
/**
* Define a "tbsp" serving unit.
*/
public function tbspServingUnit()
{
return $this->state(function (array $attributes) {
return [
'serving_unit' => 'tbsp',
'serving_size' => 1,
];
});
}
/**
* Define a "cup" serving unit.
*/
public function cupServingUnit()
{
return $this->state(function (array $attributes) {
return [
'serving_unit' => 'cup',
'serving_size' => 1,
];
});
}
/**
* Define no serving unit.
*/
public function noServingUnit()
{
return $this->state(function (array $attributes) {
return [
'serving_unit' => null,
'serving_unit_name' => 'head'
];
});
}
}

View File

@ -25,12 +25,10 @@ class IngredientAmountFactory extends Factory
if ($this->faker->boolean(90)) {
$ingredient_factory = Food::factory();
$ingredient_type = Food::class;
$ingredient_unit = Nutrients::units()->pluck('value')->random(1)->first();
}
else {
$ingredient_factory = Recipe::factory();
$ingredient_type = Recipe::class;
$ingredient_unit = 'serving';
}
$amounts = [1/8, 1/4, 1/3, 1/2, 2/3, 3/4, 1, 1 + 1/4, 1 + 1/3, 1 + 1/2, 1 + 2/3, 1 + 3/4, 2, 2 + 1/2, 3];
@ -38,7 +36,6 @@ class IngredientAmountFactory extends Factory
'ingredient_id' => $ingredient_factory,
'ingredient_type' => $ingredient_type,
'amount' => $this->faker->randomElement($amounts),
'unit' => $ingredient_unit,
'detail' => $this->faker->boolean() ? Words::randomWords('a') : null,
'weight' => $this->faker->numberBetween(0, 50),
'parent_id' => Recipe::factory(),
@ -46,6 +43,21 @@ class IngredientAmountFactory extends Factory
];
}
/**
* {@inheritdoc}
*/
public function configure(): static
{
return $this->afterMaking(function (IngredientAmount $ingredient_amount) {
// Set the unit to a random one supported by the ingredient.
$ingredient_amount->unit = $ingredient_amount->ingredient
->units_supported
->random(1)
->pluck('value')
->first();
});
}
/**
* Define a specific parent.
*/

View File

@ -4,7 +4,9 @@ namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class UserFactory extends Factory
@ -23,7 +25,32 @@ class UserFactory extends Factory
'username' => $this->faker->unique()->userName,
'password' => Hash::make('password'),
'name' => $this->faker->name,
'admin' => $this->faker->boolean,
'remember_token' => Str::random(10),
];
}
/**
* Create an admin user.
*/
public function admin(): static
{
return $this->state(['admin' => true]);
}
/**
* Create a user and add fake media to it.
*/
public function createOneWithMedia(array $attributes = []): User {
Storage::fake('tests');
/** @var \App\Models\User $user */
$user = $this->createOne($attributes);
$file = UploadedFile::fake()->image('user.jpg', 400, 400);
$path = $file->store('tests');
$user->addMediaFromDisk($path)
->usingName($user->name)
->usingFileName("{$user->slug}.jpg")
->toMediaCollection();
return $user;
}
}

View File

@ -21,7 +21,7 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
$user = User::factory()->create([
$user = User::factory()->admin()->create([
'username' => 'kcal',
'password' => Hash::make('kcal'),
'name' => 'Admin',

View File

@ -15,9 +15,11 @@ class CreateUsersTable extends Migration
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('slug')->unique();
$table->string('username')->unique();
$table->string('password');
$table->string('name');
$table->boolean('admin')->default(false);
$table->rememberToken();
$table->timestamps();
});

View File

@ -19,7 +19,7 @@ class CreateFoodsTable extends Migration
$table->string('brand')->nullable();
$table->string('source')->nullable();
$table->string('notes')->nullable();
$table->unsignedFloat('serving_size');
$table->decimal('serving_size', 10, 8)->unsigned();
$table->enum('serving_unit', ['tsp', 'tbsp', 'cup', 'oz'])->nullable();
$table->string('serving_unit_name')->nullable();
$table->unsignedFloat('serving_weight');

View File

@ -24,7 +24,7 @@ class CreateRecipesTable extends Migration
$table->string('source')->nullable();
$table->unsignedInteger('servings');
$table->unsignedFloat('weight')->nullable();
//$table->decimal('volume', 10, 8)->unsigned()->nullable();
$table->decimal('volume', 10, 8)->unsigned()->nullable();
$table->timestamps();
});
}

View File

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddVolumeToRecipes extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('recipes', function (Blueprint $table) {
$table->decimal('volume', 10, 8)->unsigned()->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('recipes', function (Blueprint $table) {
$table->dropColumn('volume');
});
}
}

View File

@ -37,7 +37,6 @@ services:
- sail
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8081:80
environment:
@ -62,7 +61,7 @@ services:
cap_add:
- IPC_LOCK
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
- 'elasticsearch-data:/usr/share/elasticsearch/data'
ports:
- '${ELASTIC_PORT:-9200}:9200'
networks:

264
package-lock.json generated
View File

@ -6,7 +6,7 @@
"": {
"dependencies": {
"@shopify/draggable": "^1.0.0-beta.12",
"alpine-magic-helpers": "^1.1.0"
"alpine-magic-helpers": "^1.2.0"
},
"devDependencies": {
"@tailwindcss/forms": "^0.3.2",
@ -20,7 +20,7 @@
"postcss-import": "^14.0.1",
"quill": "^1.3.7",
"resolve-url-loader": "^3.1.2",
"tailwindcss": "^2.0.4",
"tailwindcss": "^2.1.1",
"vue-template-compiler": "^2.6.12"
}
},
@ -2404,12 +2404,9 @@
"dev": true
},
"node_modules/alpine-magic-helpers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/alpine-magic-helpers/-/alpine-magic-helpers-1.1.0.tgz",
"integrity": "sha512-sJgPegGYunCjmdghjRe3U3WwIS5GlLJHsGHglI/2oI2AuhbFtW1KsuO4uSh57WR4WfKGzOFxL2SmShxpFW43sw==",
"dependencies": {
"deep-diff": "^1.0.2"
},
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/alpine-magic-helpers/-/alpine-magic-helpers-1.2.0.tgz",
"integrity": "sha512-iYHzBAh5pb8UmANeVNprfanV6e75iOIA3wAv+vz/Yy3I+TMkw85NLqHeKvwLV7EQnZqn02lcHepYtxJM0wd1xA==",
"peerDependencies": {
"alpinejs": "^2.8"
}
@ -4749,11 +4746,6 @@
"node": ">=0.10"
}
},
"node_modules/deep-diff": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz",
"integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg=="
},
"node_modules/deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
@ -4938,6 +4930,12 @@
"node": ">=8"
}
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"node_modules/dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -6061,6 +6059,49 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-base": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
"integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
"dev": true,
"dependencies": {
"glob-parent": "^2.0.0",
"is-glob": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/glob-base/node_modules/glob-parent": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
"dev": true,
"dependencies": {
"is-glob": "^2.0.0"
}
},
"node_modules/glob-base/node_modules/is-extglob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/glob-base/node_modules/is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true,
"dependencies": {
"is-extglob": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@ -7035,6 +7076,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-dotfile": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
"integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@ -7606,6 +7656,12 @@
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
"dev": true
},
"node_modules/lodash.topath": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
"integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=",
"dev": true
},
"node_modules/lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -8719,6 +8775,42 @@
"safe-buffer": "^5.1.1"
}
},
"node_modules/parse-glob": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
"integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
"dev": true,
"dependencies": {
"glob-base": "^0.3.0",
"is-dotfile": "^1.0.0",
"is-extglob": "^1.0.0",
"is-glob": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/parse-glob/node_modules/is-extglob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/parse-glob/node_modules/is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true,
"dependencies": {
"is-extglob": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@ -12716,6 +12808,18 @@
}
]
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
@ -14418,29 +14522,36 @@
}
},
"node_modules/tailwindcss": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.0.4.tgz",
"integrity": "sha512-WhgR0oiBxGOZ9jY0yVfaJCHnckR7U74Fs/BMsYxGdwGJQ5Hd/HlaKD26bEJFZOvYScJo0QcUj2ImldzedsG7Bw==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.1.1.tgz",
"integrity": "sha512-zZ6axGqpSZOCBS7wITm/WNHkBzDt5CIZlDlx0eCVldwTxFPELCVGbgh7Xpb3/kZp3cUxOmK7bZUjqhuMrbN6xQ==",
"dev": true,
"dependencies": {
"@fullhuman/postcss-purgecss": "^3.1.3",
"bytes": "^3.0.0",
"chalk": "^4.1.0",
"chokidar": "^3.5.1",
"color": "^3.1.3",
"detective": "^5.2.0",
"didyoumean": "^1.2.1",
"dlv": "^1.1.3",
"fast-glob": "^3.2.5",
"fs-extra": "^9.1.0",
"html-tags": "^3.1.0",
"lodash": "^4.17.21",
"lodash.topath": "^4.5.2",
"modern-normalize": "^1.0.0",
"node-emoji": "^1.8.1",
"normalize-path": "^3.0.0",
"object-hash": "^2.1.1",
"parse-glob": "^3.0.4",
"postcss-functions": "^3",
"postcss-js": "^3.0.3",
"postcss-nested": "^5.0.5",
"postcss-nested": "5.0.5",
"postcss-selector-parser": "^6.0.4",
"postcss-value-parser": "^4.1.0",
"pretty-hrtime": "^1.0.3",
"quick-lru": "^5.1.1",
"reduce-css-calc": "^2.1.8",
"resolve": "^1.20.0"
},
@ -17702,12 +17813,10 @@
"dev": true
},
"alpine-magic-helpers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/alpine-magic-helpers/-/alpine-magic-helpers-1.1.0.tgz",
"integrity": "sha512-sJgPegGYunCjmdghjRe3U3WwIS5GlLJHsGHglI/2oI2AuhbFtW1KsuO4uSh57WR4WfKGzOFxL2SmShxpFW43sw==",
"requires": {
"deep-diff": "^1.0.2"
}
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/alpine-magic-helpers/-/alpine-magic-helpers-1.2.0.tgz",
"integrity": "sha512-iYHzBAh5pb8UmANeVNprfanV6e75iOIA3wAv+vz/Yy3I+TMkw85NLqHeKvwLV7EQnZqn02lcHepYtxJM0wd1xA==",
"requires": {}
},
"alpinejs": {
"version": "2.8.2",
@ -19603,11 +19712,6 @@
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
},
"deep-diff": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz",
"integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg=="
},
"deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
@ -19760,6 +19864,12 @@
"path-type": "^4.0.0"
}
},
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -20650,6 +20760,42 @@
"path-is-absolute": "^1.0.0"
}
},
"glob-base": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
"integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
"dev": true,
"requires": {
"glob-parent": "^2.0.0",
"is-glob": "^2.0.0"
},
"dependencies": {
"glob-parent": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
"dev": true,
"requires": {
"is-glob": "^2.0.0"
}
},
"is-extglob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true
},
"is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true,
"requires": {
"is-extglob": "^1.0.0"
}
}
}
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@ -21397,6 +21543,12 @@
"integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==",
"dev": true
},
"is-dotfile": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
"integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
"dev": true
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@ -21826,6 +21978,12 @@
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
"dev": true
},
"lodash.topath": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
"integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=",
"dev": true
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -22691,6 +22849,35 @@
"safe-buffer": "^5.1.1"
}
},
"parse-glob": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
"integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
"dev": true,
"requires": {
"glob-base": "^0.3.0",
"is-dotfile": "^1.0.0",
"is-extglob": "^1.0.0",
"is-glob": "^2.0.0"
},
"dependencies": {
"is-extglob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true
},
"is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true,
"requires": {
"is-extglob": "^1.0.0"
}
}
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@ -25833,6 +26020,12 @@
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true
},
"quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
@ -27252,29 +27445,36 @@
}
},
"tailwindcss": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.0.4.tgz",
"integrity": "sha512-WhgR0oiBxGOZ9jY0yVfaJCHnckR7U74Fs/BMsYxGdwGJQ5Hd/HlaKD26bEJFZOvYScJo0QcUj2ImldzedsG7Bw==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.1.1.tgz",
"integrity": "sha512-zZ6axGqpSZOCBS7wITm/WNHkBzDt5CIZlDlx0eCVldwTxFPELCVGbgh7Xpb3/kZp3cUxOmK7bZUjqhuMrbN6xQ==",
"dev": true,
"requires": {
"@fullhuman/postcss-purgecss": "^3.1.3",
"bytes": "^3.0.0",
"chalk": "^4.1.0",
"chokidar": "^3.5.1",
"color": "^3.1.3",
"detective": "^5.2.0",
"didyoumean": "^1.2.1",
"dlv": "^1.1.3",
"fast-glob": "^3.2.5",
"fs-extra": "^9.1.0",
"html-tags": "^3.1.0",
"lodash": "^4.17.21",
"lodash.topath": "^4.5.2",
"modern-normalize": "^1.0.0",
"node-emoji": "^1.8.1",
"normalize-path": "^3.0.0",
"object-hash": "^2.1.1",
"parse-glob": "^3.0.4",
"postcss-functions": "^3",
"postcss-js": "^3.0.3",
"postcss-nested": "^5.0.5",
"postcss-nested": "5.0.5",
"postcss-selector-parser": "^6.0.4",
"postcss-value-parser": "^4.1.0",
"pretty-hrtime": "^1.0.3",
"quick-lru": "^5.1.1",
"reduce-css-calc": "^2.1.8",
"resolve": "^1.20.0"
}

View File

@ -21,11 +21,11 @@
"postcss-import": "^14.0.1",
"quill": "^1.3.7",
"resolve-url-loader": "^3.1.2",
"tailwindcss": "^2.0.4",
"tailwindcss": "^2.1.1",
"vue-template-compiler": "^2.6.12"
},
"dependencies": {
"@shopify/draggable": "^1.0.0-beta.12",
"alpine-magic-helpers": "^1.1.0"
"alpine-magic-helpers": "^1.2.0"
}
}

4
public/css/app.css vendored

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,23 @@
<a {{ $attributes->merge(['class' => "px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white text-center uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150"]) }}>
@php
$classes = [
'px-4',
'py-2',
'border',
'border-transparent',
'rounded-md',
'font-semibold',
'text-xs',
'text-center',
'uppercase',
'tracking-widest',
'focus:outline-none',
'focus:ring',
'disabled:opacity-25',
'transition',
'ease-in-out',
'duration-150'
];
@endphp
<a {{ $attributes->merge(['class' => implode(' ', $classes)]) }}>
{{ $slot }}
</a>

View File

@ -0,0 +1,3 @@
<x-button-link.base :attributes="$attributes" class="text-white bg-gray-800 hover:bg-gray-700 active:bg-gray-900 focus:border-gray-900 ring-gray-300">
{{ $slot }}
</x-button-link.base>

View File

@ -1,3 +1,3 @@
<x-button-link.base :attributes="$attributes" class="bg-green-800 hover:bg-green-700 active:bg-green-900 focus:border-green-900 ring-green-300">
<x-button-link.base :attributes="$attributes" class="text-white bg-green-800 hover:bg-green-700 active:bg-green-900 focus:border-green-900 ring-green-300">
{{ $slot }}
</x-button-link.base>

View File

@ -1,3 +1,3 @@
<x-button-link.base :attributes="$attributes" class="bg-red-800 hover:bg-red-700 active:bg-red-900 focus:border-red-900 ring-red-300">
<x-button-link.base :attributes="$attributes" class="text-white bg-red-800 hover:bg-red-700 active:bg-red-900 focus:border-red-900 ring-red-300">
{{ $slot }}
</x-button-link.base>

View File

@ -0,0 +1,23 @@
@if($model->hasMedia())
<div>
<div class="block font-medium text-sm text-gray-700 mb-1">Current image</div>
<a href="{{ $model->getFirstMedia()->getFullUrl() }}" target="_blank">
{{ $model->getFirstMedia()($previewName) }}
</a>
<fieldset class="flex space-x-2 mt-1 items-center">
<x-inputs.label for="remove_image" class="text-red-800" value="Remove this image" />
<x-inputs.input type="checkbox" name="remove_image" value="1" />
</fieldset>
</div>
@endif
<div>
@if($model->hasMedia())
<x-inputs.label for="image" value="Replace image" />
@else
<x-inputs.label for="image" value="Add image" />
@endif
<x-inputs.file name="image"
class="block mt-1 w-full"
accept="image/png, image/jpeg"/>
</div>

View File

@ -0,0 +1,17 @@
@php
$user_icon = null;
if ($user->hasMedia() && $user->getFirstMedia()->hasGeneratedConversion('icon')) {
$user_icon = $user->getFirstMediaUrl('default', 'icon');
}
@endphp
@empty($user_icon)
<svg class="h-10 w-10 fill-current text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
@else
<img {{ $attributes->merge([
'src' => $user_icon,
'class' => 'rounded-full h-10 w-10 flex items-center justify-center'
]) }} />
@endempty

View File

@ -100,9 +100,9 @@
</div>
</section>
<section class="flex flex-row space-x-2 justify-around md:flex-col md:space-y-2 md:space-x-0">
<x-button-link.base href="{{ route('foods.edit', $food) }}">
<x-button-link.gray href="{{ route('foods.edit', $food) }}">
Edit Food
</x-button-link.base>
</x-button-link.gray>
<x-button-link.red href="{{ route('foods.delete', $food) }}">
Delete Food
</x-button-link.red>

View File

@ -16,33 +16,24 @@
</div>
</div>
<!-- Settings Dropdown -->
<!-- User Menu -->
<div class="flex items-center sm:ml-6">
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">
<div class="hidden sm:block">{{ Auth::user()->name }}</div>
<div class="ml-1">
<svg class="h-10 w-10 fill-current text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<x-user-icon :user="Auth::user()" />
</button>
</x-slot>
<x-slot name="content">
<div class="ml-3">
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->username }}</div>
</div>
<div class="mt-3 space-y-1">
<x-dropdown-link :href="route('goals.index')">Goals</x-dropdown-link>
</div>
<div class="mt-3 space-y-1">
<!-- Authentication -->
<div class="space-y-2">
<x-dropdown-link :href="route('profiles.show', Auth::user())">My Profile</x-dropdown-link>
<x-dropdown-link :href="route('goals.index')">My Goals</x-dropdown-link>
@can('administer', \App\Models\User::class)
<hr />
<x-dropdown-link :href="route('users.index')">Manage Users</x-dropdown-link>
@endcan
<hr />
<form method="POST" action="{{ route('logout') }}" x-data>
@csrf
<x-dropdown-link :href="route('logout')" @click.prevent="$el.closest('form').submit();">Logout</x-dropdown-link>

View File

@ -0,0 +1,29 @@
<x-app-layout>
<x-slot name="title">Edit Profile</x-slot>
<x-slot name="header">
<h1 class="font-semibold text-xl text-gray-800 leading-tight">Edit Profile</h1>
</x-slot>
<form method="POST" enctype="multipart/form-data" action="{{ route('profiles.update', $user) }}">
@method('put')
@csrf
<div class="flex flex-col space-y-4">
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
@include('users.partials.inputs.username')
@include('users.partials.inputs.name')
</div>
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
@include('users.partials.inputs.password')
</div>
<!-- Image -->
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
<x-inputs.image :model="$user" preview_name="icon" />
</div>
</div>
<div class="flex items-center justify-end mt-4">
<x-inputs.button>Save</x-inputs.button>
</div>
</form>
</x-app-layout>

View File

@ -0,0 +1,24 @@
<x-app-layout>
@php($title = ($user->id === Auth::user()->id ? 'My Profile': "{$user->name}'s Profile"))
<x-slot name="title">{{ $title }}</x-slot>
<x-slot name="header">
<div class="flex justify-between items-center">
<h1 class="font-semibold text-2xl text-gray-800 leading-tight">{{ $title }}</h1>
@can('editProfile', $user)
<x-button-link.green href="{{ route('profiles.edit', $user) }}" class="text-sm">
Edit Profile
</x-button-link.green>
@endcan
</div>
</x-slot>
@if($user->hasMedia())
<div class="inline-block">
<a href="{{ $user->getFirstMedia()->getFullUrl() }}" target="_blank">
{{ $user->getFirstMedia()('icon') }}
</a>
</div>
@endif
<div class="mt-2 text-gray-500">
{{ $user->name }}
</div>
</x-app-layout>

View File

@ -79,29 +79,7 @@
<div class="flex flex-col space-y-4 mt-4">
<!-- Image -->
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
@if($recipe->hasMedia())
<div>
<div class="block font-medium text-sm text-gray-700 mb-1">Current image</div>
<a href="{{ $recipe->getFirstMedia()->getFullUrl() }}" target="_blank">
{{ $recipe->getFirstMedia()('preview') }}
</a>
<fieldset class="flex space-x-2 mt-1 items-center">
<x-inputs.label for="remove_image" class="text-red-800" value="Remove this image" />
<x-inputs.input type="checkbox" name="remove_image" value="1" />
</fieldset>
</div>
@endif
<div>
@if($recipe->hasMedia())
<x-inputs.label for="image" value="Replace image" />
@else
<x-inputs.label for="image" value="Add image" />
@endif
<x-inputs.file name="image"
class="block mt-1 w-full"
accept="image/png, image/jpeg"/>
</div>
<x-inputs.image :model="$recipe" />
</div>
<!-- Description -->

View File

@ -154,9 +154,9 @@
</div>
</div>
<section class="flex flex-row space-x-2 justify-around md:flex-col md:space-y-2 md:space-x-0">
<x-button-link.base href="{{ route('recipes.edit', $recipe) }}">
<x-button-link.gray href="{{ route('recipes.edit', $recipe) }}">
Edit Recipe
</x-button-link.base>
</x-button-link.gray>
<x-button-link.red href="{{ route('recipes.delete', $recipe) }}">
Delete Recipe
</x-button-link.red>

View File

@ -0,0 +1,20 @@
<x-app-layout>
<x-slot name="title">Delete {{ $user->name }}</x-slot>
<x-slot name="header">
<h1 class="font-semibold text-xl text-gray-800 leading-tight">
Delete {{ $user->name }}?
</h1>
</x-slot>
<form method="POST" action="{{ route('users.destroy', $user) }}">
@method('delete')
@csrf
<div class="pb-3">
<div class="text-lg">Are you sure what to delete <span class="font-extrabold">{{ $user->name }}</span>?</div>
</div>
<x-inputs.button class="bg-red-800 hover:bg-red-700">
Yes, delete
</x-inputs.button>
<a class="ml-3 text-gray-500 hover:text-gray-700" href="{{ route('users.index') }}">
No, do not delete</a>
</form>
</x-app-layout>

View File

@ -0,0 +1,32 @@
<x-app-layout>
@php($title = ($user->exists ? "Edit {$user->name}" : 'Add User'))
<x-slot name="title">{{ $title }}</x-slot>
<x-slot name="header">
<h1 class="font-semibold text-xl text-gray-800 leading-tight">{{ $title }}</h1>
</x-slot>
<form method="POST" enctype="multipart/form-data" action="{{ ($user->exists ? route('users.update', $user) : route('users.store')) }}">
@if ($user->exists)@method('put')@endif
@csrf
<div class="flex flex-col space-y-4">
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
@include('users.partials.inputs.username')
@include('users.partials.inputs.name')
</div>
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
@include('users.partials.inputs.password')
</div>
@include('users.partials.inputs.admin')
<!-- Image -->
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
<x-inputs.image :model="$user" preview_name="icon" />
</div>
</div>
<div class="flex items-center justify-end mt-4">
<x-inputs.button>{{ ($user->exists ? 'Save' : 'Add') }}</x-inputs.button>
</div>
</form>
</x-app-layout>

View File

@ -0,0 +1,50 @@
<x-app-layout>
<x-slot name="title">Users</x-slot>
<x-slot name="header">
<div class="flex justify-between items-center">
<h1 class="font-semibold text-2xl text-gray-800 leading-tight">Users</h1>
<x-button-link.green href="{{ route('users.create') }}">
Add User
</x-button-link.green>
</div>
</x-slot>
<table class="min-w-max w-full table-auto">
<thead>
<tr class="bg-gray-200 text-gray-600 uppercase text-sm leading-normal">
<th class="py-3 px-6 text-left">Username</th>
<th class="py-3 px-6 text-left">Name</th>
<th class="py-3 px-6 text-left">Photo</th>
<th class="py-3 px-6 text-left">Admin</th>
<th class="py-3 px-6 text-left">Created</th>
<th class="py-3 px-6 text-left">Updated</th>
<th class="py-3 px-6 text-left">&nbsp;</th>
</tr>
</thead>
<tbody>
@foreach($users as $user)
<tr class="border-b border-gray-200">
<td class="py-3 px-6">{{ $user->username }}</td>
<td class="py-3 px-6">{{ $user->name }}</td>
<td class="py-3 px-6">
<x-user-icon :user="$user" />
</td>
<td class="py-3 px-6">@if($user->admin) Yes @else No @endif</td>
<td class="py-3 px-6">{{ $user->created_at }}</td>
<td class="py-3 px-6">{{ $user->updated_at }}</td>
<td class="py-3 px-6">
<div class="flex space-x-2 justify-end">
<x-button-link.gray href="{{ route('users.edit', $user) }}">
Edit
</x-button-link.gray>
@can('delete', $user)
<x-button-link.red href="{{ route('users.delete', $user) }}">
Delete
</x-button-link.red>
@endcan
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</x-app-layout>

View File

@ -0,0 +1,9 @@
<div class="space-y-1">
<x-inputs.label for="admin" value="Site Admin" class="inline-block"/>
<x-inputs.select name="admin"
class="block"
:options="[['value' => 0, 'label' => 'No'], ['value' => 1, 'label' => 'Yes']]"
:selectedValue="old('admin', $user->admin)">
</x-inputs.select>
</div>

View File

@ -0,0 +1,10 @@
<div class="flex-auto space-y-1">
<x-inputs.label for="name" value="Display name"/>
<x-inputs.input name="name"
type="text"
class="block w-full"
:value="old('name', $user->name)"
:hasError="$errors->has('name')"
required />
</div>

View File

@ -0,0 +1,18 @@
<div class="flex-auto space-y-1">
<x-inputs.label for="password" value="Password"/>
<x-inputs.input name="password"
type="password"
class="block w-full"
:hasError="$errors->has('password')"
:required="!$user->exists"/>
</div>
<div class="flex-auto space-y-1">
<x-inputs.label for="password_confirmation" value="Confirm Password"/>
<x-inputs.input name="password_confirmation"
type="password"
class="block w-full"
:hasError="$errors->has('password')"
:required="!$user->exists"/>
</div>

View File

@ -0,0 +1,11 @@
<div class="flex-auto space-y-1">
<x-inputs.label for="username" value="Username"/>
<x-inputs.input name="username"
type="text"
class="block w-full"
autocapitalize="none"
:value="old('username', $user->username)"
:hasError="$errors->has('username')"
required />
</div>

15
routes/admin.php Normal file
View File

@ -0,0 +1,15 @@
<?php
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Admin Routes
|--------------------------------------------------------------------------
*/
Route::middleware(['auth', 'can:administer,\App\Models\User'])->group(function () {
Route::resource('users', UserController::class);
Route::get('/users/{user}/delete', [UserController::class, 'delete'])->name('users.delete');
});

View File

@ -1,15 +1,51 @@
<?php
use App\Http\Controllers\FoodController;
use App\Http\Controllers\GoalController;
use App\Http\Controllers\IngredientPickerController;
use App\Http\Controllers\JournalEntryController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\RecipeController;
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use Illuminate\Support\Facades\Route;
Route::get('/login', [AuthenticatedSessionController::class, 'create'])
->middleware('guest')
->name('login');
/*
|--------------------------------------------------------------------------
| Authorized User Routes
|--------------------------------------------------------------------------
*/
Route::post('/login', [AuthenticatedSessionController::class, 'store'])
->middleware('guest');
Route::middleware(['auth'])->group(function () {
// Auth.
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
->middleware('auth')
->name('logout');
// Foods.
Route::resource('foods', FoodController::class);
Route::get('/foods/{food}/delete', [FoodController::class, 'delete'])->name('foods.delete');
// Goals.
Route::resource('goals', GoalController::class);
Route::get('/goals/{goal}/delete', [GoalController::class, 'delete'])->name('goals.delete');
// Ingredient picker.
Route::get('/ingredient-picker/search', [IngredientPickerController::class, 'search'])->name('ingredient-picker.search');
// Journal entries.
Route::get('/journal-entries/create/from-nutrients', [JournalEntryController::class, 'createFromNutrients'])->name('journal-entries.create.from-nutrients');
Route::post('/journal-entries/create/from-nutrients', [JournalEntryController::class, 'storeFromNutrients'])->name('journal-entries.store.from-nutrients');
Route::resource('journal-entries', JournalEntryController::class);
Route::get('/journal-entries/{journal_entry}/delete', [JournalEntryController::class, 'delete'])->name('journal-entries.delete');
// Recipes.
Route::resource('recipes', RecipeController::class);
Route::get('/recipes/{recipe}/delete', [RecipeController::class, 'delete'])->name('recipes.delete');
// Users.
Route::get('/profile/{user}', [ProfileController::class, 'show'])->name('profiles.show');
});
Route::middleware(['auth', 'can:editProfile,user'])->group(function () {
// Profiles (non-admin Users variant).
Route::get('/profile/{user}/edit', [ProfileController::class, 'edit'])->name('profiles.edit');
Route::put('/profile/{user}', [ProfileController::class, 'update'])->name('profiles.update');
});

View File

@ -1,6 +1,6 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Foundation\Console\ClosureCommand;
use Illuminate\Support\Facades\Artisan;
/*
@ -14,6 +14,19 @@ use Illuminate\Support\Facades\Artisan;
|
*/
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
if (!App::isProduction()) {
/**
* Wipe, migrate, and seed the database.
*/
Artisan::command('dev:reset', function () {
/** @phpstan-ignore-next-line */
assert($this instanceof ClosureCommand);
Artisan::call('db:wipe');
$this->info(Artisan::output());
Artisan::call('migrate');
$this->info(Artisan::output());
Artisan::call('db:seed');
$this->info(Artisan::output());
$this->info('Database reset complete!');
})->purpose('Wipe, migrate, and seed the database.');
}

15
routes/guest.php Normal file
View File

@ -0,0 +1,15 @@
<?php
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Guest Routes
|--------------------------------------------------------------------------
*/
Route::middleware(['guest'])->group(function () {
Route::get('/login', [AuthenticatedSessionController::class, 'create'])->name('login');
Route::post('/login', [AuthenticatedSessionController::class, 'store']);
});

View File

@ -1,10 +1,5 @@
<?php
use App\Http\Controllers\FoodController;
use App\Http\Controllers\GoalController;
use App\Http\Controllers\IngredientPickerController;
use App\Http\Controllers\JournalEntryController;
use App\Http\Controllers\RecipeController;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Route;
@ -24,25 +19,6 @@ Route::get('/', function (): RedirectResponse {
return new RedirectResponse(RouteServiceProvider::HOME);
});
// Foods.
Route::resource('foods', FoodController::class)->middleware(['auth']);
Route::get('/foods/{food}/delete', [FoodController::class, 'delete'])->middleware(['auth'])->name('foods.delete');
// Goals.
Route::resource('goals', GoalController::class)->middleware(['auth']);
Route::get('/goals/{goal}/delete', [GoalController::class, 'delete'])->middleware(['auth'])->name('goals.delete');
// Ingredient picker.
Route::get('/ingredient-picker/search', [IngredientPickerController::class, 'search'])->middleware(['auth'])->name('ingredient-picker.search');
// Journal entries.
Route::get('/journal-entries/create/from-nutrients', [JournalEntryController::class, 'createFromNutrients'])->middleware(['auth'])->name('journal-entries.create.from-nutrients');
Route::post('/journal-entries/create/from-nutrients', [JournalEntryController::class, 'storeFromNutrients'])->middleware(['auth'])->name('journal-entries.store.from-nutrients');
Route::resource('journal-entries', JournalEntryController::class)->middleware(['auth']);
Route::get('/journal-entries/{journal_entry}/delete', [JournalEntryController::class, 'delete'])->middleware(['auth'])->name('journal-entries.delete');
// Recipes.
Route::resource('recipes', RecipeController::class)->middleware(['auth']);
Route::get('/recipes/{recipe}/delete', [RecipeController::class, 'delete'])->middleware(['auth'])->name('recipes.delete');
require __DIR__.'/guest.php';
require __DIR__.'/auth.php';
require __DIR__.'/admin.php';

7
tailwind.config.js vendored
View File

@ -1,7 +1,12 @@
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
purge: ['./storage/framework/views/*.php', './resources/views/**/*.blade.php'],
mode: 'jit',
purge: [
'./storage/framework/views/*.php',
'./resources/views/**/*.blade.php',
],
theme: {
extend: {

View File

@ -16,7 +16,7 @@ class UserAddTest extends TestCase
->expectsQuestion('Enter a username for the user', $user->username)
->expectsQuestion('Enter a password for the user (leave blank for a random password)', 'password')
->expectsQuestion('Re-type the password to confirm', 'password')
->expectsQuestion('Enter a name for the user (optional)', $user->name)
->expectsQuestion('Enter a name for the user', $user->name)
->assertExitCode(0);
/** @var \App\Models\User $new_user */
$new_user = User::whereUsername($user->username)->get()->first();
@ -41,6 +41,24 @@ class UserAddTest extends TestCase
$this->assertEquals($user->name, $new_user->name);
}
public function testCanAddAdminUser(): void
{
/** @var \App\Models\User $user */
$user = User::factory()->makeOne();
$parameters = [
'username' => $user->username,
'password' => 'password',
'--name' => $user->name,
'--admin' => true,
];
$this->artisan('user:add', $parameters)->assertExitCode(0);
/** @var \App\Models\User $new_user */
$new_user = User::whereUsername($user->username)->get()->first();
$this->assertEquals($user->username, $new_user->username);
$this->assertEquals($user->name, $new_user->name);
$this->assertTrue($new_user->admin);
}
public function testCanNotAddExistingUsername(): void
{
/** @var \App\Models\User $user */

View File

@ -31,6 +31,9 @@ abstract class HttpControllerTestCase extends LoggedInTestCase
return $this->factory()->create();
}
/**
* Test instances index.
*/
public function testCanLoadIndex(): void
{
$index_url = action([$this->class(), 'index']);
@ -38,6 +41,9 @@ abstract class HttpControllerTestCase extends LoggedInTestCase
$response->assertOk();
}
/**
* Test instance add.
*/
public function testCanAddInstance(): void
{
$create_url = action([$this->class(), 'create']);
@ -49,6 +55,9 @@ abstract class HttpControllerTestCase extends LoggedInTestCase
$response->assertSessionHasNoErrors();
}
/**
* Test instance view.
*/
public function testCanViewInstance(): void
{
$instance = $this->createInstance();
@ -58,6 +67,9 @@ abstract class HttpControllerTestCase extends LoggedInTestCase
$response->assertViewHas($this->routeKey());
}
/**
* Test instance edit.
*/
public function testCanEditInstance(): void
{
$instance = $this->createInstance();
@ -71,6 +83,9 @@ abstract class HttpControllerTestCase extends LoggedInTestCase
$response->assertSessionHasNoErrors();
}
/**
* Test instance delete/destroy.
*/
public function testCanDeleteInstance(): void
{
$instance = $this->createInstance();

View File

@ -0,0 +1,61 @@
<?php
namespace Tests\Feature\Http\Controllers;
use App\Http\Controllers\ProfileController;
use App\Models\User;
use Tests\LoggedInTestCase;
class ProfileControllerTest extends LoggedInTestCase
{
/**
* Test view own profile.
*/
public function testCanViewOwnProfile(): void
{
$view_url = action([ProfileController::class, 'show'], ['user' => $this->user]);
$response = $this->get($view_url);
$response->assertOk();
$response->assertViewHas('user', $this->user);
}
/**
* Test view other user profile.
*/
public function testCanViewOtherUserProfile(): void
{
$user = User::factory()->createOne();
$view_url = action([ProfileController::class, 'show'], ['user' => $user]);
$response = $this->get($view_url);
$response->assertOk();
$response->assertViewHas('user', $user);
}
/**
* Test view own profile.
*/
public function testCanEditOwnProfile(): void
{
$edit_url = action([ProfileController::class, 'edit'], ['user' => $this->user]);
$response = $this->get($edit_url);
$response->assertOk();
$new_user = User::factory()->make();
$put_url = action([ProfileController::class, 'update'], ['user' => $this->user]);
$response = $this->put($put_url, $new_user->toArray());
$response->assertSessionHasNoErrors();
}
/**
* Test view own profile.
*/
public function testCanNotEditOtherUserProfile(): void
{
$user = User::factory()->createOne();
$edit_url = action([ProfileController::class, 'edit'], ['user' => $user]);
$response = $this->get($edit_url);
$response->assertForbidden();
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Tests\Feature\Http\Controllers;
use App\Http\Controllers\UserController;
use App\Models\User;
use Database\Factories\UserFactory;
class UserControllerTest extends HttpControllerTestCase
{
/**
* @inheritdoc
*/
public function class(): string
{
return UserController::class;
}
/**
* @inheritdoc
*/
public function factory(): UserFactory
{
return User::factory();
}
/**
* @inheritdoc
*/
public function routeKey(): string
{
return 'user';
}
/**
* @inheritdoc
*/
protected function createInstance(): User
{
return $this->factory()->createOneWithMedia();
}
/**
* @doesNotPerformAssertions
*/
public function testCanViewInstance(): void
{
$this->setName('can *not* view instance');
// Users are not independently viewable.
}
/**
* @inheritdoc
*/
public function testCanAddInstance(): void
{
$create_url = action([$this->class(), 'create']);
$response = $this->get($create_url);
$response->assertOk();
$instance = $this->factory()->make();
$attributes = $instance->toArray();
$attributes['password'] = 'password';
$attributes['password_confirmation'] = $attributes['password'];
$store_url = action([$this->class(), 'store']);
$response = $this->post($store_url, $attributes);
$response->assertSessionHasNoErrors();
}
public function testCanChangeUserPassword(): void {
$user = $this->createInstance();
$user->password = 'password1';
$user->save();
$input = $user->toArray();
$input['password'] = 'password2';
$input['password_confirmation'] = 'password2';
$put_url = action([$this->class(), 'update'], [$this->routeKey() => $user]);
$response = $this->put($put_url, $input);
$response->assertSessionHasNoErrors();
$user->refresh();
$this->logout();
$response = $this-> post('/login', ['username' => $user->username, 'password' => 'password1']);
$response->assertSessionHasErrors();
$this->post('/login', ['username' => $user->username, 'password' => 'password2']);
$this->assertAuthenticatedAs($user);
}
public function testCanNotDeleteSelf(): void {
$user = User::first();
$edit_url = action([$this->class(), 'delete'], [$this->routeKey() => $user]);
$response = $this->get($edit_url);
$response->assertForbidden();
}
public function testCanNotAccessIndexAsNonAdmin(): void {
$this->logout();
$this->loginUser();
$index_url = action([$this->class(), 'index']);
$response = $this->get($index_url);
$response->assertForbidden();
}
}

View File

@ -116,7 +116,7 @@ class NutrientsTest extends TestCase
[$foodInvalidUnit, 1, 'tsp'],
[$foodInvalidUnit, 1, 'tbsp'],
[$foodInvalidUnit, 1, 'cup'],
[Food::factory()->tspServingUnit()->make(), 1, 'invalid'],
[Food::factory()->make(['serving_unit' => 'tsp', 'serving_size' => 1]), 1, 'invalid'],
];
}
@ -128,10 +128,10 @@ class NutrientsTest extends TestCase
/** @var \App\Models\Food[] $foods */
$foods = [
'tsp' => Food::factory()->tspServingUnit()->make(),
'tbsp' => Food::factory()->tbspServingUnit()->make(),
'cup' => Food::factory()->cupServingUnit()->make(),
'none' => Food::factory()->noServingUnit()->make(),
'tsp' => Food::factory()->make(['serving_unit' => 'tsp', 'serving_size' => 1]),
'tbsp' => Food::factory()->make(['serving_unit' => 'tbsp', 'serving_size' => 1]),
'cup' => Food::factory()->make(['serving_unit' => 'cup', 'serving_size' => 1]),
'none' => Food::factory()->make(['serving_unit' => null, 'serving_unit_name' => 'head']),
];
return [

View File

@ -9,7 +9,7 @@ abstract class LoggedInTestCase extends TestCase
public function setUp(): void
{
parent::setUp();
$this->loginUser();
$this->loginAdmin();
}
}

View File

@ -9,11 +9,12 @@ trait LogsIn
protected User $user;
/**
* Creates a user and logs the user in.
* Creates an admin and logs in.
*/
public function loginUser(): void
public function loginAdmin(): void
{
$this->user = User::factory()
->admin()
->hasGoals(2)
->hasJournalEntries(5)
->create();
@ -22,4 +23,23 @@ trait LogsIn
'password' => 'password',
]);
}
/**
* Creates a regular user and logs in.
*/
public function loginUser(): void
{
$this->user = User::factory()
->hasGoals(2)
->hasJournalEntries(5)
->createOneWithMedia(['admin' => false]);
$this->post('/login', [
'username' => $this->user->username,
'password' => 'password',
]);
}
public function logout(): void {
$this->post('/logout');
}
}