Merge remote-tracking branch 'origin/main' into demo

This commit is contained in:
Christopher C. Wells 2022-02-12 06:43:52 -08:00
commit 0b1e4c0429
35 changed files with 7917 additions and 7644 deletions

View File

@ -1,5 +1,5 @@
APP_NAME=kcal APP_NAME=kcal
APP_ENV=local APP_ENV=testing
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
@ -7,7 +7,12 @@ APP_URL=http://localhost
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=sqlite DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=kcal
DB_USERNAME=root
DB_PASSWORD=root
SCOUT_DRIVER=elastic SCOUT_DRIVER=elastic
ELASTIC_HOST=localhost:9200 ELASTIC_HOST=localhost:9200

View File

@ -10,6 +10,10 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: mirromutth/mysql-action@v1.1
with:
mysql database: kcal
mysql root password: root
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: '8.0' php-version: '8.0'
@ -41,7 +45,7 @@ jobs:
php -r "file_exists('.env') || copy('.env.ci', '.env');" php -r "file_exists('.env') || copy('.env.ci', '.env');"
php artisan key:generate php artisan key:generate
- name: Run tests - name: Run tests
run: vendor/bin/paratest --coverage-clover build/logs/clover.xml run: php artisan test --parallel --recreate-databases --coverage-clover build/logs/clover.xml
- name: Upload coverage results to Coveralls - name: Upload coverage results to Coveralls
env: env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ use App\Models\Recipe;
use App\Search\Ingredient; use App\Search\Ingredient;
use ElasticScoutDriverPlus\Builders\MultiMatchQueryBuilder; use ElasticScoutDriverPlus\Builders\MultiMatchQueryBuilder;
use ElasticScoutDriverPlus\Builders\TermsQueryBuilder; use ElasticScoutDriverPlus\Builders\TermsQueryBuilder;
use ElasticScoutDriverPlus\Support\Query;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -42,11 +43,10 @@ class IngredientPickerController extends Controller
* Search using an ElasticSearch service. * Search using an ElasticSearch service.
*/ */
private function searchWithElasticSearch(string $term): Collection { private function searchWithElasticSearch(string $term): Collection {
return Food::boolSearch() $query = Query::bool()
->join(Recipe::class)
// Attempt to match exact phrase first. // Attempt to match exact phrase first.
->should('match_phrase', ['name' => $term]) ->should(Query::matchPhrase()->field('name')->query($term))
// Attempt multi-match search on all relevant fields with search-as-you-type on name. // Attempt multi-match search on all relevant fields with search-as-you-type on name.
->should((new MultiMatchQueryBuilder()) ->should((new MultiMatchQueryBuilder())
@ -57,10 +57,12 @@ class IngredientPickerController extends Controller
->fuzziness('AUTO')) ->fuzziness('AUTO'))
// Attempt to match on any tags in the term. // Attempt to match on any tags in the term.
->should((new TermsQueryBuilder()) ->should((new TermsQueryBuilder())->field('tags')->values(explode(' ', $term)))
->terms('tags', explode(' ', $term)))
// Get resulting models. ->minimumShouldMatch(1);
return Food::searchQuery($query)
->join(Recipe::class)
->execute() ->execute()
->models(); ->models();
} }

View File

@ -2,8 +2,8 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
class TrustProxies extends Middleware class TrustProxies extends Middleware
{ {
@ -15,5 +15,10 @@ class TrustProxies extends Middleware
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $headers = Request::HEADER_X_FORWARDED_ALL; protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
} }

View File

@ -7,10 +7,9 @@ use App\Models\Traits\Journalable;
use App\Models\Traits\Sluggable; use App\Models\Traits\Sluggable;
use App\Models\Traits\Taggable; use App\Models\Traits\Taggable;
use App\Support\Number; use App\Support\Number;
use ElasticScoutDriverPlus\QueryDsl; use ElasticScoutDriverPlus\Searchable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
/** /**
* App\Models\Food * App\Models\Food
@ -80,7 +79,6 @@ final class Food extends Model
use HasFactory; use HasFactory;
use Ingredient; use Ingredient;
use Journalable; use Journalable;
use QueryDsl;
use Searchable; use Searchable;
use Sluggable; use Sluggable;
use Taggable; use Taggable;

View File

@ -9,12 +9,11 @@ use App\Models\Traits\Sluggable;
use App\Models\Traits\Taggable; use App\Models\Traits\Taggable;
use App\Support\Number; use App\Support\Number;
use App\Support\Nutrients; use App\Support\Nutrients;
use ElasticScoutDriverPlus\QueryDsl; use ElasticScoutDriverPlus\Searchable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Laravel\Scout\Searchable;
use Spatie\Image\Manipulations; use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia; use Spatie\MediaLibrary\InteractsWithMedia;
@ -91,7 +90,6 @@ final class Recipe extends Model implements HasMedia
use Ingredient; use Ingredient;
use InteractsWithMedia; use InteractsWithMedia;
use Journalable; use Journalable;
use QueryDsl;
use Searchable; use Searchable;
use Sluggable; use Sluggable;
use Taggable; use Taggable;

View File

@ -0,0 +1,20 @@
<?php
namespace App\Policies;
use App\Models\Goal;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class GoalPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can access (show, edit, delete) the goal.
*/
public function access(User $user, Goal $goal): bool {
return $user->id === $goal->user_id;
}
}

View File

@ -2,7 +2,9 @@
namespace App\Providers; namespace App\Providers;
use App\Models\Goal;
use App\Models\User; use App\Models\User;
use App\Policies\GoalPolicy;
use App\Policies\UserPolicy; use App\Policies\UserPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
@ -13,6 +15,7 @@ class AuthServiceProvider extends ServiceProvider
* @inheritdoc * @inheritdoc
*/ */
protected $policies = [ protected $policies = [
Goal::class => GoalPolicy::class,
User::class => UserPolicy::class, User::class => UserPolicy::class,
]; ];

View File

@ -9,39 +9,39 @@
"ext-gd": "*", "ext-gd": "*",
"ext-json": "*", "ext-json": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"algolia/algoliasearch-client-php": "^2.7", "algolia/algoliasearch-client-php": "^3.2",
"algolia/scout-extended": "^1.15", "algolia/scout-extended": "^2.0",
"babenkoivan/elastic-migrations": "^1.4", "babenkoivan/elastic-migrations": "^2.0",
"babenkoivan/elastic-scout-driver": "^1.3", "babenkoivan/elastic-scout-driver": "^2.0",
"babenkoivan/elastic-scout-driver-plus": "^2.0", "babenkoivan/elastic-scout-driver-plus": "^3.3",
"cloudcreativity/laravel-json-api": "^3.2", "cloudcreativity/laravel-json-api": "^4.0",
"cviebrock/eloquent-sluggable": "^8.0", "cviebrock/eloquent-sluggable": "^9.0",
"doctrine/dbal": "^3.0", "doctrine/dbal": "^3.0",
"fakerphp/faker": "^1.14", "fakerphp/faker": "^1.14",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0", "fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1", "guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.12", "laravel/framework": "^9.0",
"laravel/scout": "^8.6", "laravel/scout": "^9.0",
"laravel/tinker": "^2.5", "laravel/tinker": "^2.7",
"league/flysystem-aws-s3-v3": "~1.0", "league/flysystem-aws-s3-v3": "^3.0",
"phospr/fraction": "^1.2", "phospr/fraction": "^1.2",
"spatie/laravel-csp": "^2.6", "spatie/laravel-csp": "^2.6",
"spatie/laravel-medialibrary": "^9.0.0", "spatie/laravel-medialibrary": "^10.0",
"spatie/laravel-tags": "^3.0" "spatie/laravel-tags": "^4.0"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-ide-helper": "^2.9", "barryvdh/laravel-ide-helper": "^2.9",
"brianium/paratest": "^6.2", "brianium/paratest": "^6.2",
"cloudcreativity/json-api-testing": "^3.2", "cloudcreativity/json-api-testing": "^4.0",
"facade/ignition": "^2.5", "fakerphp/faker": "^1.9.1",
"laravel/breeze": "^1.0", "laravel/breeze": "^1.0",
"laravel/sail": "^1.10", "laravel/sail": "^1.10",
"mockery/mockery": "^1.4.2", "mockery/mockery": "^1.4.2",
"nunomaduro/collision": "^5.0", "nunomaduro/collision": "^6.1",
"nunomaduro/larastan": "^0.6.13", "nunomaduro/larastan": "^2.0",
"php-coveralls/php-coveralls": "^2.4", "php-coveralls/php-coveralls": "^2.4",
"phpunit/phpunit": "^9.3.3" "phpunit/phpunit": "^9.3.3",
"spatie/laravel-ignition": "^1.0"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,

4152
composer.lock generated

File diff suppressed because it is too large Load Diff

7014
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,18 +10,18 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --config=node_modules/laravel-mix/setup/webpack.config.js" "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --config=node_modules/laravel-mix/setup/webpack.config.js"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.3.3", "@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.4.1", "@tailwindcss/typography": "^0.5.1",
"alpinejs": "^3.4.1", "alpinejs": "^3.9.0",
"autoprefixer": "^10.3.6", "autoprefixer": "^10.4.2",
"axios": "^0.21.4", "axios": "^0.25.0",
"cross-env": "^7.0", "cross-env": "^7.0",
"laravel-mix": "^6.0.31", "laravel-mix": "^6.0.41",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"postcss-import": "^14.0.2", "postcss-import": "^14.0.2",
"quill": "^1.3.7", "quill": "^1.3.7",
"resolve-url-loader": "^4.0.0", "resolve-url-loader": "^5.0.0",
"tailwindcss": "^2.2.16", "tailwindcss": "^3.0.22",
"vue-template-compiler": "^2.6.14" "vue-template-compiler": "^2.6.14"
}, },
"dependencies": { "dependencies": {

View File

@ -19,6 +19,7 @@
</coverage> </coverage>
<php> <php>
<server name="APP_ENV" value="testing"/> <server name="APP_ENV" value="testing"/>
<server name="DB_USERNAME" value="root"/>
<server name="BCRYPT_ROUNDS" value="4"/> <server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/> <server name="CACHE_DRIVER" value="array"/>
<server name="MAIL_MAILER" value="array"/> <server name="MAIL_MAILER" value="array"/>
@ -26,9 +27,5 @@
<server name="SESSION_DRIVER" value="elastic"/> <server name="SESSION_DRIVER" value="elastic"/>
<server name="SESSION_DRIVER" value="array"/> <server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/> <server name="TELESCOPE_ENABLED" value="false"/>
<!-- @todo Figure out how to do MySQL parallel testing inside Sail. -->
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
</php> </php>
</phpunit> </phpunit>

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

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

2
public/js/quill.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
@props(['status']) @props(['status'])
@if ($status) @if ($status)
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}> <div {{ $attributes->merge(['class' => 'font-medium text-sm text-emerald-600']) }}>
{{ $status }} {{ $status }}
</div> </div>
@endif @endif

View File

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

View File

@ -28,11 +28,11 @@ switch ($width) {
<div x-show="open" <div x-show="open"
x-transition:enter="transition ease-out duration-200" x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100" x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75" x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95" x-transition:leave-end="opacity-0 scale-95"
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}" class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
style="display: none;" style="display: none;"
@click="open = false"> @click="open = false">

View File

@ -26,7 +26,7 @@
<div class="absolute border-2 border-gray-500 border-b-0 bg-white" <div class="absolute border-2 border-gray-500 border-b-0 bg-white"
x-bind="ingredient"> x-bind="ingredient">
<template x-for="result in results" :key="result.id"> <template x-for="result in results" :key="result.id">
<div class="p-1 border-b-2 border-gray-500 hover:bg-yellow-300 cursor-pointer" x-bind:data-id="result.id"> <div class="p-1 border-b-2 border-gray-500 hover:bg-amber-300 cursor-pointer" x-bind:data-id="result.id">
<div class="pointer-events-none"> <div class="pointer-events-none">
<div> <div>
<span class="font-bold" x-text="result.name"></span><span class="text-gray-600" x-text="', ' + result.detail" x-show="result.detail"></span> <span class="font-bold" x-text="result.name"></span><span class="text-gray-600" x-text="', ' + result.detail" x-show="result.detail"></span>

View File

@ -1,3 +1,3 @@
<button {{ $attributes->merge(['type' => 'submit', 'class' => "inline-flex items-center border border-transparent rounded-md font-semibold text-xs text-green-500 tracking-widest hover:text-green-700 active:text-green-900 focus:outline-none focus:border-green-900 focus:ring ring-green-300 disabled:opacity-25 transition ease-in-out duration-150"]) }}> <button {{ $attributes->merge(['type' => 'submit', 'class' => "inline-flex items-center border border-transparent rounded-md font-semibold text-xs text-emerald-500 tracking-widest hover:text-emerald-700 active:text-emerald-900 focus:outline-none focus:border-emerald-900 focus:ring ring-emerald-300 disabled:opacity-25 transition ease-in-out duration-150"]) }}>
{{ $slot }} {{ $slot }}
</button> </button>

View File

@ -1,4 +1,4 @@
<div x-data data-tags="{{ $defaultTags ?? '[]' }}"> <div x-data data-tags="{!! $defaultTags ?? '[]' !!}">
<div x-data="tagSelect()" @click.outside="clearSearch()" @keydown.escape="clearSearch()"> <div x-data="tagSelect()" @click.outside="clearSearch()" @keydown.escape="clearSearch()">
<div class="relative" @keydown.enter.prevent="addTag(searchTerm)"> <div class="relative" @keydown.enter.prevent="addTag(searchTerm)">
<x-inputs.input type="hidden" <x-inputs.input type="hidden"

View File

@ -25,7 +25,7 @@
@endif @endif
@if(!$food->ingredientAmountRelationships->isEmpty()) @if(!$food->ingredientAmountRelationships->isEmpty())
<div class="flex space-x-2 items-center text-lg"> <div class="flex space-x-2 items-center text-lg">
<div class="text-yellow-500"> <div class="text-amber-500">
<svg class="h-8 w-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-8 w-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd" />
</svg> </svg>

View File

@ -125,7 +125,7 @@
:selectedValue="$currentGoal?->id ?? null"> :selectedValue="$currentGoal?->id ?? null">
</x-inputs.select> </x-inputs.select>
<div class="flex items-center justify-start mt-4"> <div class="flex items-center justify-start mt-4">
<x-inputs.button class="bg-green-800 hover:bg-green-700">Change Goal</x-inputs.button> <x-inputs.button class="bg-emerald-800 hover:bg-emerald-700">Change Goal</x-inputs.button>
<x-button-link.red class="ml-3" x-on:click="showGoalChangeForm = !showGoalChangeForm"> <x-button-link.red class="ml-3" x-on:click="showGoalChangeForm = !showGoalChangeForm">
Cancel Cancel
</x-button-link.red> </x-button-link.red>

View File

@ -41,7 +41,7 @@
<!-- Page Content --> <!-- Page Content -->
<main> <main>
@if(session()->has('message')) @if(session()->has('message'))
<div class="bg-green-200 p-2 mb-2"> <div class="bg-emerald-200 p-2 mb-2">
{{ session()->get('message') }} {{ session()->get('message') }}
</div> </div>
@endif @endif

View File

@ -13,7 +13,7 @@
<div class="flex flex-col space-y-2 mt-2 text-lg"> <div class="flex flex-col space-y-2 mt-2 text-lg">
@if(!$recipe->ingredientAmountRelationships->isEmpty()) @if(!$recipe->ingredientAmountRelationships->isEmpty())
<div class="flex space-x-2 items-center text-lg"> <div class="flex space-x-2 items-center text-lg">
<div class="text-yellow-500"> <div class="text-amber-500">
<svg class="h-8 w-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-8 w-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd" />
</svg> </svg>

View File

@ -133,7 +133,7 @@
</div> </div>
</div> </div>
<x-inputs.button type="button" <x-inputs.button type="button"
class="bg-green-800 hover:bg-green-700 active:bg-green-900 focus:border-green-900 ring-green-300" class="bg-emerald-800 hover:bg-emerald-700 active:bg-emerald-900 focus:border-emerald-900 ring-emerald-300"
x-on:click="addNodeFromTemplate($refs.ingredients, 'ingredient');"> x-on:click="addNodeFromTemplate($refs.ingredients, 'ingredient');">
Add Ingredient Add Ingredient
</x-inputs.button> </x-inputs.button>
@ -158,7 +158,7 @@
</div> </div>
</div> </div>
<x-inputs.button type="button" <x-inputs.button type="button"
class="bg-green-800 hover:bg-green-700 active:bg-green-900 focus:border-green-900 ring-green-300" class="bg-emerald-800 hover:bg-emerald-700 active:bg-emerald-900 focus:border-emerald-900 ring-emerald-300"
x-on:click="addNodeFromTemplate($refs.steps, 'step');"> x-on:click="addNodeFromTemplate($refs.steps, 'step');">
Add Step Add Step
</x-inputs.button> </x-inputs.button>

View File

@ -43,8 +43,18 @@
@if($item::class === \App\Models\IngredientAmount::class) @if($item::class === \App\Models\IngredientAmount::class)
<li> <li>
<span> <span>
{{ \App\Support\Number::rationalStringFromFloat($item->amount) }} {{-- Prevent food with serving size > 1 from incorrectly using formatted
@if($item->unitFormatted){{ $item->unitFormatted }}@endif serving unit with number of servings. E.g., for a recipe calling for 1
serving of a food with 4 tbsp. to a serving size show "1 serving" instead
of "1 tbsp." (incorrect). --}}
@if($item->unit === 'serving' && $item->ingredient->serving_size > 1 && ($item->ingredient->serving_unit || $item->ingredient->serving_unit_name))
{{ \App\Support\Number::rationalStringFromFloat($item->amount * $item->ingredient->serving_size) }} {{ $item->unitFormatted }}
<span class="text-gray-500">({{ \App\Support\Number::rationalStringFromFloat($item->amount) }} {{ \Illuminate\Support\Str::plural('serving', $item->amount ) }})</span>
@else
{{ \App\Support\Number::rationalStringFromFloat($item->amount) }}
@if($item->unitFormatted){{ $item->unitFormatted }}@endif
@endif
@if($item->ingredient->type === \App\Models\Recipe::class) @if($item->ingredient->type === \App\Models\Recipe::class)
<a class="text-gray-500 hover:text-gray-700 hover:border-gray-300" <a class="text-gray-500 hover:text-gray-700 hover:border-gray-300"
href="{{ route('recipes.show', $item->ingredient) }}"> href="{{ route('recipes.show', $item->ingredient) }}">

View File

@ -26,8 +26,9 @@ Route::middleware(['auth'])->group(function () {
Route::get('/foods/{food}/delete', [FoodController::class, 'delete'])->name('foods.delete'); Route::get('/foods/{food}/delete', [FoodController::class, 'delete'])->name('foods.delete');
// Goals. // Goals.
Route::resource('goals', GoalController::class); Route::resource('goals', GoalController::class)->only(['index', 'create', 'store']);
Route::get('/goals/{goal}/delete', [GoalController::class, 'delete'])->name('goals.delete'); Route::resource('goals', GoalController::class)->except(['index', 'create', 'store'])->middleware(['can:access,goal']);
Route::get('/goals/{goal}/delete', [GoalController::class, 'delete'])->middleware(['can:access,goal'])->name('goals.delete');
// Ingredient picker. // Ingredient picker.
Route::get('/ingredient-picker/search', [IngredientPickerController::class, 'search'])->name('ingredient-picker.search'); Route::get('/ingredient-picker/search', [IngredientPickerController::class, 'search'])->name('ingredient-picker.search');
@ -51,10 +52,8 @@ Route::middleware(['auth'])->group(function () {
// Users. // Users.
Route::get('/profile/{user}', [ProfileController::class, 'show'])->name('profiles.show'); Route::get('/profile/{user}', [ProfileController::class, 'show'])->name('profiles.show');
});
Route::middleware(['auth', 'can:editProfile,user'])->group(function () {
// Profiles (non-admin Users variant). // Profiles (non-admin Users variant).
Route::get('/profile/{user}/edit', [ProfileController::class, 'edit'])->name('profiles.edit'); Route::get('/profile/{user}/edit', [ProfileController::class, 'edit'])->middleware(['can:editProfile,user'])->name('profiles.edit');
Route::put('/profile/{user}', [ProfileController::class, 'update'])->name('profiles.update'); Route::put('/profile/{user}', [ProfileController::class, 'update'])->middleware(['can:editProfile,user'])->name('profiles.update');
}); });

View File

@ -15,18 +15,36 @@ use Illuminate\Support\Facades\Artisan;
*/ */
if (!App::isProduction()) { if (!App::isProduction()) {
/**
* Clear all caches.
*/
Artisan::command('dev:cache-clear', function () {
/** @phpstan-ignore-next-line */
assert($this instanceof ClosureCommand);
$commands = [
'cache:clear',
'config:clear',
'route:clear',
'view:clear',
];
foreach ($commands as $command) {
Artisan::call($command);
$this->info(trim(Artisan::output()));
}
$this->info('All caches cleared!');
})->purpose('Clear all caches.');
/** /**
* Wipe, migrate, and seed the database. * Wipe, migrate, and seed the database.
*/ */
Artisan::command('dev:reset', function () { Artisan::command('dev:reset', function () {
/** @phpstan-ignore-next-line */ /** @phpstan-ignore-next-line */
assert($this instanceof ClosureCommand); assert($this instanceof ClosureCommand);
Artisan::call('db:wipe'); $commands = ['db:wipe', 'migrate', 'db:seed'];
$this->info(Artisan::output()); foreach ($commands as $command) {
Artisan::call('migrate'); Artisan::call($command);
$this->info(Artisan::output()); $this->info(trim(Artisan::output()));
Artisan::call('db:seed'); }
$this->info(Artisan::output());
$this->info('Database reset complete!'); $this->info('Database reset complete!');
})->purpose('Wipe, migrate, and seed the database.'); })->purpose('Wipe, migrate, and seed the database.');
} }

9
tailwind.config.js vendored
View File

@ -1,9 +1,8 @@
const defaultTheme = require('tailwindcss/defaultTheme'); const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = { module.exports = {
mode: 'jit',
purge: [ content: [
'./storage/framework/views/*.php', './storage/framework/views/*.php',
'./resources/views/**/*.blade.php', './resources/views/**/*.blade.php',
], ],
@ -16,12 +15,6 @@ module.exports = {
}, },
}, },
variants: {
extend: {
opacity: ['disabled'],
},
},
plugins: [ plugins: [
require('@tailwindcss/typography'), require('@tailwindcss/typography'),
require('@tailwindcss/forms') require('@tailwindcss/forms')

View File

@ -2,6 +2,7 @@
namespace Tests\Feature\Http\Controllers; namespace Tests\Feature\Http\Controllers;
use Algolia\AlgoliaSearch\Exceptions\UnreachableException;
use App\Http\Controllers\IngredientPickerController; use App\Http\Controllers\IngredientPickerController;
use App\Models\Food; use App\Models\Food;
use App\Models\Recipe; use App\Models\Recipe;
@ -42,8 +43,8 @@ class IngredientPickerControllerTest extends LoggedInTestCase
*/ */
public function testCanSearchWithAlgolia(): void public function testCanSearchWithAlgolia(): void
{ {
$this->expectException(ConnectException::class); $this->expectException(UnreachableException::class);
$this->expectExceptionMessageMatches("/Could not resolve host: \-dsn\.algolia\.net/"); $this->expectExceptionMessage("Impossible to connect, please check your Algolia Application Id.");
Config::set('scout.driver', 'algolia'); Config::set('scout.driver', 'algolia');
$response = $this->get($this->buildUrl(['term' => 'butter'])); $response = $this->get($this->buildUrl(['term' => 'butter']));