Refactoring and Remodeling Badgeyay API

When we build a full scale production application, we make sure that everything is modeled correctly and accordingly to the need of the code. The code must be properly maintained as well as designed in such a way that it is less prone to errors and bugs.

Badgeyay is also targeting to be a full production application, and in order to achieve it we first need to re-factor the code and model it using a strong yet maintainable structure.

What is the current state of Badgeyay?

Currently Badgeyay is divided into two sub folders.

\badgeyay
    \frontend
    \backend
    .
    .

It is backed by two folders, viz backend and frontend. The ‘backend’ folder handles the API that the service is currently running. The ‘frontend’ folder houses the Ember based frontend logic of the application.

Improvements to Badgeyay Backend

We have worked on improving Backend for Badgeyay. Instead of traditional methods, i.e. current method, of API development; We employ a far better approach of using Flask Blueprint as a method of refactoring the API.

The new backend API resides inside the following structure.

\badgeyay
    \backend
        \blueprint
            \api

The API folder currently holds the new API being formatted from scratch using

  • Flask Blueprint
  • Flask Utilities like jsonify, response etc

The new structure of Badgeyay Backend will follow the following structure

api
    \config
    \controllers
    \helpers
    \models
    \utils
    db.py
    run.py

The folders and their use cases are given below

  • \config
    • Contain all the configuration files
    • Configurations about URLs, PostgreSQL etc
  • \controllers
    • This will contain the controllers for our API
    • Controllers will be the house to our routes for APIs
  • \helpers
    • Helpers folder will contain the files directly related to API
  • \models
    • Models folder contains the Schemas for PostgreSQL
    • Classes like User etc will be stored in here
  • \utils
    • Utils will contain the helper functions or classes
    • This classes or functions are not directly connected to the APIs
  • db.py
    • Main python file for Flask SQLAlchemy
  • run.py
    • This is the main entry point.
    • Running this file will run the entire Flask Blueprint API

How does it help?

  • It helps in making the backend more solid.
  • It helps in easy understanding of application with maintained workflow.
  • Since we will be adding a variety of features during Google Summer of Code 2018 therefore we need to have a well structured API with well defined paths for every file being used inside it.
  • It will help in easy maintaining for any maintainer on this project.
  • Development of the API will be faster in this way, since everything is divided into sub parts therefore many people can work on many different possibilities on the same time.

Further Improvements

Since this structure has been setup correctly in Badgeyay now, so we can work on adding separate routes and different functionalities can be added simultaneously.

It ensures faster development of the project.

Resources

Adding Map Type Response to SUSI.AI Chromebot

SUSI.AI Chromebot has almost all sorts of reply that SUSI.AI Server can generate. But it still missed the Map Type response that was generated by the SUSI.AI Server.

This blog explains how the map type response was added to the chromebot.

Brief Introduction

The original issue was planned by Manish Devgan and Mohit Sharma as an advanced task for Google Code-In 2017. The link to which can be found here: #157

For a long time the issue remained untouched and after GCI got over I assigned the issue to myself as it was a priority issue since MAP type was a major response from the SUSI.AI Server.

How was Map Type response added?

There were a lot of things to be taken in mind before starting working on this issue.

  • Changing code scheme during GCI and other PRs
  • API Response from the SUSI.AI Server
  • Understanding the new codebase that got altered during GCI-17
  • Doing it quick

I will go through all the steps in detail

Changing Code Scheme

The code was altered numerous times with the addition of a number of pull requests during GCI-17 and there were no docstrings for any functions and methods. So I had to figure them out in order to start working on the map type response.

API Response from the SUSI.AI Server

To understand the JSON that server sent, I went to SUSI.AI API and did a simple search for

“Where is Berlin?” and the response generated is given below.

( Since the JSON is very big I am only posting the relevant data for this issue )

 

    "actions": [
      {
        "type": "answer",
        "language": "en",
        "expression": "Berlin (, German: [bɛɐ̯ˈliːn] ( listen)) is the capital and the largest city of Germany as well as one of its 16 constituent states."
      },
      {
        "type": "anchor",
        "link": "https://www.openstreetmap.org/#map=13/52.52436820069531/13.41053001275776",
        "text": "Here is a map",
        "language": "en"
      },
      {
        "type": "map",
        "latitude": "52.52436820069531",
        "longitude": "13.41053001275776",
        "zoom": "13",
        "language": "en"
      }
    ]

 

Here we see and understand that “actions” is an Array of JSONs and the third part has “type” as “map”. This is the relevant information that we require for generating the map-type response.

The important variables in this context are: “latitude” and “longitude”.

Understanding the Codebase

Now I had to figure out the new pattern of adding response types to the SUSI.AI Chromebot.

After having a talk with @ms10398 I figured out the route map.

The above image shows the correct flow of Javascript Code that generated the response. After this, I was good to go and start my work.

Adding the Map-Type Response

To start with I chose “LEAFLET.JS” as the Javascript Library that will be used to create maps.

  • So I added the LEAFLET.JS to the JS folder.
  • Now changes were made to the “index.html” file

 

<link rel=”stylesheet” href=”https://unpkg.com/[email protected]/dist/leaflet.css” />

http://”js/leaflet.js”

 

Appropriate CSS was added along with a link to leaflet.js was added.

  • Adding CSS to the “mapClass
.mapClass{

    height : 200px;

    width : 200px;

}

 

  • Generating Maps with dynamic IDs

This part was where I applied brain, as to add the map to any div we required the div to have a proper and unique ID and so a way to generate unique IDs for div without using any external source was to be thought of.

I came with idea of using timestamp, as it will always be unique.

var timeStamp = new Date.now().toString();

 

Then I created the “composeMapReply()” function.

function composeReplyMap(response, action){

    var newDiv = messages.childNodes[messages.childElementCount];

    var mapDiv = document.createElement(“div”);

    var mapDivId = Date.now().toString();

    mapDiv.setAttribute(“id”, mapDivId);

    mapDiv.setAttribute(“class”, “mapClass”);

    newDiv.appendChild(mapDiv);

    messages.appendChild(newDiv);

    var newMap = L.map(mapDivId).setView([Number(action.latitude), Number(action.longitude)], 13);

 L.tileLayer(“https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}”,{

    /*

        This part contains the data for api call.

    */

}).addTo(newMap);

response.isMap = true;

response.newMap = mapDiv;

return response

    

}

 

The complete code can be found: here

At last after adding so many snippets of code we were able to generate the Map-Type response for SUSI.AI Chromebot

GIF

A gif showing the Map-Type response in action.

 

Resources

 

Generating Badges for Manual Data for Pre-Selected Badges

BadgeYay is a Badge Generator developed by FOSSASIA. In recent time there was a BUG that caused the badge generator to throw errors and malfunction while generating the badges.

This error was first reported by me, @gabru-md. And later when everyone seemed to have this bug it was resolved after a complete 48 hour of reverse engineering the code.

What was the Bug?

The bug with the generator was that the server side API did not function well and was not generating badges in cases of Manual Input and Pre-Selected Images.The issue was first made sure and then created on github as Issue number 314 .

Resolving the Bug

Resolving the bug this time was a hard task as the code was not properly maintained due to many PRs being merged and due to this it took me 48 hours to figure out what was wrong with the code.

After like 1 day of reading between the lines it was found out that the bug was caused due to improper “if…else” conditions. There were several  small bugs that arouse when this main bug was being dealt with.

How was it resolved?

Many changes were done to the code to resolve the bug. It was definitely the most time consuming and important fix that I had ever applied to any project.

The only changes were done to the main server file “main.py”.

  • Adding a missing line to the file.
text_on_image = request.form[“text_on_image”]

 

  • Removing unnecessary code for cleaning up the file.
if file.filename == ‘’  and csv ==  ‘’:

    flash(‘Please select a CSV field to upload’)

    return redirect(url_for(‘index’))

 

And

elif:

    if file.find(“png.csv”) != -1:

        if img == ‘’:

            flash(‘{Please upload an image in ...’)   

            return redirect(url_for(‘index’))

    else:

        flash(‘Please upload ...’)

 

  • Adding the relevant code to fix the bug.

 

if img == ‘’:

    img = request.filed[‘image’].filename

    filename = request.files[‘image’].filename + “.csv”

elif csv != ‘’:

Changing filename to “img + .csv” resolved the filename error that caused the badge generator not to recognize the files.

Saving the files to the correct places for the script to recognize them

image.save(os.path.join(app.config[‘UPLOAD_FOLDER’],image.filename))

 

Changing the rest of the code to comply with the changes and make Badgeyay BUG free.

elif eventyay_url != ‘’:

    filename = ‘speaker.png.csv’

    generate_csv_eventyay.tocsv(eventyay_url,filename)

if filename.find(‘png.csv’) != -1:

    if img == ‘’:

        flash(“Please Upload …”)

        return redirect(url_for(‘index’))

else:

    flash(‘Please Upload a CSV …’)

    return redirect(url_for(‘index’))

 

The last change was to change an “if” condition to a relevant one.

if csv == ‘’ and filename == img + ‘.csv’ and eventyay_url == ‘’:

 

All these changes helped resolve one of the major bugs in Badgeyay. With the merging of the associated PR the bug was immediately fixed and Badgeyay was up again.

Challenges

  • Lack of time since service was down for a long time
  • Improper code

 

But I took them as challenges and was able to fix it for once and for all.

Further Improvements

Further Improvements will be leading to a more fast and stable Badgeyay with more user friendly options and an improved UI and stronger UX.

Resources

 

Adding Preview Support to BadgeYay!

In an issue it was requested to add a Preview support for BadgeYay, i.e. Badges could be seen before they were generated.

Why Preview Support?

It is a nice question. But Preview support was needed in a badge generator like BadgeYay.

This can be easily answered by an example. Let us suppose that I want to generate hundreds-thousands of badges for a meetup/event that I have organized. But I am confused as to what will look the best on and as badges. So I can just try them all in the Preview section and then choose the one that I like and generate it.

How to add Preview Support?

Adding Preview Support was not an easy task. Although coding it was not the hard part, but thinking of a way that uses less of the server’s support was a thing to take care of.

I had two options to choose from.

Implement Preview Section from backend

This was the idea that first came to my mind when i thought of implementing something like preview section.

It included of generating badges everytime the user wanted a preview and then using the same SVGs generated to show as the preview of badges.

Problems it had

Using Backend to generate badges for every instance would result to a lot of load to the server prior to the actual badge generation. And making it faster and creating less load on server was the main problem to tackle. So I came up with another idea of using frontend to generate Preview(s).

Implementing Preview Section from frontend

This method of generating preview is far more faster and less load heaving to the server.

It uses technologies such as HTML, CSS and Javascript to  generate preview for badges.

The Pull Request for the same is : here

Changes in index.html

  • Adding a button to view preview
<button type=”button” disabled=”disabled” class=”btn btn-block btn-warning” id=”preview-btn”>Preview</button>

 

  • Adding the text areas for badge

Adding appropriate HTML for text areas.

  • Adding Appropriate CSS
.preview-image{

height: 250px;

width: 180px;

margin-left: 80px;

background-size: cover;

padding: 140px 0 0 5px;

text-align: center;

margin-top: 20px;

}

.preview-image-li{

list-style: none;

color: white;

font-size: 15px;

}

#preview-btn{

font-size: 18px;

}

 

  • Adding Javascript code for functionality
function readURL(input){

if(input.files && input.files[0]){

var reader = new FileReader();

reader.onload = function(e){

$(‘#preview’).css(‘background-image’,’url(‘ + e.target.result + ‘)’);

$(‘#preview’).css(background-size’,’cover’);

$(‘#preview-btn’).prop(“disabled”,false);

};

reader.readAsDataURL(input.files[0]);

}

}

 

The above snippet of code adds the image to the background of the preview div and stretches it to occupy full space.

var textValues = $(‘#textArea’).val();

textValues = textValues.split(“/n”)[0].strip(‘,’);

$(‘#preview-li-1’).text(textValue[0]);

$(‘#preview-li-2’).text(textValue[1]);

$(‘#preview-li-3’).text(textValue[2]);

$(‘#preview-li-4’).text(textValue[3]);

The above snippet of code adds the input from the textArea to the appropriate place on the preview badge.

Further Improvements

Adding a real-time preview feature that allows user to see the changes in real-time therefore making the application more flexible and enhancing user experience.

Resources

 

Toggling Voice On/Off in SUSI Chromebot

SUSI Chromebot has a lot of features that make it one of the best projects of FOSSASIA.

Recently Voice/Speech was added to SUSI Chromebot. But there was no option that controlled the fact that whether speech output is needed or not.

The latest addition to SUSI Chromebot is Toggling the Voice of SUSI On or Off.

How was it achieved?

Toggling Voice for SUSI required adding a button and a snippet of Javascript code to the main JS file. The code will take care of the fact whether the voice is to be toggled on or off.

I started off by adding a button to the main HTML file.

<a href=”javascript: void(0)” id=”speak” style=”color: white”><i class=”material-icons” id=”speak-icon”>volume_up</i></a>

The above snippet of HTML code adds a voice button to the top bar of chromebot.

Then there was the major part where the javascript code was to be added to add the functionality to the button.

var shouldSpeak = true;

I started off by creating a variable called as “shouldSpeak” which will determine whether or not SUSI should use the Chrome’s API to speak.

Then I changed the “speakOut()” function and added another parameter to it.

function speakOut(msg,speak=false){

if(speak){

var voiceMsg = new SpeechSynthesisUtterance(msg);

window.speechSynthesis.speak(voiceMsg);

}

}

The above code made sure that susi was only allowed to speak when and only “speak” variable was set to true.

Then “eventListeners” were added to buttons and other things to link the functionality.

document.getElementById(‘speak’).addEventListener(‘click’,changeSpeak);

It adds the events of click to “speak” and associates it with the function “changeSpeak”.

Now the function “changeSpeak” is created as follows. It toggles the on/off mechanism of voice in SUSI Chromebot.

function changeSpeak(){

shouldSpeak = !shouldSpeak;

var SpeakIcon = document.getElementById(‘speak-icon’);

if(!shouldSpeak){

SpeakIcon.innerText = “volume_off”;

}

else{

SpeakIcon.innerText = “volume_up”;

}

console.log(‘Should be speaking? ’ + shouldSpeak);

}

Everytime the user clicks on the icon to toggle on/off voice the icon must also change and this functionality was taken care of by the above piece of code.

Resources

 

Resolving Internal Error on Badgeyay

Badgeyay is in development stage and is frequently seen to encounter bugs. One such bug is the Internal Server Error in Badgeyay.

What was the bug?

The bug was with the badge generator’s backend code. The generator was trying to server the zip file that was not present. After going through the log I noticed that it was because a folder was missing from Badgeyay’s directory.

 

I immediately filed an issue #58 which stated the bug and how could it be resolved. After being assigned to the issue I did my work and created a Pull Request that was merged soon.

The Pull Request can be found here.

Resolving the bug

With the help of extensive error management and proper code and log analysis I was able to figure out a fix for this bug. It was in-fact due to a missing folder that was deleted by a subsequent code during zipfile/pdf generation. It was supposed to be recreated every time it was deleted. I quickly designed a function that solved this error for future usage of Badgeyay.

 

How was it resolved?

First I started by checking if the “BADGES_FOLDER” was not present. And if it was not present then the folder was created using the commands below

 

if not os.path.exists(BADGES_FOLDER):

    os.mkdir(BADGES_FOLDER)

 

Then, I added docstring to the remaining part of the code. It was used to empty all the files and folder inside the “BADGES_FOLDER”. We could have to delete two things, a folder or a file.

So proper instructions are added to handle file deletion and folder deletion.

 

for file in os.listdir(BADGES_FOLDER):

    file_path = os.path.join(BADGES_FOLDER, file)

    try:

        if os.path.isfile(file_path):

            os.unlink(file_path)

        elif os.path.isdir(file_path):

            shutil.rmtree(file_path)

    except Exception:

        traceback.print_exc()

 

Here “os.unlink” is a function that is used to delete a file. And “shutil.rmtree” is a function that deletes the whole folder at once. It is similar to “sudo rm -rf /directory”. Proper error handling is done as well to ensure stability of program as well.

Challenges

There were many problems that I had to face during this bug.

  • It was my first time solving a bug, so I was nervous.
  • I had no knowledge about “shutil” library.
  • I was a new-comer.

But I took these problems as challenges and was able to fix this bug that caused the INTERNAL SERVER ERROR : 500 .

Resources