Deploy with Circle CI on shared hosting via FTP

The following config.yml script can be used in circle CI to deploy code from github / bitbucket to any hosting provider with FTP credentials. Please note the FTP credentials will have to be saved as environment variables in circle CI with the following names FTP_USER, FTP_PWD, FTP_LOCATION. Example of the values for the environment variables can be
FTP_USER = “user@abc.com”, FTP_PWD = “abc45sdd”, FTP_LOCATION = “ftp://abc.com/”.

version: 2
jobs:
  deploy:
    docker:
      - image: circleci/php:7.1.5-browsers

    working_directory: ~/repo

    steps:
      - checkout

      - run: 
          name: Deploy Master Branch
          command: |
            sudo apt-get update
            sudo apt-get -qq install git-ftp
            echo "Deploying project ..."
            echo $(git status)
            git ftp init --init-if-not-exist --user "${FTP_USER}" --passwd "${FTP_PWD}" ${FTP_LOCATION}

workflows:
  version: 2
  just-deploy:
    jobs:
      - deploy:
          filters:
            branches:
              only: master
Advertisements

Setup sentry on CodeIgniter 3.1

First thing you need to do is install sentry using composer, as composer is now supported and comes with CI 3.1.

composer require cartalyst/sentry 2.0.*
composer require illuminate/database 4.0.*
composer require ircmaxell/password-compat 1.0.*

Then, enable composer based auto-loading in codeigniter from application/config/config.php. Since by default composer creates the vendor folder at the root we need to give the path to the auto-loader file.

$config['composer_autoload'] = 'vendor/autoload.php';

Also, you need to update application/config/database.php to use PDO driver and don’t forget to import the SQL onto your database to create the required tables. You will find the SQL dump at vendor/cartalyst/sentry/schema folder.

Now you are ready to use sentry, just add the following line at any controller you want to use the sentry object.

use Cartalyst\Sentry\Facades\CI\Sentry as Sentry;

A sample use-case can be –

<?php defined('BASEPATH') OR exit('No direct script access allowed');

use Cartalyst\Sentry\Facades\CI\Sentry as Sentry;

class Welcome extends MY_Controller {

	public function signup()
	{
		$sentry = Sentry::createSentry();
		$sentry->register(array(
		    'email'    => 'faisal@mailinator.com',
		    'password' => 'abcdefg'
		),true);
	}
}

Redux in ReactJS

Redux lets react component states to be stored in a centralized manner. The idea is to be able to impact the state from one component and be able to reflect the change be applied to another one as they share the state through the store.

The biggest problem with bootstrapping redux with react is to keeping in mind the relations. I have found it easier to remember it in the following order.

Chronological Order:

1. Wrap main app return in Provider and pass store object to Provider as parameter. (Possible location: ./app.js)
2. Create store (Location: ./store.js)
2.1 While creating store you will need to pass your rootReducer (Location: ./reducers/index.js)
2.2 While creating rootReducer you need to combine other reducers (Example: ./reducers/postReducer.js)
2.3 While creating postReducer you need to bring in the action types. (Example: ./actions/type.js)
2.4 After creating type.js you need to create postActions.js to define actions allowed on Post component. (Example: ./actions/postActions.js)
2.5 Now that the Post actions are defined you need to connect it to the Post component. (Location: ./components/post.js)

To use redux in react you need to have reduxreact-redux and redux-thunk installed. You can install them via node package manager by typing –

npm install redux react-redux redux-thunk

The first thing needed after installing the modules is to wrap the main app return call in <Provider&gt; tag in your app.js or main app file. Make sure you import Provider from ‘react-redux’ in the app.js or your main app file. The provider requires that you give it a store object in it’s store parameter.

Step 1:

import React, {component} from 'react';
import {Provider} from 'react-redux';

import Posts from './components/posts';
import Store from './store';

class App extends React.Component{
  render(){
   return (
     <Provider store={Store}>
       <Posts/>
     </Provider>
   );
  }
} 

export default App;

You create the store object using redux’s createStore method. The method takes 3 parameters, they are 1) reducer 2) initial state 3) enhancer. You need to import createStore and applyMiddleware enhancer method from ‘redux‘ before you use them. Common approach is to save a store.js file at root and import it then pass the object as parameter. In the store.js file you should also import thunk from ‘redux-thunk‘ which is used for dispatching the action.

Step 2:

import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';

import rootReducer from './reducers';

const initialState = [];
const middleware = [thunk]

const Store = createStore(rootReducer, initialState, ...middleware); 

export default Store;

This file is the rootReducer, located at ‘./reducers/index.js’. It will combine all separate reducers into single file. combineReducers() is used for combining the reducers. It must be imported.

Step 2.1:

import { combineReducers } from 'redux';
import postReducer from './postReducer';

export default combineReducers({
  posts: postReducer
});

Step 2.2:

The following file is the ‘./reducers/postReducer.js’. It defines the initial state and the object to be returned based on the type of action.

import { FETCH_POSTS } from '../actions/types';

const initialState = {
  items: [],
  item: {}
};

export default function(state = initialState, action) {
  switch (action.type) {
    case FETCH_POSTS:
      return {
        ...state,
        items: action.payload
      };
     default:
      return state;
  }
}

Step 2.3:

Whole application can have many different action types. So, its best to store them all in a single file. For instance this file is stored at ‘./actions/types.js’;

export const FETCH_POSTS = 'FETCH_POSTS';

Step 2.4:

The following code is in ‘./actions/postActions.js’. This defines the fetchPosts() method. It needs to import action type values from ‘./actions/type.js’ so it can be dispatched as action type. The action type is a required element of the object here. Payload can be anything and not necessarily has to be payload.

import { FETCH_POSTS } from './types';

export const fetchPosts = () => dispatch => {
  fetch('https://jsonplaceholder.typicode.com/posts')
    .then(res => res.json())
    .then(posts =>
      dispatch({
        type: FETCH_POSTS,
        payload: posts
      })
    );
};

Step 2.5:

The following code is in ‘./components/Post.js’ here we connect the fetchPosts action and update the components properties from the store state values. If you notice this.props.fetchPosts() is called in componentWillMount() life-cycle method. The fetchPosts() method is injected as props when we connect the postAction with this component. The connection is set at the last line connect(mapStateToProps, { fetchPosts })(Posts);. Here the first parameter maps the state values to the component properties as per our requirement. The second parameter is taking the fetchPosts() method thats injected as property to this component.

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/postActions';

class Posts extends Component {
  componentWillMount() {
    this.props.fetchPosts();
  }
  render() {
    const postItems = this.props.posts.map(post => (
      <div key={post.id}>
        <h3>{post.title}</h3>
        <p>{post.body}</p>
      </div>
    ));
    return (
      <div>
        <h1>Posts</h1>
        {postItems}
      </div>
    );
  }
}

Posts.propTypes = {
  fetchPosts: PropTypes.func.isRequired,
  posts: PropTypes.array.isRequired
};

const mapStateToProps = state => ({
  posts: state.posts.items
});

export default connect(mapStateToProps, { fetchPosts })(Posts);

Reference:
https://www.youtube.com/watch?v=93p3LxR9xfM
https://github.com/bradtraversy/redux_crash_course

React app on Codeanywhere

As of now there is no container available for react app only. So, you can follow these steps to get a ReactJS app running on codeanywhere.com

  • Start a blank container. I used ubuntu 14.04 based blank container.
  • Install node, npm and npm react app generator

sudo apt-get update

sudo apt-get install nodejs

sudo apt-get install npm

npm install -g create-react-app

create-react-app my-react-app

cd my-react-app

npm start

  • You will see in command prompt the development server is run and http://localhost:3000 or similar address is given for accessing it. Replace localhost with your codeanywhere.com container domain followed by the port 3000. That is the public url of your react app. The url will be similar to `http://reactjs-userx12345.codeanyapp.com/`

 

Batch compress image using google guetzli

You can use the following code written by me to run batch compression of images with google’s guetzli image compression tool. It creates a compressed folder and saves all the compressed images of the folder inside the folder. Note: Right now this only compresses image from the location that is given and not the sub-folders.

Usage:

Syntax: base [stcriptname] [relative folder location] [percentage]

Implementation: bash batch-compresssion . 84

#/usr/bin/bash

if [ ! -d "$1/compressed" ]; then
	mkdir "$1/compressed"
fi

for file in $1/*.png
do
  echo $file
  guetzli --quality $2 "$file" "$1/compressed/$file"
done

for file in $1/*.jpg
do
  echo $file
  guetzli --quality $2 "$file" "$1/compressed/$file"
done

for file in $1/*.jpeg
do
  echo $file
  guetzli --quality $2 "$file" "$1/compressed/$file"
done

Adding virtual host to bitnami magento 2 virtual machine

To setup virtual host on the bitnami virtual machine two files will have to be changed. They are htaccess.conf and httpd-vhosts.conf. These files are located at /opt/bitnami/apps/magento/conf/ folder.

First we have to update the htaccess file and add the following lines at the end –

SetEnvIf Host ^m2\.dev MAGE_RUN_CODE=base
SetEnvIf Host ^m2\.dev MAGE_RUN_TYPE=website

Here, the MAGE_RUN_CODE’s value “base” is the code for the default magento stores front-end and “^m2\.dev” is regular expression for setting domain staring with m2.dev

Once the htaccess file is updated. Now, we have to update the httpd-vhosts.conf file and add the m2.dev alias.

<VirtualHost *:80>
  ServerName yourdomain.com
  ServerAlias www.yourdomain.com m2.dev www.m2.dev
  DocumentRoot "/opt/bitnami/apps/magento/htdocs/"
  Include "/opt/bitnami/apps/magento/conf/httpd-app.conf"
</VirtualHost>

Once these two files are updated we have to restart apache server by running the following command –

sudo /opt/bitnami/ctlscript.sh restart apache

Now, we have to login to the admin using IP address one last time and go to stores > configuration > web. We have to update the “Base URL” to http://m2.dev/ and “Secure Base URL” to https://m2.dev/ and click save. After saving the user will be logged out of the system as the m2.dev domain is applied. Login back to the admin panel and now you will see notification to refresh cache. Follow the link and refresh the cache. Now you are ready to use the virtual host with this virtual machine.

Don’t forget to add the virtual host IP in the host computers /etc/hosts file.

192.168.1.108 m2.dev www.m2.dev

To keep the virtual host setup from getting reset follow the following steps.

Additional Task:
Every time the virtual machine is restarted the IP is automatically assigned as the base url of magento. This is done to ensure the application adapt to a changing IP address on each restart. But in our case this becomes a problem as on each restart we have to go and set the virtual host domain again. To stop this from happening we have to rename two files at /opt/bitnami/apps/magento/ the files that needs to be renamed are updateip and bnconfig.

mv /opt/bitnami/apps/magento/updateip /opt/bitnami/apps/magento/updateip.old
mv /opt/bitnami/apps/magento/bnconfig /opt/bitnami/apps/magento/bnconfig.old

Source: http://bit.ly/2qgRjTQ