0

I am trying to implement user tracking in Laravel by writing statistics after user login. I.e, location, timezone and such. I sort of had a half luck of implementing it.

I used ajax by attaching event listener to submit button on login form. After the form is submitted, ajax method is called. This method gets user's stats by using an api call. Then that ajax request sends that data to StatsController@store which then records that entry to stats table.

This only achieved by using e.preventDefault();, if i don't use it, records are not inserted into db and it's throws an error which quickly vanishes after redirecting to dashboard.

Ajax method resides in js file which is being called in layout file.

Here is a ajax method:

function getCoords(){

    return fetch('https://ipinfo.io/geo')
    .then((response) => {
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        else{

            return response.json();

        }

    })
    .then((response) => {

        let url = "/writeStats";
        let token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
        let forma = document.getElementById("loginForm");

        let formElements = {};

        formElements.email = forma.elements[1].value;
        formElements.password = forma.elements[2].value;

        $.ajax({
            url: url,
            type: 'POST',
            data: {_token: token , message: "bravo", stats: response, formElements: formElements},
            dataType: 'JSON',
            success: (response) => { 
                console.log("success");
                console.log(response);
            },
            error: (response) => {
                console.log("error");
                console.log(response);
            }
        }); 

    })
    .catch((error) => {
        console.error('There has been a problem with your fetch operation:', error);
    });

}

window.addEventListener("click", (e) => {

    if(e.target.id==="loginBtn"){
        //e.preventDefault();
        getCoords();
    }

});

web.php:

Route::post('/writeStats','StatsController@store');

StatsController:

public function store(Request $request)
{
    if($request->ajax()){

        $email = $request->formElements["email"];
        $password = $request->formElements["password"];

        $user = DB::table('users')->where("email", "=", $email)->first();

        if(Hash::check($password, $user->password)) {   

            $stat = new Stats;
            $stat->ip = $request->stats["ip"];
            $stat->city = $request->stats["city"];
            $stat->region = $request->stats["region"];
            $stat->country = $request->stats["country"];
            $stat->coords = $request->stats["loc"];
            $stat->timezone = $request->stats["timezone"];
            $stat->user_id = $user->id;
            $stat->save();

            $response = array(
                "message" => "bravo",
                "request" => $request->all(),
                "stats" => $request->stats,
                "user" => $user,
                "stat" => $stat,
            );

            return response()->json($response);

        }

    }

}

php artisan --version shows Laravel Framework 6.9.0

My question is: How to insert into stats table just after user's logging?

Edit1:

I am now trying somewhat different approach. Instead of some custom method from StatsController, i am now using authenticated method in LoginController.

public function authenticated(Request $request)
    {
        $credentials = $request->only('email', 'password');

        $email = $request->input("email");
        $password = $request->input("password");

        $user = DB::table('users')->where("email", "=", $email)->first();
        if (Auth::attempt($credentials)) {

            $stat = new Stats;
            $stat->ip = $request->stats["ip"];
            $stat->city = $request->stats["city"];
            $stat->region = $request->stats["region"];
            $stat->country = $request->stats["country"];
            $stat->coords = $request->stats["loc"];
            $stat->timezone = $request->stats["timezone"];
            $stat->user_id = auth()->user()->id;
            $stat->save();

            $response = array(
                "stat" => $request->all(),
            );

            return redirect()->intended('dashboard');

        }
    }

and in web.php:

Route::post('/login','Auth\LoginController@authenticated');

Still no luck. I just don't know what approach to use. It feels so close, like i missing something small.

Edit2:

Let me rephrase question:

How to send api call response to login controller, so that after user is authenticated, i can insert api response data into another table(not users table)? Can i:

  1. Somehow insert api response into authenticate request?
  2. Or, how to authenticate while simultaneously writing records into table stats?

I tried both approaches(see: Original and Edits), but not sure what should i write...

2 Answers2

0

Thats its intended behaviour. Since in your usecase the form submission is dependent on async operations, you WANT this behaviour.

https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault

The Event interface's preventDefault() method tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.

UPDATE: Ref: using a fetch inside another fetch in javascript for nested async operations.

Angad Dubey
  • 5,067
  • 7
  • 30
  • 51
0

I found a solution which works perfectly. To explain better, i had couple of errors:

First one is dataType in ajax call where is expected to be returned json response. There is no need for any response since upon login and subsequent insert of data from api, user is sent to /dashboard.

Second one is my use of e.preventDefault(); since i wanted to see what will i get in case of submission. There is no need for that, explanation-last sentence in previous paragraph.

And btw, return is html code, since user is redirected to view...

Here is revised code:

LoginController:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

use Illuminate\Http\Request;
use Auth;
use App\Stats;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/dashboard';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    public function authenticated(Request $request)
    {

        $email = $request->formElements["email"];
        $password = $request->formElements["password"];

        if (Auth::attempt(['email' => $email,'password' => $password])) {

            $stat = new Stats;
            $stat->ip = $request->stats["ip"];
            $stat->city = $request->stats["city"];
            $stat->region = $request->stats["region"];
            $stat->country = $request->stats["country"];
            $stat->coords = $request->stats["loc"];
            $stat->timezone = $request->stats["timezone"];
            $stat->user_id = auth()->user()->id;
            $stat->save();

            $response = array(
                "stat" => $request->all(),
            );

            return redirect()->intended('dashboard');
        }
    }
}

Code for ajax method and event listeners inside a script called in layout file:

function getCoords(){

    return fetch('https://ipinfo.io/geo')
    .then((response) => {
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        else{

            return response.json();

        }

    })
    .then((response) => {

        let url = "/login";
        let token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
        let forma = document.getElementById("loginForm");

        let formElements = {};

        formElements.email = forma.elements[1].value;
        formElements.password = forma.elements[2].value;
        console.log(formElements);
        $.ajax({
            url: url,
            type: 'POST',
            data: {_token: token , message: "bravo", stats: response, formElements: formElements},
            dataType: 'html',
            success: (response) => { 
                console.log("success");
                console.log(response);
                forma.submit();

            },
            error: (response) => {
                console.log("error");
                console.log(response);
            }
        }); 

    })
    .catch((error) => {
        console.error('There has been a problem with your fetch operation:', error);
    });

}

window.addEventListener("click", (e) => {

    if(e.target.id==="loginBtn"){
        getCoords();     
    }

});

window.addEventListener("keypress", (e) => {

    let forma = document.getElementById("loginForm");
    let isFocused = (document.activeElement === forma.elements[2]);

    if(forma.elements[1].value && forma.elements[2].value && e.key === 'Enter' && isFocused){

        getCoords();

    }

});

So there it is, to "whomever may be concerned", a solution if you want to add ajax call during authentication in Laravel. Probably can be used for something else also ... like custom authentication or something.