{blog}


  • Time String Helper

    Time String Helper

    Formatting Time in Flutter/Dart: A Simple Helper Class

    When working on Flutter projects, you might need to format durations into readable time strings. This could be for timers, media players, or activity trackers. To make this task a bit easier, here’s a lightweight helper class, TimeStringHelper, that handles common formatting needs.

    What TimeStringHelper Does

    Converts milliseconds into readable time strings.

    Formats durations into HH:mm:ss or mm:ss formats.

    Provides methods to extract minutes and seconds from a total duration in seconds.

    The Code

    
    class TimeStringHelper {
      static String timeStringFromMilliSeconds(int milliseconds) {
        return timeStringFromSeconds((milliseconds / 1000).floor());
      }
    
      static String timeStringFromSeconds(int secondsDuration) {
        int hours = secondsDuration ~/ 3600;
        int minutes = (secondsDuration % 3600) ~/ 60;
        int seconds = secondsDuration % 60;
    
        var hoursStr = hours.toString();
        var minutesStr = minutes.toString().padLeft(2, '0');
        var secondsStr = seconds.toString().padLeft(2, '0');
    
        return hours > 0 ? "$hoursStr:$minutesStr:$secondsStr" : "$minutesStr:$secondsStr";
      }
    
      static int minutesFromTotalSeconds(int secondsDuration) {
        return secondsDuration ~/ 60;
      }
    
      static int secondsFromTotalSeconds(int secondsDuration) {
        return secondsDuration % 60;
      }
    }
    
    Dart

    How It Works

    Here’s a quick breakdown of how the methods in the class work:

    1. timeStringFromMilliSeconds

    Converts milliseconds to seconds and formats the result.

    
    print(TimeStringHelper.timeStringFromMilliSeconds(3661000)); 
    // Output: "1:01:01"
    
    Dart
    1. timeStringFromSeconds

    Formats seconds into a readable string. If the duration is an hour or longer, it includes the hours.

    
    print(TimeStringHelper.timeStringFromSeconds(3661)); 
    // Output: "1:01:01"
    
    print(TimeStringHelper.timeStringFromSeconds(61)); 
    // Output: "01:01"
    
    Dart
    1. minutesFromTotalSeconds
      This one returns an int. It’s useful if you want to create a duration time picker and need to set it to an initial value. So in my case I have a duration in total seconds and I want to get the minutes value from it.

    Calculates the total minutes from the duration.

    
    print(TimeStringHelper.minutesFromTotalSeconds(3661)); 
    // Output: 61
    
    Dart
    1. secondsFromTotalSeconds
      This is the same as the last function, just for the remaining seconds.

    Extracts the remaining seconds after minutes are calculated.

    
    print(TimeStringHelper.secondsFromTotalSeconds(3661)); 
    // Output: 1
    
    Dart

    Why Use This?

    This class keeps time formatting simple and reusable. It’s not overly complex, but it can save time when working with durations in different parts of your app.


  • Use search terms in eloquent queries for Laravel

    Use search terms in eloquent queries for Laravel

    I needed to make an Eloquent query that could take search terms. The search terms are optional – no search term and the whole dataset is returned. In our particular data model the Users have one or many Associations.

    So the search term should check for user name, phone, email and association name. In the sub queries you simply foreach through the search terms and check if there is a match in any column.

    After the when-clause you can add any where-clauses that applies to every query, no matter the result of the search. In this case we only wanted users that are admins.

    $search = $request->get('search');
    $searchTerms = null;
    // Split search string at spaces if there is one
    if ($search) {
       $searchTerms = explode(' ', $search);
    }
    $users = User::orderBy($'created_at', 'DESC')->
              with('associations')->
              when($searchTerms, function ($q) use ($searchTerms) {
                  foreach ($searchTerms as $searchTerm) {
                      $q->orWhereHas('associations', function ($qa) use ($searchTerm) {
                          $qa->where('name', 'LIKE', "%{$searchTerm}%");
                      });
                      $q->orWhere('first_name', 'LIKE', "%{$searchTerm}%");
                      $q->orWhere('last_name', 'LIKE', "%{$searchTerm}%");
                      $q->orWhere('phone', 'LIKE', "%{$searchTerm}%");
                      $q->orWhere('email', 'LIKE', "%{$searchTerm}%");
                  }
              })->
              where('is_admin', 1)->get();
    PHP

  • Impersonate users with Sanctum in Laravel

    Impersonate users with Sanctum in Laravel

    I needed to write a somewhat clean solution to let an admin impersonate other users. Which basically means that one user can appear as another user – without having to get access that users credentials to log in.

    The most obvious use case for this would be when an admin needs access to a user’s account. The app I did this for relies heavily on user created data, so this is very useful when customer service needs to look into any problems the users may have. 

    Earlier we were using Tymon JWT for authentication and it was quite easy to implement impersonation it with Rickycezar/laravel-jwt-impersonate

    But after migrating to Sanctum for authentication I needed to come up with something else. Here’s my solution (I posted this first in a stackoverflow thread).

    How it works

    In front end admins can view a list of all users, where they can klick a button to triggers the impersonate endpoint for any user. After doing this the admin will appear to the system as the impersonated user.

    When getting the response from the endpoint, front end sets a switch to keep track of that the current user is an impersonation – and to shows a button to leave impersonation. In essence what happens in front end is just that the access token is replaced when starting and ending an impersonation. 

    In backend what what happens is:
    Impersonate
    1. Create a new access token for the impersonated user
    2. Save a connection between this impersonation and the admin user 
    3. Delete the admin’s access token
    4. Send the new access token back

    Leave impersonation
    1. Create a new access token for the admin connected to the impersonation token
    2. Delete the impersonation token
    3. Send the new access token back

    It’s quite simple. So here we go: 

    1. Migration

    First we write a migration that will create a new table called impersonations. This table will store the connection between a personal access token and the impersonating user.

    public function up()
    {
        Schema::create('impersonations', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('personal_access_token_id')->unsigned();
            $table->bigInteger('user_id')->unsigned();
            $table->timestamps();
    
            $table->foreign('personal_access_token_id')
            ->references('id')
            ->on('personal_access_tokens')->cascadeOnDelete();
    
            $table->foreign('user_id')
            ->references('id')->on('users')->cascadeOnDelete();
        });
    }

    2. User model

    Add three function to your USER model. These functions are used to determine if a user can impersonate others, can be impersonated by others and if the user is currently impersonating. You can make up your own rules for who can impersonate or get impersonated – just add whatever logic you want to the canImpersonate and canBeImpersonated functions.

    I chose to only let admins impersonate and only non admins to be impersonated.

    public function canImpersonate()
    {
        return $this->is_admin;
    }
    
    
    public function canBeImpersonated()
    {
        return !$this->is_admin;
    }
    
    
    public function isImpersonated() {
        $token = $this->currentAccessToken();
        return $token->name == 'IMPERSONATION token';
    }

    3. Impersonation Controller Functions

    Add two functions. One to start impersonation (take the persona of another user), and one function to leave impersonation (go back to your own user).

    In my case i have an AdminController. But you can put these functions wherever it makes sense to you. Maybe you want a dedicated ImpersonationController.

    In these function there are two users – the impersonator and the persona. The impersonator is the admin who wants to access the system as another user, and the persona is the user that’s being impersonated.

    Also you can modify the responses to however you prefer it for your frontend app. You most likely want to keep track of whether the user is actually impersonating another user, so you can add button for the admin to go back to its own account. I used the same structure as Rickycezar/laravel-jwt-impersonate so the we didn’t have to make any changes in front end from our old solution.

    // START IMPERSONATION
    public function impersonate($userId)
    {
        $impersonator = auth()->user();
        $persona = User::find($userId);
    
        // Check if persona user exists, can be impersonated and if the impersonator has the right to do so.
        if (!$persona || !$persona->canBeImpersonated() || !$impersonator->canImpersonate()) {
            return false;
        }
    
        // Create new token for persona
        $personaToken = $persona->createToken('IMPERSONATION token');
    
        // Save impersonator and persona token references
        $impersonation = new Impersonation();
        $impersonation->user_id = $impersonator->id;
        $impersonation->personal_access_token_id = $personaToken->accessToken->id;
        $impersonation->save();
    
        // Log out impersonator
        $impersonator->currentAccessToken()->delete();
    
        $response = [
            "requested_id" => $userId,
            "persona" => $persona,
            "impersonator" => $impersonator,
            "token" => $personaToken->plainTextToken
        ];
    
        return response()->json(['data' => $response], 200);
    }
    
    
    // LEAVE IMPERSONATION
    public function leaveImpersonate()
    {
        // Get impersonated user
        $impersonatedUser = auth()->user();
    
        // Find the impersonating user
        $currentAccessToken = $impersonatedUser->currentAccessToken();
        $impersonation = Impersonation::where('personal_access_token_id', $currentAccessToken->id)->first();
        $impersonator = User::find($impersonation->user_id);
        $impersonatorToken = $impersonator->createToken('API token')->plainTextToken;
    
        // Logout impersonated user
        $impersonatedUser->currentAccessToken()->delete();
    
        $response = [
            "requested_id" => $impersonator->id,
            "persona" => $impersonator,
            "token" => $impersonatorToken,
        ];
    
        return response()->json(['data' => $response], 200);
    }

    4. Routes

    Last and least, the routes. Remember that if you’re using a middleware to protect the impersonate route, so only admins can access it, you need to put the leave impersonation route outside that middleware. Since the persona taken by the admin most likely wont be a admin

    // Impersonate
    $api->get('/impersonate/take/{userId}', [AdminController::class, 'impersonate'])->name('users.impersonate');
    
    // Leave impersonation
     $api->get('/impersonate/leave', [AdminController::class, 'leaveImpersonate'])->name('users.leaveImpersonate');

  • Round integer to 10,100, 1000 etc.

    Round integer to 10,100, 1000 etc.

    If you need to shave off the last few digits of a big int, you can use this simple solution. Came in handy for me when working with milliseconds in timers. Basically you cast the integer to a double and divide it by the number of 0’s you want at the end. Then round, ceil or floor it and turn it back into an int.

    For type safety you might have to do some more meticulous work, depending on the language you’re working in. Here’s an example in Dart.

    var integerToRound = 31555;
    var roundedInteger = (integerToRound / 10).floor() * 10;
    
    print(roundedInteger);
    
    //output: 31560
    Dart

    Change the divider and multiplier to the number of zeros you need.

    var integerToRound = 31555;
    var roundedInteger = (integerToRound / 100).floor() * 100;
    
    print(roundedInteger);
    
    //output: 31600
    Dart


  • Calculate price excluding VAT

    Calculate price excluding VAT

    Sometimes you need to calculate the price of a product excluding VAT, and the only details you have is the amount including vat and the vat percent. This might be a bit tricky in some applications when there are mixed VAT percentages.

    For example, you paid 1000 space credits and in that sum there is a 12% VAT included. If you need to find out how much of the 1000 is actual VAT you can use this simple function:

    function getAmountWithoutVat(amountIncludingVat, vatPercent) {
    
      var amountExVat = amountIncludingVat / (100 + vatPercent) * 100;
      var sumVat = amountIncludingVat - amountExVat;
      var amounts = {
        "priceExVat": amountExVat.toFixed(2),
        "vat": sumVat.toFixed(2)
      };
    
      return amounts;
    }
    
    console.log(getAmountWithoutVat(1000, 12));
    JavaScript

    Output:

    {
      priceExVat: "892.86",
      vat: "107.14"
    }
    JavaScript


  • Convert short color hex to pair

    Convert short color hex to pair

    If you have a three digit hex for a color. For example #FFF and want to convert it to a 6 digit number here’s a simple way:

    // Expects 3 digit hex, like '#FFF';
    function uniformColorHex(hex) {
    
      let newHex = "#" +
        hex.charAt(1) +
        hex.charAt(1) +
        hex.charAt(2) +
        hex.charAt(2) +
        hex.charAt(3) +
        hex.charAt(3);
    
      console.log(hex + " => " + newHex);
      return newHex;
    }
    JavaScript

  • Validate date in format YYYY-MM-DD

    Validate date in format YYYY-MM-DD

    A simple JS function to validate that a date string in the format YYYY-MM-DD is a valid date. Will validate that the day is correct for the given month, including leap years

    /**
    * Validate that a date string in the format YYYY-MM-DD is a valid date
    * @param dateString (YYYY-MM-DD)
    * @returns {boolean}
    */
    function isValidDate(dateString) {
    
    // Date format: YYYY-MM-DD
    var datePattern = /^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/;
    
    // Check if the date string format is a match
    var matchArray = dateString.match(datePattern);
    if (matchArray == null) {
    return false;
    }
    
    // Remove any non digit characters
    var cleanDateString = dateString.replace(/\D/g, '');
    
    // Parse integer values from date string
    var year = parseInt(cleanDateString.substr(0, 4));
    var month = parseInt(cleanDateString.substr(4, 2));
    var day = parseInt(cleanDateString.substr(6, 2));
    
    // Define number of days per month
    var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    
    // Adjust for leap years
    if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {
    daysInMonth[1] = 29;
    }
    
    // check month and day range
    if (month < 1 || month > 12 || day < 1 || day > daysInMonth[month - 1]) {
    return false;
    }
    
    // You made it through!
    return true;
    }
    JavaScript

  • Validate Swedish personnummer and organisationsnummer

    Validate Swedish personnummer and organisationsnummer

    JavaScript functions for validating Swedish personal identity numbers (personnummer), and organisation numbers (organisationsnummer). The functions for personal identity number will also validate co-ordination number (samordningsnummer).

    /**
     * Validate a 10 digit swedish personnummer
     * @param pnr
     * @returns {boolean|boolean}
     */
    function validatePersonnummer(pnr) {
        let personummer = cleanDigitString(pnr);
        if (personummer.length > 10) {
            return false;
        }
    
        return isValidLuhn(personummer) && isPersonnummerDate(personummer);
    }
    
    function validateOrganisationNumber(orgnr) {
        let orgnumber = cleanDigitString(orgnr);
    
        if (orgnumber.length < 10 || orgnumber.length > 12 || orgnumber.length === 11) {
            console.log(orgnumber.length);
        }
    
        return isValidLuhn(orgnumber);
    }
    
    /**
     * Remove any non digit characters
     * @param digitString
     * @returns {*}
     */
    function cleanDigitString(digitString) {
        return digitString.replace(/\D/g, '');
    }
    
    /**
     * Check if date is valid for personnummer
     * @param pnr
     * @returns {boolean}
     */
    function isPersonnummerDate(pnr) {
        let year = parseInt(pnr.substring(0, 2));
        let month = parseInt(pnr.substring(2, 4));
        let day = parseInt(pnr.substring(4, 6));
    
        // Check year and month values
        if (year < 0 || year > 99 || month < 0 || month > 12) {
            return false;
        }
    
        let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    
        // Adjust for leap years
        if (year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0)) {
            daysInMonth[1] = 29;
        }
    
        // Check that day is within range
        let dayIsValid = day > 0 && day <= daysInMonth[month - 1];
    
        // If day is outside range, check if it's +60 for samordningsnummer
        if (!dayIsValid) {
            dayIsValid = day > 60 && day <= daysInMonth[month - 1] + 60;
        }
    
        return dayIsValid;
    }
    
    /**
     * Check if last digit of number is vald Luhn control digit
     * @param pnr
     * @returns {boolean}
     */
    function isValidLuhn(pnr) {
        let number;
        let checksum = 0;
        for (let i = pnr.length - 1; i >= 0; i--) {
            number = parseInt(pnr.charAt(i));
            if (i % 2 === 1) {
                checksum += number;
            } else {
                checksum += (number * 2) > 9 ? (number * 2) - 9 : number * 2;
            }
        }
    
        return checksum % 10 === 0;
    }
    JavaScript

  • Laravel with SSL through Cloudflare on Heroku.

    Laravel with SSL through Cloudflare on Heroku.

    I deployed a Laravel app on Heroku, using Cloudflare for SSL. As a quick note, here’s how I did it.

    1. Deploy the app on Heroku and make sure everything works fine using the heroku app url.
    2. Add the domain names to your app in Heroku (in the settings tab for the app). Make sure you add both the root domain and www if you’re using it (example.com, www.example.com). Don’t activate SSL in Heroku.
    3. Add the site to your Cloudflare account (choose the free plan, when asked).
    4. Point your domain to Cloudflare by changing the name servers  (at the registrars control panel) to the ones Cloudflare gives you when adding the site.
    5. Wait for the name server changes to go through. It will be notified under the Overview tab on Cloudflare. When this is done you will administer the domain records on Cloudflare instead of your domain regristrar.
    6. Remove all the DNS records you don’t need, under the DNS tab in Cloudflare.  For the next step to work you need to remove the A records for the root domain – since you won’t point it to an IP address, but a domain on Heroku.
    7. Point Cloudflare to your Heroku app by adding cname records pointing to the Heroku app url.
      Like this.

      Type: CNAME
      Name: jymden.com
      Domain name: myapp.herokuapp.com


      Type: CNAME
      Name: www
      Domain name: myapp.herokuapp.com


    8. In Cloudflare, go to the Crypto tab. Set SSL to Full:
    9. Make sure your Universal SSL certificate is activated. This will happen automatically a little while after adding the site to Cloudflare (up to 24 hours, but usually faster).  When it’s activated you’ll see it a bit down in the Crypto tab, like this:
    10. Prepare your Laravel app to use https by adding this to the boot function AppServiceProvider.php (App/Providers):
    
    public function boot(UrlGenerator $url)
    {
      if (env('APP_ENV') !== 'local') {
        $url->forceSchema('https');
      }
    }
    
    PHP

    NOTE: if you’re using Laravel 5.4 or higher it’s forceScheme instead of forceSchema

    Also, you need to set your Laravel environment variable APP_ENV to production (or at least something else than local). Do this in the Heroku app settings tab.

  • Now try to enter your site with https. It might take a while for it to kick in.
  • When you see that https is working correctly, go in to the Page Rules tab in Cloudflare. Click Create Page Rule and add the rule to always use https for the domain. Use wildcards to cover all urls. Like this:
  • Drink coffee.

  • Use include method in IE

    Use include method in IE

    Here’s a hack for using the includes method also in the horrible Internet Explorer.

    if (!String.prototype.includes) {
      String.prototype.includes = function() {
        "use strict";
        return String.prototype.indexOf.apply(this, arguments) !== -1;
      };
    }
    JavaScript