I am using a larvel backend with a vue frontend. They are connected by API. I have been trying to figure out how i can correctly submit a form for a model that has a foreign_key relationship with a parent model.
Here is the relationships
A User has many Tasks
A Task belongs to User
A Client has many Engagements
A Engagement belongs to Client
Engagements and Tasks have many to many
User and Client have no direct relationship
So I know that if i wanted to submit a post request for data that had a relationship with user It would be simple to assign the foreign_key like below
*Example
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string',
'completed' => 'required|boolean',
]);
$todo = Todo::create([
'user_id' => auth()->user()->id,
'title' => $request->title,
'completed' => $request->completed,
]);
return response($todo, 201);
}
But when i try to do this same thing for my client I get a 500 internal server error..
So here is the Workflow
On my vue frontend in the AddEngagment.vue component
<template>
<div class="page-wrapper mt-1">
<div class="add-engagement container">
<div class="card-body bg-light border-primary mb-2">
<h4 class="text-left text-primary m-0"><i class="mr-3 far fa-folder-open"></i>New Engagement</h4>
</div>
<form #submit.prevent="addEngagement" class="d-flex-column justify-content-center">
<div class="form-group">
<select class="form-control mb-3" id="type" v-model="engagement.return_type">
<option v-for="type in types" :key="type.id" :value="type">{{ type }}</option>
</select>
<input type="text" class="form-control mb-3" placeholder="Year" v-model="engagement.year">
<input type="text" class="form-control mb-3" placeholder="Assign To" v-model="engagement.assigned_to">
<input type="text" class="form-control mb-3" placeholder="Status" v-model="engagement.status">
<button type="submit" class="btn btn-lg btn-primary d-flex justify-content-start">Create</button>
</div>
</form>
</div>
</div>
</template>
<script>
export default {
name: 'add-engagement',
data() {
return {
engagement: {
return_type: null,
year: '',
assigned_to: '',
status: '',
},
types: [
'Choose Return Type...',
'1040',
'1120',
],
}
},
methods: {
addEngagement(e) {
if(!this.engagement.return_type || !this.engagement.year ){
return
} else {
this.$store.dispatch('addEngagement', {
id: this.idForEngagement,
return_type: this.engagement.return_type,
year: this.engagement.year,
assigned_to: this.engagement.assigned_to,
status: this.engagement.status,
})
e.preventDefault();
}
e.preventDefault();
this.engagement = ""
this.idForEngagement++
this.$router.push('/engagements')
},
},
created: function() {
this.engagement.return_type = this.types[0]
},
}
</script>
I am capturing the data in the input and then dispatching the store for the addEngagement action
Now in the URL for the AddEngagement.vue component I have the client_id shown like below
add-engagement/{client_id}
This is the store.js action for the addEngagement
addEngagement(context, engagement) {
axios.post(('/engagements'), {
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
.then(response => {
context.commit('getClientEngagement', response.data)
})
.catch(error => {
console.log(error)
})
},
So from here, it sends the request to my /engagement on the laravel api route file like below
Route::post('/engagements', 'EngagementsController#store');
And then goes to the EngagementsController to run the store method below
public function store($client_id, Request $request)
{
$data = $request->validate([
'return_type' => 'required|string',
'year' => 'required|string',
'status' => 'required|string',
'assigned_to' => 'required|string',
'done' => 'required|boolean'
]);
$engagement = Engagement::create([
'return_type' => $request->return_type,
'year' => $request->year,
'assigned_to' => $request->assigned_to,
'status' => $request->status,
'done' => $request->done,
+ [
'client_id' => $client_id
]
]);
return response($engagement, 201);
}
At this point is where I am getting my error and I think it has to do with the $client_id which I have tried many different ways of formatting it with no success. When the URL is storing the client_id on the front end for the post request I do not know how that data is getting shared with laravel for it to know which client the client_id foreign key will be assigned to.
First of all:
$engagement = Engagement::create([
'return_type' => $request->return_type,
'year' => $request->year,
'assigned_to' => $request->assigned_to,
'status' => $request->status,
'done' => $request->done,
+ [
'client_id' => $client_id
]
]);
why is there this +[...] array thing? Doesn't really makes sense to me...
But the 500 Server Error most probably results from your false request (which most of 500's do).
If this is your Route definition:
Route::post('/engagements', 'EngagementsController#store');
This is the method head:
public function store($client_id, Request $request)
And your Post request is the following:
axios.post(('/engagements'), {
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
You will notice, that the function definition differs from your Route. In the function you expect a parameter 'client_id' which isn't known by the route. So to resolve this issue, put your client_id into the Post request body (underneath your done: false for example) and remove the parameter from the function definition.
Related
LONG POST WARNING
why isn't my form to create a new user not working? im using laravel 9 and livewire. This is my code:
this is the button from where i show the model to create a form:
<div class="py-4 space-y-4">
<div class="flex justify-between px-2">
<div class="w-1/4">
<x-jet-input placeholder="search will go here"/>
</div>
<div>
<x-jet-button wire:click="create">New Skill</x-jet-button>
</div>
</div>
</div>
This is the model that shows the form. this model is also used to edit a skill as per Caleb the livewire creator:
<form wire:submit.prevent="save">
<x-jet-dialog-modal wire:model.defer="showEditModal">
<x-slot name="title">Edit Skill</x-slot>
<x-slot name="content">
<div class="col-span-6 sm:col-span-4">
<x-jet-label for="name" value="{{ __('Skill name') }}" />
<select wire:model="editing.name"
id="name"
type="text"
class="mt-1 block w-full border-gray-300
focus:border-indigo-300 focus:ring
focus:ring-indigo-200 focus:ring-opacity-50
rounded-md shadow-sm">
#foreach(\App\Models\Skill::LANGUAGES as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
#endforeach
</select>
<x-jet-input-error for="editing.name" class="mt-2" />
<x-jet-label for="years" value="{{ __('Years of experience') }}" class="mt-4"/>
<x-jet-input wire:model="editing.years" id="years" type="number"
min="{{\App\Models\Skill::MIN_YEARS_OF_EXPERIENCE}}"
max="{{\App\Models\Skill::MAX_YEARS_OF_EXPERIENCE}}"
class="mt-1 block w-full"
placeholder="Years of experience"/>
<x-jet-input-error for="editing.years" class="mt-2" />
</div>
</x-slot>
<x-slot name="footer">
<x-jet-secondary-button wire:click="$set('showEditModal', false)" class="mr-2">Cancel</x-jet-secondary-button>
<x-jet-button type="submit">Save</x-jet-button>
</x-slot>
</x-jet-dialog-modal>
</form>
And this is my livewire component:
<?php
namespace App\Http\Livewire;
use App\Models\Skill;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Skills extends Component
{
public $name ='';
public $showEditModal = false;
public Skill $editing;
public function rules()
{
return [
'editing.name' => 'required|in:'.collect(Skill::LANGUAGES)->keys()->implode(','),
'editing.years' => 'required|numeric|between:' . Skill::MIN_YEARS_OF_EXPERIENCE . ',' . Skill::MAX_YEARS_OF_EXPERIENCE,
];
}
public function render()
{
return view('livewire.skills', [
'skills' => Skill::where('user_id', auth()->id())->get(),
]);
}
public function mount(){
$this->editing = $this->makeBlankSkill();
}
public function makeBlankSkill(){
return Skill::make([
'name' => 'javascript',
'user_id' => auth()->user()->id,
]);
}
public function create(){
if ($this->editing->getKey()) $this->editing = $this->makeBlankSkill();
$this->showEditModal = true;
}
public function edit(Skill $skill) {
if ($this->editing->isNot($skill)) $this->editing = $skill;
$this->showEditModal = true;
}
public function save()
{
$this->validate();
$this->editing->save();
$this->showEditModal = false;
}
}
I keep getting SQLSTATE[HY000]: General error: 1364 Field 'user_id' doesn't have a default value and i dont know why.
This is my modal:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Skill extends Model
{
use HasFactory;
const DEFAULT_OPTION = 'Please select a skill';
const LANGUAGES = [
'javascript' => 'JavaScript',
'php' => 'PHP',
'python' => 'Python',
'java' => 'Java',
'c#' => 'C#',
'c++' => 'C++',
'ruby' => 'Ruby',
'swift' => 'Swift',
'typescript' => 'TypeScript',
'rust' => 'Rust',
'go' => 'Go',
'kotlin' => 'Kotlin',
'scala' => 'Scala',
'dart' => 'Dart',
'r' => 'R',
'perl' => 'Perl',
'elixir' => 'Elixir',
'clojure' => 'Clojure',
'haskell' => 'Haskell',
'erlang' => 'Erlang',
'lisp' => 'Lisp',
'sql' => 'SQL',
'bash' => 'Bash',
'laravel' => 'Laravel',
'symfony' => 'Symfony',
'codeigniter' => 'CodeIgniter',
'yii' => 'Yii',
'zend' => 'Zend',
'cakephp' => 'CakePHP',
'fuelphp' => 'FuelPHP',
'slim' => 'Slim',
'lumen' => 'Lumen',
'phalcon' => 'Phalcon',
'silex' => 'Silex',
'express' => 'Express',
'koa' => 'Koa',
'hapi' => 'Hapi',
'meteor' => 'Meteor',
'angular' => 'Angular',
'ember' => 'Ember',
'react' => 'React',
'vue' => 'Vue',
'backbone' => 'Backbone',
'd3' => 'D3',
'threejs' => 'Three.js',
];
const MIN_YEARS_OF_EXPERIENCE = 1;
const MAX_YEARS_OF_EXPERIENCE = 50;
protected $fillable = [
'name', 'user_id', 'years'
];
public function user()
{
return $this->belongsTo(User::class);
}
}
Any help is greatly appriceated
I've done all there is to do.At least i hope. I've added the
$illable
array ive set the
'user_id' => auth()->user()->id,
Not sure what else im missing
public function save()
{
$this->validate();
$user = auth()->user();
$this->editing->user_id = $user->id;
$this->editing->save();
$this->showEditModal = false;
}
This was the answer for me
If user_id is null when creating a new Skill, this means there is no authenticated user. You can simply check by doing dd(auth()->id()). If you're logged in, this will return the primary key for your authentication model. If this is empty, you're simply not authenticated, and so you must first log in.
In the case your user_id is actually set, but it isn't arriving in your database upon saving, you'll have to check if the property user_id is correctly set on the Skill model's protected $fillable property.
If you dd($this->editing) right after mount, you can check the attributes of the model, and if the user_id is set, you know the error happens when saving to the database.
As it turns out, Livewire won't hydrate newly set properties on models. This is because Livewire "rehydrates" the models by simply re-fetching them from the database. This can be solved defining a rules property as shown here, directly relating to the model properties. This would ensure Livewire keeps the state of the updated properties.
I have been looking online but i can't find any way of doing it. Let me explain, i have an API (Laravel passport on lumen), i tested it with Postman, i get my access token with oauth, everything is fine. Now i have another Laravel application and i want to know how i can keep all my authentification stuff using the API to login. I have seen lot of apps that actualy retrieve an api_token and they use 'Auth::user()->where('api_token', $token)'. But i find this wrong because i don't want my client to have access to the database, i want every request to the database to be handled by the API. Is that possible?
Let say you want to login to a laravel backend app via api. make sure you install guzzle.
Route(api): Route::POST('/login', 'AuthController#login')
Controller: AuthController.php
public function login(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|string',
]);
$http = new \GuzzleHttp\Client;
try {
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'your client_id',
'client_secret' => 'your client_secret',
'username' => $request->email,
'password' => $request->password,
// 'scope' => '',
],
]);
return $response->getBody();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if ($e->getCode() == 401) {
return response()->json(['message' => 'This action can\'t be perfomed at this time. Please try later.'], $e->getCode());
} else if ($e->getCode() == 400) {
return response()->json(['message' => 'These credentials do not match our records.'], $e->getCode());
}
return response()->json('Something went wrong on the server. Please try letar.', $e->getCode());
}
}
In your front-end app for example vuejs, even laravel using vue component. As you can see, i'm using boostrap-vue but feel free to use the regular html elements
<template>
<div>
<form #submit.prevent="login()">
<b-form-group label="Email">
<b-input placeholder="E-Mail" class="ml-1" v-model="form.email" type="email" name="email" :class="{ 'is-invalid': form.errors.has('email') }"/>
<has-error :form="form" field="email"></has-error>
</b-form-group>
<b-form-group>
<div slot="label" class="d-flex justify-content-between align-items-end">
<div>Password</div>
Forgot password?
</div>
<b-input v-model="form.password" type="password" name="password" :class="{ 'is-invalid': form.errors.has('password') }" />
<has-error :form="form" field="password"></has-error>
</b-form-group>
<div class="d-flex justify-content-between align-items-center m-0">
<b-check v-model="form.rememberMe" class="m-0">Remember me</b-check>
<b-btn type="submit" variant="primary">Sign In</b-btn>
</div>
</form>
</div>
<template>
<script>
export default ({
name: 'pages-authentication-login-v2',
metaInfo: {
title: 'Login'
},
state: {
token: localStorage.getItem('access_token'),
},
mutations: {
login(state, token) {
state.token = token
},
},
data: () => ({
form: new Form({
email: '',
password: '',
})
}),
methods: {
login(){
this.form.post('/api/login')
.then((response) =>{
const token = response.data.access_token
localStorage.setItem('access_token', token)
// console.log(response);
this.$router.push('/dashboard');
})
.catch((error)=>{
this.$toasted.error('Ooops! Something went wrong', {
icon : "warning",
theme: "bubble",
closeOnSwipe: true,
position: "top-right",
duration : 5000,
singleton: true,
})
});
},
}
})
</script>
I'm trying to use sync on a many to many that includes a status and a comment. I can sync the applications without status and comment just fine.
NewUserAccount Model
public function applications()
{
return $this->belongsToMany('App\Application', 'new_user_account_applications', 'new_user_id')->withPivot('application_comment', 'status');
}
Application Model
public function newUserAccounts()
{
return $this->belongsToMany('App\NewUserAccount', 'new_user_accounts_applications', 'new_user_id')->withPivot('application_comment', 'status');
}
My NewUserAccountController
public function store(StoreRequest $request)
{
$userAccount = NewUserAccount::create(array_merge(
$request->all(),
['submitted_by' => $requester->id],
['start_date' => Carbon::parse($request->input('start_date'))],
['account_expires' => $request->accountExpires('newAccountExpireDate')],
['company_id' => $requester->company_id],
['username' => $request->manuallyAssignId()]
));
// Here I sync applications and include application comment and status
$userAccount->applications()->sync($request->applications, ['application_comment' => $request->application_comment, 'status' => 0]);
....
}
My pivot showing status and comment correctly
My form. Here is where I'm not sure how to handle the comment and get it to save with each application pivot record.
#foreach($applications as $application)
<label class="k-checkbox">
<input value="{{ $application->id }}" name="applications[]" type="checkbox">{{ $application->application_name }} <span></span>
</label>
<div class="form-group col-lg-4 mb-3">
<label>Comments</label>
<textarea name="application_comment[]" class="form-control" rows="2"></textarea>
</div>
#endforeach
First, you need to set the correct index for the application_comment attribute in your textarea. It's needed to correctly determine the comment for each application.
#foreach($applications as $application)
...
<textarea name="application_comment[{{ $application->id }}]" class="form-control" rows="2"></textarea>
...
#endforeach
Then, you just need to format your data to:
$userAccount->applications()->sync([
application_id_1 => ['application_comment' => 'comment for application_id 1'],
application_id_2 => ['application_comment' => 'comment for application_id 2'],
...
]);
So, here it is
$applications = collect($request->applications)->mapWithKeys(function ($appId) use ($request) {
return [$appId => [
'application_comment' => $request->input('application_comment')[$appId],
'status' => 0,
]];
});
$userAccount->applications()->sync($applications);
Hey guys so I'm planning to have a list of my recent chats. The problem is that i just want to loop the same Id (ex. Chatroom_id), in my case its looping the same the Chatroom Id 5 times, cause im checking a db that reciever_id is mine and the result is 5, hence the 5 times it loops the same Chatroom ID. I just want it to loop once ever chatroom Id
Laravel View // where i insert my vue component
<!--- Message or online followers and followings -->
<div id="messagesidebar" class="app-sidebar-userdetails">
<h6 class="sm text-white"> <i> Recent Chat </i> </h6>
<div id="recentchat">
<recentchat-component :user_id="{{ Auth::user()->id }}"></recentchat-component>
</div>
</div>
<!-- /# Message or online followers and followings -->
</div>
Vue Template
<div class="chats" v-for="recentchat in recentchats" v-bind:key="recentchat.id">
{{ recentchat.chatroom_id}}
</div>
Laravel Controller
public function fetchrecentchat($user_id)
{
$recentchat = Chat::Where('receiver_id', $user_id)
->orderBy('created_at', 'DESC')
->get();
return ChatResource::collection($recentchat);
}
Chat Resource
public function toArray($request)
{
// return parent::toArray($request);
return [
'id' => $this->id,
'message' => $this->message,
'chatroom_id' => $this->chatroom_id,
'user_id' => $this->user_id,
'receiver_id' => $this->receiver_id,
'created_at' => $this->created_at->diffForHumans(),
'updated_at' => $this->updated_at,
];
}
Tell me if you need to review codes that i did not add above so much thanks guys!
Update
Vue script
<script>
export default {
props: [ 'user_id' ],
data() {
return {
recentchats: [],
recentchat: {
id: '',
chatroom_id: '',
user_id: '',
receiver_id: '',
created_at: '',
updated_at: '',
}
}
},
created() {
this.fetchchatrooms();
},
methods: {
fetchchatrooms(){
fetch('/api/fetchrecentchat/' +this.user_id)
.then(res => res.json())
.then(res => {
console.log(res);
this.recentchats = res.data
});
}
},
mounted() {
console.log('Recent Chat mounted.')
}
}
I'm trying to save the data I send from the Event view. in the storeEvent method of the EventController driver but it gives me error 422 and I can not find the problem so far.
The Event model has a many-to-many relationship with the Categories model, and Event also has a many-to-many relationship with the Coins model, which is why I occupy vue-multiselect since the user can select several categories or several coins for the same event
Event.vue
<template>
<form v-on:submit.prevent="createdEvent" class="form-horizontal">
<div class="form-group row">
<label>Titulo</label>
<input type="text" name="title" maxlength="25" v-model="title">
</div>
<div class="form-group row">
<label>*Cryptodivisas</label>
<multiselect v-model="coinvalue" :options="coins"
:multiple="true" label="name"
track-by="id" placeholder="Seleccione">
</multiselect>
</div>
<div class="form-group row">
<label>*Categoría</label>
<multiselect v-model="categoryvalue" :options="categories"
:multiple="true" label="name"
track-by="id" placeholder="Seleccione">
</multiselect>
</div>
<div class="col-sm-12">
<button class="btn" type="submit">Crear evento</button>
</div>
</form>
<script>
import Multiselect from 'vue-multiselect';
export default {
components: {
Multiselect,
},
props: ['auth'],
data () {
return {
user: {},
title: '',
coins: [],
coinvalue: [],
categories: [],
categoryvalue: [],
}
},
created() {
this.getCoins();
this.getCategories();
},
mounted() {
this.user = JSON.parse(this.auth);
},
methods: {
getCoins(){
let urlCoin = '/dashboard/coins';
axios.get(urlCoin)
.then((response) => {
this.coins = response.data;
})
.catch((err) => {
})
},
getCategories(){
let urlCategories = '/dashboard/categories';
axios.get(urlCategories)
.then((response) => {
this.categories = response.data;
})
.catch((err) => {
})
},
createdEvent(){
let urlEvent = '/dashboard/newEvent';
const eventData = {
'id' : this.user.id,
'title' : this.title,
'coin' : this.coinvalue,
'category' : this.categoryvalue,
}
console.log(eventData);
axios.post(urlEvent, eventData)
.then((response) => {
console.log(ok);
})
.catch((err) => {
console.log(err.response.data);
})
}
</script>
storeEvent (EventController)
public function storeEvent(Request $request)
{
$this->validate($request, [
'title' => 'required|max:25',
'coin' => 'required',
'category' => 'required',
]);
$userAuth = Auth::user()->id;
$userEvent = $request->id;
if($userAuth === $userEvent){
$event = new Event;
$event->user_id = $userEvent;
$event->title = $request->title;
if($event->save()){
$event->coins()->attach($request->coin);
$event->categories()->attach($request->category);
return response()->json([
'status' => 'Muy bien!',
'msg' => 'Tu evento ya fue creado con éxito.',
], 200);
}
else {
return response()->json([
'status' => 'Error!',
'msg' => 'No pudimos crear tu evento.',
], 401);
}
}
}
I think the problem may be when I assign the values to the coin () -> attach () and category () -> attach () section, but I have no idea how to solve it due to my inexperience in the tool.
The system was made entirely in Laravel and it worked without problems, now that it is being updated with Vue it began to give inconveniences.
Any ideas? I occupy Laravel 5,6, Vuejs 2, Axios and Vue-multiselect 2
Try sending form data
Here is the example for you.
var urlEvent = '/dashboard/newEvent';
var form_data = new FormData();
form_data.append('id',this.user.id);
form_data.append('title',this.title);
for(let coin of this.coinvalue)
form_data.append('coin[]', coin);
for(let category of this.categoryvalue)
form_data.append('category[]', category);
axios(urlEvent,{
method: "post",
data: form_data
})
.then(res => {
console.log(ok);
})
.catch(err => {
console.log(err.response.data);
});
If this stills gives you a 422 status code (Unprocessable entities). Then try returning $request in you controller. And check what data are actually send to the controller and what your validation is.
422 means validation error so do a console.log or inspect the element on axios call and check that :
'title' : this.title,
'coin' : this.coinvalue,
'category' : this.categoryvalue,
Are not empty, cause right now some data from above is missing since its a 422 validation exception.