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 Answers 2

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.

Sign up to request clarification or add additional context in comments.

8 Comments

Yes, i know that. What i don't know is how to submit data derived from api first, then to proceed with authentication and subsequent redirection. How to achieve that?
Authentication is native Laravel authentication, it is not from ajax call. You can see that in controller method. So i don't need to chain promises. I need to "inject" response from api to native Laravel authentication request.
So you want the native form submission but make an async call before that event?
In short, yes. i want to check(native) authentication and concurrently take api call response and insert it into table 'stats'.
have you tried submitting the form after your async stuff is complete ? form.submit() - you will still need to use preventDefault()
|
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.