Browse Source

Add outlet basic CRUD

php artisan make:crud Outlet
pull/3/head
Nafies Luthfi 7 years ago
parent
commit
9f2e246996
  1. 120
      app/Http/Controllers/OutletController.php
  2. 29
      app/Outlet.php
  3. 64
      app/Policies/OutletPolicy.php
  4. 1
      app/Providers/AuthServiceProvider.php
  5. 16
      database/factories/OutletFactory.php
  6. 36
      database/migrations/2018_12_10_125521_create_outlets_table.php
  7. 18
      resources/lang/en/app.php
  8. 33
      resources/lang/en/outlet.php
  9. 32
      resources/views/outlets/create.blade.php
  10. 60
      resources/views/outlets/edit.blade.php
  11. 56
      resources/views/outlets/index.blade.php
  12. 27
      resources/views/outlets/show.blade.php
  13. 5
      routes/web.php
  14. 32
      tests/BrowserKitTest.php
  15. 162
      tests/Feature/ManageOutletTest.php
  16. 38
      tests/Unit/Models/OutletTest.php
  17. 43
      tests/Unit/Policies/OutletPolicyTest.php

120
app/Http/Controllers/OutletController.php

@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers;
use App\Outlet;
use Illuminate\Http\Request;
class OutletController extends Controller
{
/**
* Display a listing of the outlet.
*
* @return \Illuminate\View\View
*/
public function index()
{
$outletQuery = Outlet::query();
$outletQuery->where('name', 'like', '%'.request('q').'%');
$outlets = $outletQuery->paginate(25);
return view('outlets.index', compact('outlets'));
}
/**
* Show the form for creating a new outlet.
*
* @return \Illuminate\View\View
*/
public function create()
{
$this->authorize('create', new Outlet);
return view('outlets.create');
}
/**
* Store a newly created outlet in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Redirector
*/
public function store(Request $request)
{
$this->authorize('create', new Outlet);
$newOutlet = $request->validate([
'name' => 'required|max:60',
'description' => 'nullable|max:255',
]);
$newOutlet['creator_id'] = auth()->id();
$outlet = Outlet::create($newOutlet);
return redirect()->route('outlets.show', $outlet);
}
/**
* Display the specified outlet.
*
* @param \App\Outlet $outlet
* @return \Illuminate\View\View
*/
public function show(Outlet $outlet)
{
return view('outlets.show', compact('outlet'));
}
/**
* Show the form for editing the specified outlet.
*
* @param \App\Outlet $outlet
* @return \Illuminate\View\View
*/
public function edit(Outlet $outlet)
{
$this->authorize('update', $outlet);
return view('outlets.edit', compact('outlet'));
}
/**
* Update the specified outlet in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Outlet $outlet
* @return \Illuminate\Routing\Redirector
*/
public function update(Request $request, Outlet $outlet)
{
$this->authorize('update', $outlet);
$outletData = $request->validate([
'name' => 'required|max:60',
'description' => 'nullable|max:255',
]);
$outlet->update($outletData);
return redirect()->route('outlets.show', $outlet);
}
/**
* Remove the specified outlet from storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Outlet $outlet
* @return \Illuminate\Routing\Redirector
*/
public function destroy(Request $request, Outlet $outlet)
{
$this->authorize('delete', $outlet);
$request->validate(['outlet_id' => 'required']);
if ($request->get('outlet_id') == $outlet->id && $outlet->delete()) {
return redirect()->route('outlets.index');
}
return back();
}
}

29
app/Outlet.php

@ -0,0 +1,29 @@
<?php
namespace App;
use App\User;
use Illuminate\Database\Eloquent\Model;
class Outlet extends Model
{
protected $fillable = ['name', 'description', 'creator_id'];
public function getNameLinkAttribute()
{
$title = __('app.show_detail_title', [
'name' => $this->name, 'type' => __('outlet.outlet'),
]);
$link = '<a href="'.route('outlets.show', $this).'"';
$link .= ' title="'.$title.'">';
$link .= $this->name;
$link .= '</a>';
return $link;
}
public function creator()
{
return $this->belongsTo(User::class);
}
}

64
app/Policies/OutletPolicy.php

@ -0,0 +1,64 @@
<?php
namespace App\Policies;
use App\User;
use App\Outlet;
use Illuminate\Auth\Access\HandlesAuthorization;
class OutletPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the outlet.
*
* @param \App\User $user
* @param \App\Outlet $outlet
* @return mixed
*/
public function view(User $user, Outlet $outlet)
{
// Update $user authorization to view $outlet here.
return true;
}
/**
* Determine whether the user can create outlet.
*
* @param \App\User $user
* @param \App\Outlet $outlet
* @return mixed
*/
public function create(User $user, Outlet $outlet)
{
// Update $user authorization to create $outlet here.
return true;
}
/**
* Determine whether the user can update the outlet.
*
* @param \App\User $user
* @param \App\Outlet $outlet
* @return mixed
*/
public function update(User $user, Outlet $outlet)
{
// Update $user authorization to update $outlet here.
return true;
}
/**
* Determine whether the user can delete the outlet.
*
* @param \App\User $user
* @param \App\Outlet $outlet
* @return mixed
*/
public function delete(User $user, Outlet $outlet)
{
// Update $user authorization to delete $outlet here.
return true;
}
}

1
app/Providers/AuthServiceProvider.php

@ -13,6 +13,7 @@ class AuthServiceProvider extends ServiceProvider
* @var array
*/
protected $policies = [
'App\Outlet' => 'App\Policies\OutletPolicy',
'App\Model' => 'App\Policies\ModelPolicy',
];

16
database/factories/OutletFactory.php

@ -0,0 +1,16 @@
<?php
use App\User;
use App\Outlet;
use Faker\Generator as Faker;
$factory->define(Outlet::class, function (Faker $faker) {
return [
'name' => $faker->word,
'description' => $faker->sentence,
'creator_id' => function () {
return factory(User::class)->create()->id;
},
];
});

36
database/migrations/2018_12_10_125521_create_outlets_table.php

@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateOutletsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('outlets', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 60);
$table->string('description')->nullable();
$table->unsignedInteger('creator_id');
$table->timestamps();
$table->foreign('creator_id')->references('id')->on('users')->onDelete('restrict');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('outlets');
}
}

18
resources/lang/en/app.php

@ -0,0 +1,18 @@
<?php
return [
// Labels
'table_no' => '#',
'total' => 'Total',
'action' => 'Actions',
'show_detail_title' => 'View :name :type detail',
// Actions
'show' => 'View Detail',
'edit' => 'Edit',
'delete' => 'Delete',
'cancel' => 'Cancel',
'reset' => 'Reset',
'delete_confirm' => 'Are you sure to delete this?',
'delete_confirm_button' => 'YES, delete it!',
];

33
resources/lang/en/outlet.php

@ -0,0 +1,33 @@
<?php
return [
// Labels
'outlet' => 'Outlet',
'list' => 'Outlet List',
'search' => 'Search Outlet',
'search_text' => 'Name ...',
'all' => 'All Outlet',
'select' => 'Select Outlet',
'detail' => 'Outlet Detail',
'not_found' => 'Outlet not found.',
'empty' => 'Outlet is empty.',
'back_to_show' => 'Back to Outlet Detail',
'back_to_index' => 'Back to Outlet List',
// Actions
'create' => 'Create new Outlet',
'created' => 'A new Outlet has been created.',
'show' => 'View Outlet Detail',
'edit' => 'Edit Outlet',
'update' => 'Update Outlet',
'updated' => 'Outlet data has been updated.',
'delete' => 'Delete Outlet',
'delete_confirm' => 'Are you sure to delete this Outlet?',
'deleted' => 'Outlet has been deleted.',
'undeleted' => 'Outlet not deleted.',
'undeleteable' => 'Outlet data cannot be deleted.',
// Attributes
'name' => 'Outlet Name',
'description' => 'Outlet Description',
];

32
resources/views/outlets/create.blade.php

@ -0,0 +1,32 @@
@extends('layouts.app')
@section('title', __('outlet.create'))
@section('content')
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">{{ __('outlet.create') }}</div>
<form method="POST" action="{{ route('outlets.store') }}" accept-charset="UTF-8">
{{ csrf_field() }}
<div class="card-body">
<div class="form-group">
<label for="name" class="control-label">{{ __('outlet.name') }}</label>
<input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required>
{!! $errors->first('name', '<span class="invalid-feedback" role="alert">:message</span>') !!}
</div>
<div class="form-group">
<label for="description" class="control-label">{{ __('outlet.description') }}</label>
<textarea id="description" class="form-control{{ $errors->has('description') ? ' is-invalid' : '' }}" name="description" rows="4">{{ old('description') }}</textarea>
{!! $errors->first('description', '<span class="invalid-feedback" role="alert">:message</span>') !!}
</div>
</div>
<div class="card-footer">
<input type="submit" value="{{ __('outlet.create') }}" class="btn btn-success">
<a href="{{ route('outlets.index') }}" class="btn btn-link">{{ __('app.cancel') }}</a>
</div>
</form>
</div>
</div>
</div>
@endsection

60
resources/views/outlets/edit.blade.php

@ -0,0 +1,60 @@
@extends('layouts.app')
@section('title', __('outlet.edit'))
@section('content')
<div class="row justify-content-center">
<div class="col-md-6">
@if (request('action') == 'delete' && $outlet)
@can('delete', $outlet)
<div class="card">
<div class="card-header">{{ __('outlet.delete') }}</div>
<div class="card-body">
<label class="control-label text-primary">{{ __('outlet.name') }}</label>
<p>{{ $outlet->name }}</p>
<label class="control-label text-primary">{{ __('outlet.description') }}</label>
<p>{{ $outlet->description }}</p>
{!! $errors->first('outlet_id', '<span class="invalid-feedback" role="alert">:message</span>') !!}
</div>
<hr style="margin:0">
<div class="card-body text-danger">{{ __('outlet.delete_confirm') }}</div>
<div class="card-footer">
<form method="POST" action="{{ route('outlets.destroy', $outlet) }}" accept-charset="UTF-8" onsubmit="return confirm(&quot;{{ __('app.delete_confirm') }}&quot;)" class="del-form float-right" style="display: inline;">
{{ csrf_field() }} {{ method_field('delete') }}
<input name="outlet_id" type="hidden" value="{{ $outlet->id }}">
<button type="submit" class="btn btn-danger">{{ __('app.delete_confirm_button') }}</button>
</form>
<a href="{{ route('outlets.edit', $outlet) }}" class="btn btn-link">{{ __('app.cancel') }}</a>
</div>
</div>
@endcan
@else
<div class="card">
<div class="card-header">{{ __('outlet.edit') }}</div>
<form method="POST" action="{{ route('outlets.update', $outlet) }}" accept-charset="UTF-8">
{{ csrf_field() }} {{ method_field('patch') }}
<div class="card-body">
<div class="form-group">
<label for="name" class="control-label">{{ __('outlet.name') }}</label>
<input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name', $outlet->name) }}" required>
{!! $errors->first('name', '<span class="invalid-feedback" role="alert">:message</span>') !!}
</div>
<div class="form-group">
<label for="description" class="control-label">{{ __('outlet.description') }}</label>
<textarea id="description" class="form-control{{ $errors->has('description') ? ' is-invalid' : '' }}" name="description" rows="4">{{ old('description', $outlet->description) }}</textarea>
{!! $errors->first('description', '<span class="invalid-feedback" role="alert">:message</span>') !!}
</div>
</div>
<div class="card-footer">
<input type="submit" value="{{ __('outlet.update') }}" class="btn btn-success">
<a href="{{ route('outlets.show', $outlet) }}" class="btn btn-link">{{ __('app.cancel') }}</a>
@can('delete', $outlet)
<a href="{{ route('outlets.edit', [$outlet, 'action' => 'delete']) }}" id="del-outlet-{{ $outlet->id }}" class="btn btn-danger float-right">{{ __('app.delete') }}</a>
@endcan
</div>
</form>
</div>
</div>
</div>
@endif
@endsection

56
resources/views/outlets/index.blade.php

@ -0,0 +1,56 @@
@extends('layouts.app')
@section('title', __('outlet.list'))
@section('content')
<div class="mb-3">
<div class="float-right">
@can('create', new App\Outlet)
<a href="{{ route('outlets.create') }}" class="btn btn-success">{{ __('outlet.create') }}</a>
@endcan
</div>
<h1 class="page-title">{{ __('outlet.list') }} <small>{{ __('app.total') }} : {{ $outlets->total() }} {{ __('outlet.outlet') }}</small></h1>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<form method="GET" action="" accept-charset="UTF-8" class="form-inline">
<div class="form-group">
<label for="q" class="control-label">{{ __('outlet.search') }}</label>
<input placeholder="{{ __('outlet.search_text') }}" name="q" type="text" id="q" class="form-control mx-sm-2" value="{{ request('q') }}">
</div>
<input type="submit" value="{{ __('outlet.search') }}" class="btn btn-secondary">
<a href="{{ route('outlets.index') }}" class="btn btn-link">{{ __('app.reset') }}</a>
</form>
</div>
<table class="table table-sm table-responsive-sm">
<thead>
<tr>
<th class="text-center">{{ __('app.table_no') }}</th>
<th>{{ __('outlet.name') }}</th>
<th>{{ __('outlet.description') }}</th>
<th class="text-center">{{ __('app.action') }}</th>
</tr>
</thead>
<tbody>
@foreach($outlets as $key => $outlet)
<tr>
<td class="text-center">{{ $outlets->firstItem() + $key }}</td>
<td>{!! $outlet->name_link !!}</td>
<td>{{ $outlet->description }}</td>
<td class="text-center">
@can('view', $outlet)
<a href="{{ route('outlets.show', $outlet) }}" id="show-outlet-{{ $outlet->id }}">{{ __('app.show') }}</a>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="card-body">{{ $outlets->appends(Request::except('page'))->render() }}</div>
</div>
</div>
</div>
@endsection

27
resources/views/outlets/show.blade.php

@ -0,0 +1,27 @@
@extends('layouts.app')
@section('title', __('outlet.detail'))
@section('content')
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">{{ __('outlet.detail') }}</div>
<div class="card-body">
<table class="table table-sm">
<tbody>
<tr><td>{{ __('outlet.name') }}</td><td>{{ $outlet->name }}</td></tr>
<tr><td>{{ __('outlet.description') }}</td><td>{{ $outlet->description }}</td></tr>
</tbody>
</table>
</div>
<div class="card-footer">
@can('update', $outlet)
<a href="{{ route('outlets.edit', $outlet) }}" id="edit-outlet-{{ $outlet->id }}" class="btn btn-warning">{{ __('outlet.edit') }}</a>
@endcan
<a href="{{ route('outlets.index') }}" class="btn btn-link">{{ __('outlet.back_to_index') }}</a>
</div>
</div>
</div>
</div>
@endsection

5
routes/web.php

@ -18,3 +18,8 @@ Route::get('/', function () {
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
/*
* Outlets Routes
*/
Route::resource('outlets', 'OutletController');

32
tests/BrowserKitTest.php

@ -0,0 +1,32 @@
<?php
namespace Tests;
use App\User;
use Laravel\BrowserKitTesting\TestCase as BaseTestCase;
abstract class BrowserKitTest extends BaseTestCase
{
use CreatesApplication;
protected $baseUrl = 'http://localhost';
protected function setUp()
{
parent::setUp();
\Hash::setRounds(5);
}
protected function loginAsUser()
{
$user = $this->createUser();
$this->actingAs($user);
return $user;
}
protected function createUser()
{
return factory(User::class)->create();
}
}

162
tests/Feature/ManageOutletTest.php

@ -0,0 +1,162 @@
<?php
namespace Tests\Feature;
use App\Outlet;
use Tests\BrowserKitTest as TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ManageOutletTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function user_can_see_outlet_list_in_outlet_index_page()
{
$outlet = factory(Outlet::class)->create();
$this->loginAsUser();
$this->visitRoute('outlets.index');
$this->see($outlet->name);
}
private function getCreateFields(array $overrides = [])
{
return array_merge([
'name' => 'Outlet 1 name',
'description' => 'Outlet 1 description',
], $overrides);
}
/** @test */
public function user_can_create_a_outlet()
{
$this->loginAsUser();
$this->visitRoute('outlets.index');
$this->click(__('outlet.create'));
$this->seeRouteIs('outlets.create');
$this->submitForm(__('outlet.create'), $this->getCreateFields());
$this->seeRouteIs('outlets.show', Outlet::first());
$this->seeInDatabase('outlets', $this->getCreateFields());
}
/** @test */
public function validate_outlet_name_is_required()
{
$this->loginAsUser();
// name empty
$this->post(route('outlets.store'), $this->getCreateFields(['name' => '']));
$this->assertSessionHasErrors('name');
}
/** @test */
public function validate_outlet_name_is_not_more_than_60_characters()
{
$this->loginAsUser();
// name 70 characters
$this->post(route('outlets.store'), $this->getCreateFields([
'name' => str_repeat('Test Title', 7),
]));
$this->assertSessionHasErrors('name');
}
/** @test */
public function validate_outlet_description_is_not_more_than_255_characters()
{
$this->loginAsUser();
// description 256 characters
$this->post(route('outlets.store'), $this->getCreateFields([
'description' => str_repeat('Long description', 16),
]));
$this->assertSessionHasErrors('description');
}
private function getEditFields(array $overrides = [])
{
return array_merge([
'name' => 'Outlet 1 name',
'description' => 'Outlet 1 description',
], $overrides);
}
/** @test */
public function user_can_edit_a_outlet()
{
$this->loginAsUser();
$outlet = factory(Outlet::class)->create(['name' => 'Testing 123']);
$this->visitRoute('outlets.show', $outlet);
$this->click('edit-outlet-'.$outlet->id);
$this->seeRouteIs('outlets.edit', $outlet);
$this->submitForm(__('outlet.update'), $this->getEditFields());
$this->seeRouteIs('outlets.show', $outlet);
$this->seeInDatabase('outlets', $this->getEditFields([
'id' => $outlet->id,
]));
}
/** @test */
public function validate_outlet_name_update_is_required()
{
$this->loginAsUser();
$outlet = factory(Outlet::class)->create(['name' => 'Testing 123']);
// name empty
$this->patch(route('outlets.update', $outlet), $this->getEditFields(['name' => '']));
$this->assertSessionHasErrors('name');
}
/** @test */
public function validate_outlet_name_update_is_not_more_than_60_characters()
{
$this->loginAsUser();
$outlet = factory(Outlet::class)->create(['name' => 'Testing 123']);
// name 70 characters
$this->patch(route('outlets.update', $outlet), $this->getEditFields([
'name' => str_repeat('Test Title', 7),
]));
$this->assertSessionHasErrors('name');
}
/** @test */
public function validate_outlet_description_update_is_not_more_than_255_characters()
{
$this->loginAsUser();
$outlet = factory(Outlet::class)->create(['name' => 'Testing 123']);
// description 256 characters
$this->patch(route('outlets.update', $outlet), $this->getEditFields([
'description' => str_repeat('Long description', 16),
]));
$this->assertSessionHasErrors('description');
}
/** @test */
public function user_can_delete_a_outlet()
{
$this->loginAsUser();
$outlet = factory(Outlet::class)->create();
factory(Outlet::class)->create();
$this->visitRoute('outlets.edit', $outlet);
$this->click('del-outlet-'.$outlet->id);
$this->seeRouteIs('outlets.edit', [$outlet, 'action' => 'delete']);
$this->press(__('app.delete_confirm_button'));
$this->dontSeeInDatabase('outlets', [
'id' => $outlet->id,
]);
}
}

38
tests/Unit/Models/OutletTest.php

@ -0,0 +1,38 @@
<?php
namespace Tests\Unit\Models;
use App\User;
use App\Outlet;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\BrowserKitTest as TestCase;
class OutletTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function a_outlet_has_name_link_attribute()
{
$outlet = factory(Outlet::class)->create();
$title = __('app.show_detail_title', [
'name' => $outlet->name, 'type' => __('outlet.outlet'),
]);
$link = '<a href="'.route('outlets.show', $outlet).'"';
$link .= ' title="'.$title.'">';
$link .= $outlet->name;
$link .= '</a>';
$this->assertEquals($link, $outlet->name_link);
}
/** @test */
public function a_outlet_has_belongs_to_creator_relation()
{
$outlet = factory(Outlet::class)->make();
$this->assertInstanceOf(User::class, $outlet->creator);
$this->assertEquals($outlet->creator_id, $outlet->creator->id);
}
}

43
tests/Unit/Policies/OutletPolicyTest.php

@ -0,0 +1,43 @@
<?php
namespace Tests\Unit\Policies;
use App\Outlet;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\BrowserKitTest as TestCase;
class OutletPolicyTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function user_can_create_outlet()
{
$user = $this->createUser();
$this->assertTrue($user->can('create', new Outlet));
}
/** @test */
public function user_can_view_outlet()
{
$user = $this->createUser();
$outlet = factory(Outlet::class)->create();
$this->assertTrue($user->can('view', $outlet));
}
/** @test */
public function user_can_update_outlet()
{
$user = $this->createUser();
$outlet = factory(Outlet::class)->create();
$this->assertTrue($user->can('update', $outlet));
}
/** @test */
public function user_can_delete_outlet()
{
$user = $this->createUser();
$outlet = factory(Outlet::class)->create();
$this->assertTrue($user->can('delete', $outlet));
}
}
Loading…
Cancel
Save