Friday, January 26, 2024

Handling date and time with carbon

In PHP and Laravel, date and time can be managed using Carbon. Default PHP uses the Date object, which does not have as many flexibility as Carbon object. To start using Carbon on Laravel, add at top, along with other "use" statements. 

use Carbon\Carbon

Here are examples of its usage. Declare current date and time

$currentDateTime = Carbon::now();

The current value can be printed with 

print_r($currentDateTime);


Format to user specific output.

$now = Carbon::now()->format('d-m-Y'); // 1-1-2024

$now->toDateString(); // 2024-01-01

$now->toFormattedDateString(); // Jan 1, 2024

$now->toTimeString(); // 00:00:00

$now->toDateTimeString(); // 2024-01-01 00:00:00

$now->toDayDateTimeString(); // Mon, Jan 1, 2024 12:00 AM

$now->toCookieString(); // Monday, 01-Jan-2024 00:00:00 UTC

$now->toIso8601String(); // 2024-01-01T00:00:00+00:00


Other ways of creating a Carbon object

Carbon::parse('2023-03-10'); // Carbon instance for 2023-01-01 00:00:00

Carbon::parse('Monday of this week'); // Monday of this week

Carbon::parse('first day of January 2024'); // first day of January 2024

Carbon::parse('first day of this month'); // first day of this month

Carbon::parse('first day of next month'); // first day of next month

Carbon::parse('first day of last month'); // first day of last month

Carbon::parse('last day of last month'); // last day of last month


Retrieve values of a carbon in various formats;

$now->year; 

$now->month; 

$now->dayOfWeek; 

$now->englishDayOfWeek; 

$now->englishMonth; 

$now->tzName; 

$now->dst;


Subtract one hour

Carbon::now()->subHour();

Subtract more than 1 hour

Carbon::now()->subHours(2);


Add one hour

Carbon::now()->addHour();

Add more than 1 hour

Carbon::now()->addHours(2);


Add one day

Carbon::now()->addDay();

Add more than 1 day

Carbon::now()->addDays(2);


This can also be applied to subWeeks(), addWeeks().


Set to a specific date by altering day or month or year

$currentDateTime = $currentDateTime->setMonth(2);

$currentDateTime = $currentDateTime->setDay(18);

$currentDateTime = $currentDateTime->setYear(2025);

Example that applies a specific day and month.

$currentDateTime = $workDayStart->setDay($calcCreatedDate->format('d'))->setMonth($calcCreatedDate->format('m'))->setYear($calcCreatedDate->format('Y'));


Retrieve the difference between 2 Carbon dates $start and $now.

$start->diff($now); \\ returns DateInterval

$start->diffInMinutes($now); \\ returns difference in minutes

$start->diffInMinutes($now); \\ returns difference in

$start->diffForHumans($now);

Friday, December 1, 2023

Manage services on Centos Linux

On Centos Linux (in this case version 8), the command systemctl allows administration of services on Linux. The version of systemctl in use is displayed with command

systemctl --version

systemd 239 (239-58.el8)

+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=legacy


Check status of services

systemctl status httpd

systemctl status containerd

systemctl status kubelet

systemctl list-unit-files


Background services is list with

systemctl list-jobs


View service information

systemctl show httpd

systemctl show containerd


Start and stop a service

systemctl start httpd

systemctl stop httpd


On some services, there is the command to restart or reload. Reload, reads the updated configuration for a service without stopping the service.

systemctl start httpd

systemctl reload httpd


Boot target

On linux, the run levels describe what the server should do after a startup. Where runlevel and the numeric equivalent of target. Here is a list of runlevel and in brackets are the systemctl commands for it.

Runlevel 0 - poweroff.target (systemctl isolate poweroff.target)
Runlevel 1 - rescue.target  (systemctl isolate rescue.target)
Runlevel 2 - text based multi-user.target without network  (systemctl isolate runlevel2.target)
Runlevel 3 - text based multi-user.target with network  (systemctl isolate runlevel3.target)
Runlevel 5 - graphical graphical.target  (systemctl isolate graphical.target)
Runlevel 6 - reboot.target (systemctl isolate reboot.target)

Default boot target is set by /etc/systemd/system/default.target and can be easily viewed with the command 'ls'.

Or the command systemctl get-default
multi-user.target

View available targets
systemctl list-units --type target --all

To change a default boot target,
systemctl set-default multi-user.target

Troubleshooting

List dependencies of the service

systemctl list-dependencies httpd


Unit files are list as

systemctl list-unit files


When a service is mask, it cannot be started until it is unmask. This can be done with

systemctl unmask httpd


Wednesday, October 18, 2023

Configure L5 Swagger and documention for GET and POST

Here, I will describe usage of Swagger, list the L5 Swagger basic configurations, provide templates to document POST and GET API (Application Programming Interface).

What does L5 Swagger provide?

For PHP developers, here in particular those using Laravel framework, L5 Swagger provide the means to document your API and have it presented in the form of a web page for quick browsing of available APIs and testing its results.

Currently here are good to know facts

  1. Its is developed as a wrapper on swagger-php and swagger-api specifically for Laravel framework.
  2. It supports OpenAPI (formerly known as Swagger), a specification for documentation of RESTful API irrespective of technology, like PHP, Java or .Net.
  3. L5-Swagger currently supports OpenAPI version 3.0 and 3.1. Its project page is https://github.com/DarkaOnLine/L5-Swagger
  4. It supports at least PHP version 7.2. PHP 8.1 introduces the use of attributes.
  5. An online swagger editor is available at swagger.io
My example hinges on PHP 7.4 with darkaonline/l5-swagger version 8.5.1. Will just dump example of code and configurations here. Details will be explained at another time. Good luck.

Quick notes to setup of Swagger in a ready Laravel version 7 or 10 project;

composer require "darkaonline/l5-swagger"
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
php artisan l5-swagger:generate

Customisation can be done by editing config/swagger.php, which can be continued in future articles.

Security

Security options are;
  • None - no security is set to access API
  • Basic Auth - Username and password is set for each request
  • API Key - A key is set for each request
  • OATH - An authorisation scheme for each request

Example 1 - API to login

Request 

Headers:
App-Key: SOmeVeryLongKey

Body form-data:
username: example@some.email.example
password: password

API returns with HTTP code 200

{
    "user_id": 4142,
    "token": "173892|HxOQJBfDgDgDgaqgCpSS1rh7UY7HWdurtanHhq7"
}

API returns with HTTP code 400

{
    "message": "These credentials do not match our records."
}

Example 2 - API to retrieve user profile

Request 

Headers:
App-Key: SOmeVeryLongKey

API returns with HTTP code 200

{
  "status": 0,
  "message": null,
  "data": [
    {
      "id": 385,
      "name": "Bintulu",
      "address1": "no.3 River side, Sarawak",
      "address2": null,
      "introduction": "Software architect and Postgresql architect",
      "phone": "1234512345",
      "email": "bintulu@some.email.example",
      "notes": "Call by phone"
    }
  ],
  "timestamp": "2023-03-17T10:00:09"
}

Swagger documentation

Swagger group for Login

Swagger group for login


Swagger for login

Added example to submit with multi/form-data (which is not necessary for login)




Swagger for user profile

Swagger user profile

Swagger user profile


The Code

Our example api.php and UserController.php

routes/api.php

Route::post('/login', 'API\UserController@login');
Route::get('/user/profile', 'API\UserController@login');

Function login in app/Http/Controllers/API/UserController.php

    /**
     * @OA\Post(
     *     path="/api/login",
     *      tags={"Login"},
     *      security={{"appkey":{}}},
     *      @OA\RequestBody( required=true, description="Login",
     *           @OA\MediaType(
     *             mediaType="multipart/form-data",
     *             @OA\Schema(
     *                 required={"username","password"},
     *                 @OA\Property(
     *                     property="username",
     *                     type="string",
     *                     description="user login id of type email"
     *                 ),
     *                 @OA\Property(
     *                     property="password",
     *                     type="password"
     *
     *                ),
     *             ),
     *          ),
     *
     *     ),
     *     @OA\Response(response="200", description="An example endpoint",
     *          @OA\JsonContent(
     *               @OA\Property(property="id", type="number", example="1957"),
     *               @OA\Property(property="token", type="string", example="173892|HxOQJBfDgDgDgaqgCpSS1rh7UY7HWdurtanHhq7"),
     *          ),
     *     ),
     *     @OA\Response(response="400", description="The id or password incorrect.",
     *           @OA\JsonContent(
     *               @OA\Property(property="message", type="string", example="These credentials do not match our records."),
     *           ),
     *     ),
     * )
     */

Function getUserProfile in app/Http/Controllers/API/UserController.php
    /**
     * @OA\Get(
     *     path="/api/user/profile",
     *     tags={"Login"},
     *     summary="Retrieve user profile",
     *     description="Retrieve user profile based on user auth detected. No parameters are required",
     *     operationId="getUserProfile",
     *     security={{"bearer_token":{}}},
     *      @OA\Parameter(
     *         name="App-Key",
     *         in="header",
     *         description="App-Key",
     *         example=L5_SWAGGER_APPKEY
     *      ),
     *
     *     @OA\Response(response=401, description="User not authenticated",
     *           @OA\JsonContent(
     *               @OA\Property(property="status", type="number", example="1"),
     *               @OA\Property(property="message", type="string", example="Not authenticated"),
     *               @OA\Property(property="data", type="string", example=null),
     *               @OA\Property(property="timestamp", type="string", example="2023-03-17T10:00:09"),
     *           ),
     *     ),
     *       @OA\Response(
     *         response=200,
     *         description="Success",
     *         @OA\JsonContent(
     *           @OA\Property(property="status", type="number"),
     *           @OA\Property(property="message", type="string", example=null),
     *           @OA\Property(property="data", type="array",
     *               @OA\Items(
     *                  @OA\Property(property="id", type="number", example=385),
     *                  @OA\Property(property="name", type="string", example="Bintulu"),
     *                  @OA\Property(property="address1", type="string", example="no.3 River side, Sarawak"),
     *                  @OA\Property(property="address2", type="string", example=null),
     *                  @OA\Property(property="introduction", type="string", example="Software architect and Postgresql architect"),
     *                  @OA\Property(property="phone", type="string", example="1234512345"),
     *                  @OA\Property(property="email", type="string", example="bintulu@some.email.example"),
     *                  @OA\Property(property="notes", type="string", example="Call by phone"),
     *              ),
     *           ),
     *           @OA\Property(property="timestamp", type="string", example="2023-03-17T10:00:09"),
     *         ),
     *       ),
     *  ),
     */


Tuesday, October 17, 2023

Laravel Helper class

Programming is made more systematic with a large number of helper classes in Laravel. Examples are the Arr::last, Arr::add, Arr::get, asset, route, secure_url, url, dd, collect, env)

Lots of documentations are available at Laravel (see Laravel 7). 

Example of usage

Helper url( )

Returns a fully qualified URL

$url = url('user/profile');

Creating your first helper class

The following illustrates a function named "courier" that will be available to all controllers. It typically returns data in a predefined format.

Step 1: Create a helper file in app/Helpers with the name myHelpers.php


/app/Helpers/myHelpers.php

Step 2: Create the function in the file myHelpers.php


<?php
use Carbon\Carbon;

if (! function_exists('courier')) {
function courier($status, $message, $data){
$now = carbon::now();
$status = $status??0;
$package = [
'status'=>$status,
'message'=>$message,
'data'=>$data,
'timestamp'=>$now,
];
return $package;
}
}

Step 3: Edit [autoload] in composer.json


"autoload": {
        "files": [
            "app/Helpers/myHelpers.php",
        ],

Step 4: Reload Laravel


composer dump-autoload

Usage of "courier" helper 

In any of the function in Controller classes, call the helper function. Example

public function getUsers( ){
$users = User::where('status','active')->get();
$status=0; // success
$message=null;
if(count($users)>0){
$status=1; // success but not users available
$message="None";
}
return response(courier($status, $message, $users), 200);
}




Thursday, October 12, 2023

Laravel 10 - User API authentication with Sanctum

Laravel 10 and User API authentication with sanctum

Laravel 10 is available to create restful API where it provides (1)process to issue API tokens to users AND (2)authentication of single page applications(SPA).

This tutorial requirements of system;

  • laravel/sanctum version 3.3.1
  • PHP version 8.2.11
  • Node version 18.12.1
  • Composer version 2.6.3
  • Npm version 8.19.2
  • PostgreSQL database version 15

Laravel application is successfully installed will all recommended PHP extensions.

Create the database and assign user hello assign to that database, which I name as demo. Use hello, or any other user you have created in the database.


create database demo;
grant all privileges on database demo to hello;
ALTER DATABASE demo OWNER TO hello;


Lets create the Laravel application and add sanctum support


composer create-project laravel/laravel demo
cd demo


Configure the .env file to access the database that was declared as demo.


DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=demo
DB_USERNAME=hello
DB_PASSWORD=

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"


Identify and inspect the following folders and files;


config/sanctum.php
database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php


Create database for Sanctum and enable Sanctum


php artisan migrate


Edit app/Http/Kernel.php


'api' => [
    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],


Configure sanctum by editing model, service provider and auth config. Edit app/Models/User.php


use Laravel\Sanctum\HasApiTokens;
...
use HasApiTokens;


Add API to register and login


Edit routes/api.php

  

Route::controller(RegisterController::class)->group(function(){
    Route::post('register', 'register');
    Route::post('login', 'login');
});


php artisan make:controller BaseController
php artisan make:controller RegisterController


Edit RegisterController 


use App\Http\Controllers\BaseController as BaseController;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Validator;
use Illuminate\Http\JsonResponse;


public function register(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email',
            'password' => 'required',
            'c_password' => 'required|same:password',
        ]);
   
        if($validator->fails()){
            return $this->sendError('Validation Error.', $validator->errors());       
        }
   
        $input = $request->all();
        $input['password'] = bcrypt($input['password']);
        $user = User::create($input);
        $success['token'] =  $user->createToken('MyApp')->plainTextToken;
        $success['name'] =  $user->name;
   
        return $this->sendResponse($success, 'User register successfully.');
    }
   
    /**
     * Login api
     *
     * @return \Illuminate\Http\Response
     */
    public function login(Request $request): JsonResponse
    {
        if(Auth::attempt(['email' => $request->email, 'password' => $request->password])){ 
            $user = Auth::user(); 
            $success['token'] =  $user->createToken('MyApp')->plainTextToken; 
            $success['name'] =  $user->name;
   
            return $this->sendResponse($success, 'User login successfully.');
        } 
        else{ 
            return $this->sendError('Unauthorised.', ['error'=>'Unauthorised']);
        } 
    }


Retrieve the registration api 


{
    "success": true,
    "data": {
        "token": "1|R8qfygjItwjleo23QwdqqS5ZcVLZwaRH72iJjiEqd4d85583",
        "name": "admin@example.com"
    },
    "message": "User register successfully."
}


Retrieve login api


{
    "success": true,
    "data": {
        "token": "2|IyNnxOU0N1cc0s2bADqzASxzwc8kl7z5UbqZ2oARd68aa58b",
        "name": "admin@example.com"
    },
    "message": "User login successfully."
}


Ref: https://www.itsolutionstuff.com/post/laravel-10-rest-api-authentication-using-sanctum-tutorialexample.html#google_vignette

https://laravel.com/docs/10.x/sanctum#token-ability-middleware


Next, add a appkey token.

https://laravel.com/docs/10.x/middleware

Monday, October 2, 2023

MySQL group by unix timestamp

Drupal CMS includes a webform where each form has an ID. An example to retrieve number of user access of a Drupal database for a given node id. The column ws.created is stored with a unix timestamp (looks like many digits number). Use MySQL function from_unixtime to format into something like 2023-10-02. The node in this example have an ID=940.

select count(ufd.name) submissions, DATE_FORMAT(from_unixtime(ws.created), "%Y-%m-%d")
from webform_submission ws 
left join users_field_data ufd 
on ws.uid = ufd.uid
left join node_field_data nfd 
on ws.entity_id = nfd.nid
where ws.in_draft = 0
and ws.entity_type like 'node'
and ws.entity_id = 940
group by DATE_FORMAT(from_unixtime(ws.created), "%Y-%m-%d")

Here is a query to list all the associated users who accessed the webform

select ufd.name name, ufd.mail email, from_unixtime(ws.created) accepted_at, from_unixtime(ws.changed) changed_at, ws.remote_addr 
,nfd.title, ws.uri URL
from webform_submission ws 
left join users_field_data ufd 
on ws.uid = ufd.uid
left join node_field_data nfd 
on ws.entity_id = nfd.nid 
where ws.in_draft = 0
and ws.entity_type like 'node'
and ws.entity_id = 940

Tuesday, September 19, 2023

R language basics

The R programming language can be downloaded then installed from https://cran.r-project.org/index.html

Next download and install R Studio from https://posit.co/download/rstudio-desktop/

Install the R tutorial by opening R Studio, in a console install the package swirl and start the tutorial.

install.packages("swirl")

library(swirl)

swirl()

Following are the initial list of commands learnt from Swirl in lesson 1 to 4

skip(), play(), nxt(), bye(), main(), info()

c(), sqrt(), info()

help commands ?c , ?`:`

getwd(), ls(), list.files(), dir(), args(), getwd(), dir.create(), setwd(), file.create(mytest.R), file.exists(), file.info(), file.rename(from, to), file.path(),setwd(),unlink("testdir",recursive=TRUE)

seq(), seq(1,10, by=0.5), length(), rep(0, times=40),rep(c(0,1,2), times=10)

rep(c(0,1,2), each=10)


Happy R gramming!

Blog Archive