live-chat application message and user model - php

I'm developing a live chat application and i don't know which is the proper db structure. I thought that the best solution is to have a Message model that belongs to a Sender and to a Receiver. In this way things should work, but when i seed i have a lot of problems, like only the first user got the relations accessible. I share my code below.
Message Migration
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->text('content');
$table->foreignUuid('from_id')->references('uuid')->on('users');
$table->foreignUuid('to_id')->references('uuid')->on('users');
});
}
User migration
$table->id();
$table->uuid('uuid')->unique();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('profile')->default(public_path('images/default_profile.jpg'));
$table->rememberToken();
$table->timestamps();
Message Model
class Message extends Model
{
use HasFactory;
protected $table = "messages";
public function sender()
{
return $this->belongsTo(User::class, 'from_id');
}
public function receiver()
{
return $this->belongsTo(User::class, 'to_id');
}
}
User model
public function sent()
{
return $this->hasMany(Message::class, 'from_id');
}
public function received()
{
return $this->hasMany(Message::class, 'to_id');
}
public function contacts()
{
DatabaseSeeder
User::factory()->create([
'name' => 'username',
'email' => 'admin#admin.com',
'password' => bcrypt('password')
]);
Message::factory()
->count(200)
->create();
User::factory()->count(10)->create();
$users = User::all();
$messages = Message::all();
User::all()->each(function ($user) use ($users) {
do {
$idsToAttach = $users->random(rand(1, 3))->pluck('id')->toArray();
$idsToAttach = array_diff($idsToAttach, array($user->id));
} while (count($idsToAttach) <= 0);
$user->contacts()->attach(
$idsToAttach
);
});
$messages->each(function ($message) use ($users) {
do {
$sender = $users->random(1)->pluck('uuid');
$receiver = $users->random(1)->pluck('uuid');
} while ($sender[0] == $receiver[0]);
$message->receiver()->associate(
$receiver[0]
);
$message->sender()->associate(
$sender[0]
);
$message->save();
});
}

your seeder can be way easier,
maybe in your user factory, you can override configure() method as below, and register an afterCreating callback as below:
public function configure(): UserFactory
{
return $this->afterCreating(Closure::fromCallable([$this, 'attachRelations']));
}
where attachRelations is a method that will be called after every user creation, you can do many things in there, things like :
function attachRelations(User $user)
{
// Assign a random amount of languages to a user
$languages = Language::all();
$user->languages()->sync($languages->random(rand(1, $languages->count())));
// Assign a fake avatar to a user, just a record no file
$avatar = Avatar::factory()->make();
$user->avatar()->save($avatar);
}
this way, I will attach a random amount of languages to each user, and also assign an avatar record, I hope you got the idea
laravel doc: Here
let me know if you need help

Related

Booking system with many to many laravel relationship

I'm trying to make an app where airbnb hosts can have a log of their bookings, I created three models: Home, Guest and Booking. Booking being the main player, I also think there should be a pivot table but I'm not sure which models should it link... I decided to go with booking_guest but I'm getting the following error when I create a booking:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'booking_id' cannot be null (SQL: insert into `booking_guest` (`booking_id`, `guest_id`) values (?, 1), (?, 2))
I do something like this in my BookingController:
public function create(Request $request)
{
$guestIds = Guest::latest()->take(2)->pluck('id');
$home = Home::findOrFail(1);
$booking = new Booking();
$booking->home_id = $home->id;
$booking->guests()->attach($guestIds);
$booking->save();
return response()->json([
'booking' => $booking,
]);
}
I'm not feeling too sure about this configuration, could you guys share some light on me.
These are my models:
class Home extends Model
{
public function guests()
{
return $this->belongsToMany('App\Models\Guest', 'guest_home', 'home_id', 'guest_id');
}
public function bookings()
{
return $this->hasMany('App\Models\Booking');
}
}
class Booking extends Model
{
public function guests()
{
return $this->belongsToMany('App\Models\Guest', 'booking_guest', 'booking_id', 'guest_id');
}
}
class Guest extends Model
{
public function bookings()
{
return $this->belongsToMany('App\Models\Booking', 'booking_guest', 'guest_id', 'booking_id');
}
}
My migrations:
//Booking guest pivot table
Schema::create('booking_guest', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('booking_id')->index();
$table->foreign('booking_id')->references('id')->on('bookings')->onDelete('cascade')->onUpdate('cascade');
$table->unsignedInteger('guest_id')->nullable()->index();
$table->foreign('guest_id')->references('id')->on('guests')->onDelete('cascade')->onUpdate('cascade');
$table->timestamps();
});
Schema::create('guests', function (Blueprint $table) {
$table->increments('id');
$table->string('fullName');
$table->text('country');
$table->timestamps();
});
Schema::create('bookings', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('home_id')->index();
$table->foreign('home_id')->references('id')->on('homes')->onDelete('cascade')->onUpdate('cascade');
$table->timestamp('entryDate')->nullable();
$table->timestamp('exitDate')->nullable();
$table->timestamps();
});
Schema::create('homes', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('host_id')->index();
$table->foreign('host_id')->references('id')->on('hosts')->onDelete('cascade')->onUpdate('cascade');
$table->string('fullAddress')->unique();
$table->integer('rooms')->unique();
$table->timestamps();
});
As you can see from here:
public function create(Request $request)
{
...
$booking = new Booking(); // <-- here
$booking->guests()->attach($guestIds); // <-- here
$booking->save(); // <-- here
...
}
you are creating a new instance of Booking, then associating to it a Guest and then saving the instance of Booking.
However ->attach(...) tries to associate the Booking with the Guest, but the Booking does not exists at that time on the DB.
I would suggest to use Booking::create, so that after that statement, the booking exists on the DB and so you can attach to it the Guest:
public function create(Request $request)
{
$guestIds = Guest::latest()->take(2)->pluck('id');
$home = Home::findOrFail(1);
$booking = Booking::create([ // <- using Booking::create
'home_id' => $home->id // <- using Booking::create
]); // <- using Booking::create
$booking->guests()->attach($guestIds);
return response()->json([
'booking' => $booking,
]);
}

Unread message feature not working in Laravel

I am trying to make an "unread" function for my messenger. When I refresh try to refresh the database it shows
There is no column with name 'read' on table 'messages'.
This error and my "unread" feature is not working. I am using SQlite.
Here's my Unread migration:-
public function up()
{
Schema::table('messages', function (Blueprint $table) {
$table->boolean('read')->after('to')->default(false);
});
}
public function down()
{
Schema::table('messages', function (Blueprint $table) {
$table->dropColumn('read');
});
}
Here my Messages migration:-
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->integer('from')->unsigned();
$table->integer('to')->unsigned();
$table->text('text');
$table->timestamps();
});
}
Here's my controller:-
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
use App\Friend;
use App\Message;
use App\Events\NewMessage;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class ContactsController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
return view('home');
}
public function get(){
$sem = Auth::user()->id;
$contacts = DB::table('friends')
->where('my_id', $sem)
->get();
// get a collection of items where sender_id is the user who sent us a message
// and messages_count is the number of unread messages we have from him
$unreadIds = Message::select(\DB::raw('`from` as sender_id, count(`from`) as messages_count'))
->where('to', auth()->id())
->where('read', false)
->groupBy('from')
->get();
// add an unread key to each contact with the count of unread messages
$contacts = $contacts->map(function($contact) use ($unreadIds) {
$contactUnread = $unreadIds->where('sender_id', $contact->friends_id)->first();
$contact->unread = $contactUnread ? $contactUnread->messages_count : 0;
return $contact;
});
return response()->json($contacts);
}
public function getMessagesFor($id)
{
$messages = Message::where('from', $id)->orWhere('to', $id)->get();
$messages = Message::where(function($q) use ($id) {
$q->where('from', auth()->id());
$q->where('to', $id);
})->orWhere(function($q) use ($id){
$q->where('from', $id);
$q->where('to', auth()->id());
})
->get();
return response()->json($messages);
}
public function send(Request $request)
{
$message = Message::create([
'from' => auth()->id(),
'to' => $request->contact_id,
'text' => $request->text
]);
broadcast(new NewMessage($message));
return response()->json($message);
}
}
This is where contacts Ids are coming from:-
public function up()
{
Schema::create('friends', function (Blueprint $table) {
$table->id();
$table->string('created_by');
$table->string('my_id');
$table->string('friends_id');
$table->string('name');
$table->timestamps();
});
}
I can't seem to figure out the problem and have been stuck with this since weeks, your help would really be appreciated.
There are a few things that may be causing you issues with your migration.
You have a couple of reserved words in SQL (thought maybe not in
SQLite) that could be causing an issue. I would remove them in favor
of something that is not potentially causing a conflict.
You might assign foreign keys to the database so it will play nice
with your models. Then you don't have to go through the whole
DB::raw('from... stuff, it is automatically assigned by the model
if you set up the relationships correctly.
I think the issue you are having though, is that you may be using the
migration a bit differently that expected. Why not put the boolean
field on the original migration? If this migration isn't run, or is
run in the wrong order, or if it is run in down() mode, that read
field will not be in the database table.
I suggest to test, start with the following migration. Make note of the different (non-conflicting) field names and the auto-increment field:
Schema::create('messages', function (Blueprint $table) {
$table->increments('id');
$table->integer('from_id')->unsigned();
$table->integer('to_id')->unsigned();
$table->boolean('has_read')->default(false);
$table->text('text');
$table->timestamps();
});
Run a fresh migration on the database with this. Rework your controller code to work with the new field names and test. If this fixes the error:
There is no column with name 'read' on table 'messages'.
Then I would recommend adding in the foreign keys to those ids like this for example:
$table->foreign('from_id')->references('id')->on('users');
But this is a whole different issue and requires re-working of your controller db draws. For now, see if the above migration will solve the original error.

How to handle user likes another user logic in Laravel?

I would like to build 'likes' functionality in my app where one user (user1) can like another user (user2), and then if user2 likes back user1 they would match. What is the best way to implement that kind of logic? My current idea is something like this.
users_table
public function up()
{
Schema::create('users', function(Blueprint $table)
{
$table->increments('id');
$table->string('email');
$table->string('first_name');
$table->string('last_name');
$table->string('password', 60);
$table->rememberToken()->nullable();
$table->timestamps();
});
}
likes_users_table
public function up()
{
Schema::create('likes_users', function(Blueprint $table)
{
$table->integer('liked_user_id')->unsigned();
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->foreign('liked_user_id')->references('id')->on('users');
$table->primary(array('user_id', 'liked_user_id'));
});
}
User.php
public function likes()
{
return $this->belongsToMany('User', 'likes_users', 'user_id', 'liked_user_id');
}
UserController.php
public function getIndex()
{
$not_friends = User::where('id', '!=', Auth::user()->id);
if (Auth::user()->likes->count()) {
$not_friends->whereNotIn('id', Auth::user()->likes->modelKeys());
}
$not_friends = $not_friends->get();
return View::make('dashboard.index')->with('not_friends', $not_friends);
}
public function add(User $user)
{
$user->likes()->attach($user->id);
}
public function store($id)
{
$user = User::find($id);
Auth::user()->add($user);
return Redirect::back();
}
This is a quick solution. There could be a more efficient way to accomplish this. Suggestions welcome.
My idea is to set a is_mutual flag for user likes. If the users like each other the is_mutual flag will be set.
Let's start with the migration.
Create user_likes table.
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('user_likes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id')->comment('user who liked this');
$table->unsignedBigInteger('liked_user_id')->comment('user whom this liked');
$table->boolean('is_mutual')->default(false)->comment('is users like each other');
$table->timestamps();
$table->softDeletes();
$table->foreign('user_id')->references('id')->on('users');
$table->foreign('liked_user_id')->references('id')->on('users');
});
}
Then, I have updated my User model to add the following relations
public function likedUsers()
{
return $this->belongsToMany('App\User', 'user_likes', 'user_id', 'liked_user_id');
}
public function mutualFriends()
{
return $this->belongsToMany('App\User', 'user_likes', 'user_id', 'liked_user_id')
->where('is_mutual',1);
}
Now Let's create a controller to handle the User Likes logic.
public function storeUserLikes(Request $request)
{
$user = Auth::user(); // Take the currently authenticated user
// Ofcourse, you should validate the $request
$likedUser = $request->likedUser;
// Let's findout if the other user already likes the current user
$isLikedAlready = UserLike::where([
'user_id' => $likedUser,
'liked_user_id' => $user->id,
])
->first();
// Store the user like record in the db
UserLike::create([
'user_id' => $user->id,
'liked_user_id' => $likedUser,
'is_mutual' => $isLikedAlready ? true : false
]);
// If the other user already likes the current user,
// they are a mutual connection.
// Update the row accordingly
if ($isLikedAlready) {
$isLikedAlready->is_mutual = true;
$isLikedAlready->save();
}
// Done! Now show a proper response to the User.
// I am leaving it to you :-)
}
Now, let's add the routes
Auth::routes();
Route::group(['prefix' => 'user', 'middleware' => 'auth'], function ($router) {
$router->get('/like-user/{likedUser}', ['uses' => 'UserLikesController#storeUserLikes']);
});
Now, add some users to your database (for testing). Refer Laravel database seeding and factory for more details.
How it works
The logged-in user can go to DOMAIN/user/like-user/x to like a user.
Where x is the id of the User to be liked.
Note: I have added a get route for ease. You can use POST/GET methods of your choice.
Now, Let's find a list of mutual friends from the DB
Add a function to the UserLikes controller.
/**
* This function will return a JSON Response with mutually liked users
* for the current logged in user.
**/
public function listLikedUsers()
{
$user = Auth::user();
return response()->json([
'status' => true,
'data' => $user->mutualFriends()->get()
]);
}
Now add a route for getting the mutually liked users. Just below the current route add the following GET route
$router->get('/likes', ['uses' => 'UserLikesController#listLikedUsers']);
You can have a pivot boolean column (is_liked_back) in likes_users table. In this way you will only have one entry for likes getting from either side.

Laravel FollowController relationship error

I am making FollowController where I have two tables following_users table and following_user_item table. which is in hasMany relationship. When a authenticate current_user wants to follow an user, the ID of the user will stored in following_users table and its relational table stored the current_user_id and the following_user_id (which is the id of following_users table). here is the schema.
following_users_table:
public function up()
{
Schema::create('following_users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('user_id')->unsigned();
$table->foreign('user_id')
->references('id')
->on('users');
$table->timestamps();
});
}
following_user_item_table:
public function up()
{
Schema::create('following_user_items', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('following_users_id')->unsigned();
$table->foreign('following_users_id')
->references('id')
->on('following_users');
$table->bigInteger('user_id')->unsigned();
$table->timestamps();
});
}
I have done the FollowController but the problem is comming when try to check whether the user is already followed or not.
Follow Relationship in User model
public function followingUserList()
{
return $this->hasOne('App\FollowingUser');
}
/**
* Get the stories associated with the user through an intermediate table
*
*/
public function followingUsers()
{
return $this->hasManyThrough(
'App\FollowingUserItem',
'App\FollowingUser',
null,
'following_users_id'
);
}
FollowingUser Model Relationship with User and FollowingUserItem
public function user()
{
return $this->belongsTo('App\User');
}
public function users()
{
return $this->hasMany('App\FollowingUserItem','following_users_id');
}
Here is my FollowController:
class FollowController extends Controller
{
//
public function index($id)
{
$user = User::find($id);
$logged_userId = Auth::User();
if ($user->id == $logged_userId->id) {
return [
'status' => false,
'message' => 'You can not Follow yourself',
];
}
if ($user && $logged_userId) {
$checkUsers = FollowingUser::where('user_id', $user->id)->get()->users()->where('user_id', $logged_userId->id);
if ($checkUsers)
{
return 'already followed';
}
else
{
$user->followingUserList()->save(new FollowingUser())->users()->save(new FollowingUserItem(['user_id' => $logged_userId->id]));
return 'sucess';
}
}
}
}
I go the error
Method Illuminate\Database\Eloquent\Collection::users does not exist.
When you call get() Laravel returns a collection as it does not know how many rows there will be there. This is why you get collection does not have users set error. Since you filter on an id you know there is only gonna be one, therefor you can utilize the first() method.
So change the code to use first().
$checkUsers = FollowingUser::where('user_id', $user->id)->first()->users()->where('user_id', $logged_userId->id);

How do I show user login history?

I am a beginner in Laravel. I use in my project Laravel 5.8.
I have this schema:
Schema::create('user_login_histories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->dateTime('date_time');
$table->ipAddress('ip');
$table->engine = "InnoDB";
$table->charset = 'utf8mb4';
$table->collation = 'utf8mb4_unicode_ci';
});
and my Model:
class UserLoginHistory extends Model
{
protected $quarded = ['id'];
public $timestamps = false;
protected $fillable = ['user_id', 'date_time', 'ip'];
public function user()
{
return $this->belongsTo('App\User');
}
// Show User login history
public function scopeHistory()
{
//return $this->hasMany('App\UserLoginHistory');
return $this->hasMany('App\UserLoginHistory', 'user_id', 'id');
}
}
I want to show my user history by this function:
public function getLoginAdminHistory(int $idAdmin)
{
return UserLoginHistory::history()->orderBy('id', 'desc')->paginate(25);
}
but this is not working.
How can I fix this?
The way you are using scope here is wrong. It is not meant to be used like that and will not help you with this issue, so I am not gonna go into details with that.
I am going to assume that you want to store more than one UserLoginHistory per User, so what you need is a HasMany relationship on the User class.
So to make this work, you need something like:
in User.php
public function userLoginHistory(): HasMany
{
return $this->hasMany(UserLoginHistory::class);
}
Now you should be able to do:
$user->userLoginHistory, which will return a collection of UserLoginHistory for you.
public function getLoginAdminHistory(int $idAdmin)
{
$user = User::findOrFail($idAdmin);
return $user->userLoginHistory()->orderBy('id', 'desc')->paginate(25);
}

Categories