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.
Related
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
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,
]);
}
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);
I have a blog built with laravel.
and I want to add likes to my posts.
so I created a Like model with a likes table.
this is what i have done in my Like model
public function post(){
return $this->belongsTo(Post::class);
}
public function user(){
return $this->belongsTo(User::class);
}
in my Post and User models
public function likes(){
return $this->hasMany(Like::class);
}
and my migration file for likes table
Schema::create('likes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->foreign('post_id')->references('id')->on('posts');
$table->foreign('user_id')->references('id')->on('users');
$table->boolean('value');
$table->timestamps();
});
I want to set the values in my controller on this way!
public function liker($postID, $userID, $value){
$like = new Like([
'post_id' => $postID,
'user_id' => $userID,
'value' => $value
]);
$like->save();
return redirect()->back();
}
but the view return 419 error page. (Page Expired)
and also no changes (no row) adds to my database(likes table)!
can you help me?
you dont need value on a like, if it exists, it's a "like" and you should use is as a pivot table (you already have 2 foreign IDs in it)
Schema::create('likes', function (Blueprint $table) {
$table->unsignedInteger('post_id');
$table->unsignedInteger('user_id');
$table->foreign('post_id')->references('id')->on('posts');
$table->foreign('user_id')->references('id')->on('users');
$table->tinyInteger('is_dislike')->default(0);
$table->timestamps();
});
then declare the relation between Post and User
Post
public function votedUsers(){ //or simply likes
return $this->belongsToMany(User::class, 'likes')->withPivot('is_dislike')->withTimestamps();
}
User
public function votedPosts(){
return $this->belongsToMany(Post::class, 'likes')->withPivot('is_dislike')->withTimestamps();
}
Next to create a like just do it like this
public function liker($postId, $userId, $value){
$user = User::findOrFail($userId); //or use auth()->user(); if it's the authenticated user
$user->votedPosts()->attach($postId);
return redirect()->back();
}
to Remove a like use detach($postId) instead.
For dislike
$user->votedPosts()->attach($postId, ['is_dislike' => 1]);
I currently learn Laravel 5.8 and created database where users can have two roles: admin or user stored in roles table. I tried to populate role_users intermediate table but Laravel doubles rows and seeds wrong data like this: Crazy ROLE_USER Table. role_id should be only 1 or 2. user_id should be unique. What did I do wrong?
User model:
public function roles() {
return $this->belongsToMany('App\Role', 'role_users')->withTimestamps();
}
Role model:
public function roles() {
return $this->belongsToMany('App\User', 'role_users')->withTimestamps();
}
create_role_users_table migration:
Schema::create('role_users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('role_id');
$table->unsignedBigInteger('user_id');
$table->timestamps();
});
RoleUsersFactory factory:
$factory->define(RoleUser::class, function (Faker $faker) {
return [
'role_id' => Role::all()->random()->id,
'user_id' => User::all()->random()->id,];
});
RoleTableSeeder seeder:
$role_user = new Role();
$role_user->role = 'user';
$role_user->save();
$role_admin = new Role();
$role_admin->role = 'admin';
$role_admin->save();
DatabaseSeeder seeder:
$this->call(RoleTableSeeder::class);
factory(User::class, 5)->create()->each(function($user) {
$user->roles()->save(factory(RoleUser::class)->make());
});
create a role then.
$role = Role::create(['role_name' => 'admin']);
use attach()
factory(User::class, 100)->create()->each(function ($user) use ($role ) {
$role ->users()->attach($user);
});
Try the above code.
This will create 100 different users with admin as role.
you can add other roles like this.
hope this suite for you.
You have the wrong relationships (you state that you want all the users to have a single role, which should be a BelongsTo / HasMany).
On the User model, have this method:
public function role() {
return $this->belongsTo('App\Role');
}
Create this method on the Role model:
public function users() {
return $this->hasMany('App\User');
}
Delete the migration you made where the role_users table is created and create a new migration with the following code:
Schema::table('users', function(Blueprint $table) {
// remove the nullable if a user HAS to have a role at all times
$table->unsignedBigInteger('role_id')->nullable();
}
Now, as a last step, the seeders. Your Role-seeder is correct. You shouldn't change that. Change your User-seeder to the following:
factory(User::class, 100)->create(['role_id' => Role::all()->random()->id]);
This will create a database with 100 users, where each has a role of admin or normal user. You can get all the users attached to a role with $role->users and get the role of a user with $user->role.