Skip to content

Instantly share code, notes, and snippets.

@hovsep
Created May 21, 2018 09:11
Show Gist options
  • Save hovsep/734471e36d7be145d38c6df389072a44 to your computer and use it in GitHub Desktop.
Save hovsep/734471e36d7be145d38c6df389072a44 to your computer and use it in GitHub Desktop.
Example of Laravel code
<?php
namespace App\Http\Controllers;
use App\Entities\CargoCategory;
use App\Entities\CargoState;
use App\Entities\DocumentType;
use App\Entities\PaymentTerm;
use App\Entities\LocationType;
use App\Entities\TruckFeature;
use App\Entities\TruckType;
use App\Helpers\GoogleMap;
use App\Jobs\NotifyCarriersAboutNewLoad;
use App\Models\Cargo;
use App\Models\Document;
use App\Models\Location;
use App\Models\SearchFilter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Session;
class CargoController extends Controller
{
/**
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$this->authorize('create', Cargo::class);
$this->validate($request, [
'from_districts' => 'required|array',
'from_districts.*' => 'string|exists:districts,summ',
'to_districts' => 'required|array',
'to_districts.*' => 'string|exists:districts,summ',
'pickup_range' => 'required|string',
'delivery_range' => 'required|string',
'category' => 'required|string|in:' . CargoCategory::all()->implode(','),
'weight' => 'required|numeric|min:0',
'volume' => 'required|numeric|min:0',
'length' => 'required|numeric|min:0',
'width' => 'required|numeric|min:0',
'height' => 'required|numeric|min:0',
'required_truck_types' => 'required|array',
'required_truck_types.*' => 'string|in:' . TruckType::all()->implode(','),
'required_truck_features' => 'nullable|array',
'required_truck_features.*' => 'string|in:' . TruckFeature::all()->implode(','),
'description' => 'nullable|string',
'payment_term' => 'required|string|in:' . PaymentTerm::all()->implode(','),
'auction_enabled' => 'sometimes|bool',
'budget' => 'sometimes|required_without:auction_enabled|numeric|min:0',
'photos' => 'sometimes|array',
'photos.*' => 'file|mimes:jpeg,gif,png'
]);
list($pickupStartDate, $pickupEndDate) = explode(' - ', $request->pickup_range);
list($deliveryStartDate, $deliveryEndDate) = explode(' - ', $request->delivery_range);
try {
$cargo = new Cargo($request->all());
if (Auth::user()->is_employee) {
$cargo->owner_id = Auth::user()->employer->id;
} else {
$cargo->owner_id = Auth::id();
}
$cargo->pickup_from = $pickupStartDate;
$cargo->pickup_to = $pickupEndDate;
$cargo->delivery_from = $deliveryStartDate;
$cargo->delivery_to = $deliveryEndDate;
$cargo->auction_enabled = !($cargo->budget > 0);
DB::transaction(function() use ($cargo, $request) {
$cargo->save();
if ($request->files->has('photos')) {
foreach ($request->files->get('photos') as $key => $item) {
$upload = $request->photos[$key];
$document = new Document([
'document_type' => DocumentType::CARGO_PHOTO,
'name' => $upload->getClientOriginalName(),
'filename' => $upload->storeAs('cargo', "cargo-{$cargo->id}" . DIRECTORY_SEPARATOR . uniqid() . '.' . $upload->extension(), 'public'),
]);
$cargo->photos()->save($document);
}
}
foreach($request->from_districts as $fromDistrictId) {
$location = new Location([
'district_hash_sum' => $fromDistrictId,
'type' => LocationType::FROM
]);
$cargo->locations()->save($location);
}
unset($location);
foreach($request->to_districts as $toDistrictId) {
$location = new Location([
'district_hash_sum' => $toDistrictId,
'type' => LocationType::TO
]);
$cargo->locations()->save($location);
}
$cargo->distance = GoogleMap::distance($cargo);
$cargo->save();
dispatch_now(new NotifyCarriersAboutNewLoad($cargo));
});
} catch (\Exception $e) {
return response()->json(['error' => 'Failed to save cargo. Reason: ' . $e->getMessage()], 500);
}
return response()->json(['msg' => 'Cargo was posted']);
}
/**
* @param Cargo $cargo
* @param Request $request
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
*/
public function show(Cargo $cargo, Request $request)
{
try {
$cargo->load([
'questions.owner.photo',
'owner.photo',
'carrier',
'bids.cargo',
'bids.owner.photo',
'bids.truck',
'bids.messages.sender.photo'
]);
//@todo: filter messages from other carriers
if ($request->isXmlHttpRequest()) {
return response()->json($cargo);
} else {
return view('cargo.page.index', ['cargo' => $cargo]);
}
} catch (\Exception $e) {
Log::error('Failed to show cargo', ['id' => $cargo->id, 'reason' => $e->getMessage()]);
if ($request->isXmlHttpRequest()) {
return response()->json(['error' => 'Failed to show cargo. Reason: ' . $e->getMessage()], 500);
} else {
return Redirect::back()->with('app-error', 'Something goes wrong');
}
}
}
/**
* @param Cargo $cargo
* @param Request $request
* @return mixed
*/
public function update(Cargo $cargo, Request $request)
{
$this->authorize('update', $cargo);
$this->validate($request, [
'state' => 'sometimes|string|in:' . CargoState::all()->implode(','),
'canceling_reason' => 'required_if:state,' . CargoState::CANCELED . '|string'
]);
try {
$cargo->fill($request->all());
$cargo->save();
return Redirect::back()->with('app-message', 'Cargo updated');
} catch (\Exception $e) {
Log::warning('Failed to update cargo', ['reason' => $e->getMessage()]);
return Redirect::back()->with('app-error', 'Something goes wrong');
}
}
/**
* @param Cargo $cargo
* @return mixed
*/
public function destroy(Cargo $cargo)
{
$this->authorize('delete', $cargo);
try {
if (!$cargo->delete()) {
throw new \Exception('DB error');
}
Session::flash('app-message', 'Cargo deleted');
} catch (\Exception $e) {
Log::error('Failed to delete cargo. Reason:' . $e->getMessage());
Session::flash('app-error', 'Failed to delete cargo');
}
return Redirect::back();
}
public function search(Request $request)
{
$filterParams = $request->except(['filter_name', 'save_filter', 'filter_id']);
if (!Auth::guest()) {
//Save new search filter
if ($request->has('save_filter') && $request->filled('filter_name')) {
SearchFilter::create([
'name' => $request->filter_name,
'params' => $filterParams,
'owner_id' => Auth::id()
]);
}
//Apply saved filter
if ($request->has('filter_id') && !$request->has('save_filter') && !$request->filled('filter_name')) {
$savedFilter = SearchFilter::find($request->filter_id);
if (!empty($savedFilter)) {
return redirect()->route('cargo.search', $savedFilter->params);
}
}
}
$cargoes = Cargo::search($filterParams);
return view('cargo.search.index', [
'cargoes' => $cargoes,
'filter' => $filterParams
]);
}
public function getQuestions(Cargo $cargo)
{
return response()->json($cargo->questions()->with('owner.photo')->get());
}
public function getBids(Cargo $cargo)
{
return response()->json($cargo->bids()->with(['owner', 'truck'])->get());
}
}
<?php
namespace App\Models;
use App\Entities\CargoState;
use App\Entities\LocationType;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Cargo extends Model
{
use SoftDeletes;
protected $table = 'cargoes';
protected $fillable = [
'pickup_from',
'pickup_to',
'delivery_from',
'delivery_to',
'category',
'weight',
'volume',
'length',
'width',
'height',
'required_truck_types',
'required_truck_features',
'description',
'payment_term',
'auction_enabled',
'budget',
'distance',
'owner_id',
'carrier_id',
'state',
'canceling_reason'
];
protected $casts = [
'required_truck_features' => 'array',//Has workaround @see getRequiredTruckFeaturesAttribute
'required_truck_types' => 'array'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'deleted_at',
'pickup_from',
'pickup_to',
'delivery_from',
'delivery_to',
];
protected $appends = [
'applies_bids',
'has_carrier'
];
public function owner()
{
return $this->belongsTo(User::class);
}
public function carrier()
{
return $this->belongsTo(User::class);
}
public function photos()
{
return $this->morphMany(Document::class, 'parent');
}
public function locations()
{
return $this->morphMany(Location::class, 'parent');
}
public function fromLocations()
{
return $this->locations()->where('type', LocationType::FROM);
}
public function toLocations()
{
return $this->locations()->where('type', LocationType::TO);
}
public function getStartLocationAttribute()
{
return $this->fromLocations()->first();
}
public function getFinishLocationAttribute()
{
return $this->toLocations()->latest('id')->first();
}
public function questions()
{
return $this->hasMany(Question::class);
}
public function bids()
{
return $this->hasMany(Bid::class);
}
public static function search(array $filter)
{
$results = null;
$query = self::with('locations')->where('state', CargoState::ACTIVE);
if (!empty($filter['from_district'])) {
$query->whereHas('fromLocations', function($q) use ($filter) {
$q->where('district_hash_sum', $filter['from_district']);
});
}
if (!empty($filter['to_district'])) {
$query->whereHas('toLocations', function($q) use ($filter) {
$q->where('district_hash_sum', $filter['to_district']);
});
}
if (!empty($filter['pickup_range'])) {
list($pickupStart, $pickupEnd) = explode(' - ', $filter['pickup_range']);
if (!empty($pickupStart)) {
$query->where('pickup_from', '>=', $pickupStart);
}
if (!empty($pickupEnd)) {
$query->where('pickup_to', '<=', $pickupEnd);
}
}
if (!empty($filter['delivery_range'])) {
list($deliveryStart, $deliveryEnd) = explode(' - ', $filter['delivery_range']);
if (!empty($deliveryStart)) {
$query->where('delivery_from', '>=', $deliveryStart);
}
if (!empty($deliveryEnd)) {
$query->where('delivery_to', '<=', $deliveryEnd);
}
}
if (!empty($filter['categories']) && is_array($filter['categories'])) {
$query->whereIn('category', $filter['categories']);
}
if (!empty($filter['truck_types']) && is_array($filter['truck_types'])) {
$query->whereRaw(sprintf('JSON_CONTAINS(required_truck_types, \'%s\' )', json_encode($filter['truck_types'])));
}
if (!empty($filter['truck_features']) && is_array($filter['truck_features'])) {
$query->whereRaw(sprintf('JSON_CONTAINS(required_truck_features, \'%s\' )', json_encode($filter['truck_features'])));
}
if (!empty($filter['weight'])) {
$query->where('weight', '<=', (int) $filter['weight']);
}
if (!empty($filter['volume'])) {
$query->where('volume', '<=', (int) $filter['volume']);
}
if (!empty($filter['length'])) {
$query->where('length', '<=', (int) $filter['length']);
}
if (!empty($filter['width'])) {
$query->where('width', '<=', (int) $filter['width']);
}
if (!empty($filter['height'])) {
$query->where('height', '<=', (int) $filter['height']);
}
if (!empty($filter['added_today'])) {
$query->whereDate('created_at', today());
}
$results = $query->paginate(empty($filter['per_page']) ? 30 : $filter['per_page']);
return $results;
}
public function getHasCarrierAttribute()
{
return (bool) (($this->state == CargoState::HAS_CARRIER) && (!empty($this->carrier_id)));
}
public function getAppliesBidsAttribute()
{
return (bool) (($this->state == CargoState::ACTIVE) && empty($this->carrier_id));
}
public function getRequiredTruckFeaturesAttribute($value)
{
return empty($value) ? [] : json_decode($value);
}
public function review()
{
return $this->hasMany(Review::class);
}
public static function filter(array $filter = [])
{
$query = self::select();
if (!empty($filter['ids'])) {
$ids = explode(',', $filter['ids']);
if (!empty($ids)) {
$query->whereIn('id', $ids);
}
}
if (!empty($filter['state']) && is_array($filter['state'])) {
$query->whereIn('state', $filter['state']);
}
if (!empty($filter['category']) && is_array($filter['category'])) {
$query->whereIn('category', $filter['category']);
}
return $results = $query->paginate(empty($filter['per_page']) ? 30 : $filter['per_page']);
}
}
<template>
<div>
<p class="text-center">
<a data-toggle="collapse" :href="'#' + messagesBoxId" :dusk="'bid-' + bid.id + '-expand-messages'">
<template v-if="bid.messages.length > 0">
{{ bid.messages.length }} messages
</template>
<template v-else>
New message
</template>
</a>
</p>
<div :id="messagesBoxId" class="collapse">
<template v-if="bid.messages.length > 0">
<ul class="chat-list p-20 bg-white">
<template v-for="m in bid.messages">
<li class="reverse" v-if="m.sender_id == user.id">
<div class="chat-time">
{{ m.created_at }}
</div>
<div class="chat-content">
<h5>
{{ m.sender.full_name }}
</h5>
<div class="box bg-light-success">
{{ m.text }}
</div>
</div>
<div class="chat-img" v-if="!_.isEmpty(m.sender.photo)">
<img :src="m.sender.photo.url">
</div>
</li>
<li v-else>
<div class="chat-img" v-if="!_.isEmpty(m.sender.photo)">
<img :src="m.sender.photo.url">
</div>
<div class="chat-content">
<h5>
{{ m.sender.full_name }}
</h5>
<div class="box bg-light-info">
{{ m.text }}
</div>
</div>
<div class="chat-time">
{{ m.created_at }}
</div>
</li>
</template>
</ul>
</template>
<template v-else>
<p class="text-center">
No messages
</p>
</template>
<div class="card-body b-t" v-if="showForm">
<div class="row">
<div class="col-12">
<textarea class="form-control b-0" placeholder="Type your message here" v-model="newMessage.text" dusk="message-text"></textarea>
<div class="text-danger" v-if="validationErrors.text">
{{ _.first(validationErrors.text) }}
</div>
<div class="text-center m-t-10">
<button type="button" class="btn btn-info waves-effect waves-light" @click.prevent="submit" dusk="btn-submit-message">
<i class="fa fa-paper-plane-o"></i>
Submit
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
preloadedBid: {
type: Object,
required: true
},
//Logged in user
user: {
type: Object,
required: true
}
},
computed: {
messagesBoxId: function() {
return 'bid_' + this.bid.id + '_messages';
},
showForm: function() {
return ((this.user.id == this.bid.owner_id) || (this.user.id == this.bid.cargo.owner_id));
}
},
data: function() {
return {
bid: _.defaultTo(this.preloadedBid, {}),
newMessage:{
text: ''
},
validationErrors: {}
}
},
created: function() {
var vm = this;
bus.$on([
'new-message',
], function() {
vm.loadMessages();
});
},
methods: {
submit: function() {
var vm = this;
axios.post('/message', {
text: vm.newMessage.text,
context_id: vm.bid.id,
context_type: "App\\Models\\Bid"
}).then(function (response) {
bus.$emit('new-message');
vm.validationErrors = {};
vm.newMessage.text = '';
if (response && response.data.msg) {
bus.$emit('app-message', response.data.msg);
}
}).catch(function (error) {
if (error.response && (error.response.status == 500) && error.response.data.error) {
bus.$emit('app-error', error.response.data.error);
}
if (error.response && (error.response.status == 422)) {
Vue.set(vm, 'validationErrors', error.response.data.errors);
}
});
},
loadMessages: function() {
var vm = this;
axios.get('/bid/' + vm.bid.id + '/messages')
.then(function(response) {
if (!_.isEmpty(response.data)) {
Vue.set(vm.bid, 'messages', response.data);
}
})
.catch(function(error) {
bus.$emit('app-error', 'Failed to load messages');
});
}
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment