Using Travis CI to Generate Sample Apks for Testing in Open Event Android

In the Open Event Android app we were using Travis already to push an apk of the Android app to the apk branch for easy testing after each commit in the repo. A better way to test the dynamic nature of the app would be to use the samples of different events from the Open Event repo to generate an apk for each sample. This could help us identify bugs and inconsistencies in the generator and the Android app easily. In this blog I will be talking about how this was implemented using Travis CI.

What is a CI?

Continuous Integration is a DevOps software development practice where developers regularly push their code changes into a central repository. After the merge automated builds and tests are run on the code that has been pushed. This helps developers to identify bugs in code quite easily. There are many CI’s available such as Travis, Codecov etc.

Now that we are all caught up with let’s dive into the code.

Script for replacing a line in a file (configedit.sh)

The main role of this script would be to replace a line in the config.json file. Why do we need this? This would be used to reconfigure the Api_Link in the config.json file according to our build parameters. If we want the apk for Mozilla All Hands 2017 to be built, I would use this  script to replace the Api_Link in the config.json file to the one for Mozilla All Hands 2017.

This is what the config.json file for the app looks like.

{
   "Email": "dev@fossasia.org",
   "App_Name": "Open Event",
   "Api_Link": "https://eventyay.com/api/v1/events/6/"
 }

We are going to replace line 4 of this file with

“Api_Link”:”https://raw.githubusercontent.com/fossasia/open-event/master/sample/MozillaAllHands17

VAR=0
 STRING1=$1
 while read line
 do
 ((VAR+=1))
 if [ "$VAR" = 4 ]; then
 echo "$STRING1"
 else
 echo "$line"
 fi
 done < app/src/main/assets/config.json

The script above reads the file line by line. If it reaches line 4 it would print out the string that was given into the script as a parameter else it just prints out the line in the file.

This script would print the required file for us in the terminal if called but NOT create the required file. So we redirect the output of this file into the same file config.json.

Now let’s move on to the main script which is responsible for the building of the apks.

Build Script(generate_apks.sh)

Main components of the script

  • Build the apk for the default sample i.e FOSSASIA17 using the build scripts ./gradlew build and ./gradlew assembleRelease.
  • Store all the Api_Links and apk names for which we need the apks for different arrays
  • Replace the Api_Link in the json file found under android/app/src/main/assets/config.json using the configedit.sh.
  • Run the build scripts ./gradlew build and ./gradlew assembleRelease to generate the apk.
  • Move the generated apk from app/build/outputs/apk/ to a folder called daily where we store all the generated apks.
  • We then repeat this process for the other Api_Links in the array.

As of now we are generating the apks for the following events:

  1. FOSSASIA 17
  2. Mozilla All Hands 17
  3. Google I/O 17
  4. Facebook Developer Conference 17

Care is also taken to avoid all these builds if it is a PR. All the apks are generated only when there is a commit on the development branch i.e when the PR is merged.

Usage of Scripts in .travis.yml

To add Travis integration for the repo we need to include a file named .travis.yml in the repo which indicates Travis CI what to build.

language: android
 ….
 jdk: oraclejdk8
 ….
 before_script:
   - cd android
 script:
   - chmod +x generate_apks.sh
   - chmod +x configedit.sh
   - ./generate_apks.sh
 …..
 after_success:
   - bash <(curl -s https://codecov.io/bash)
   - cd ..
   - chmod +x upload-apk.sh
   - ./upload-apk.sh

In this file we need to define the language for which Travis will build. Here we indicate that it is android. We also specify the jdk version to be used.

Now let’s talk about the other parts of this snippet.

  • before_script : Executes the bash instructions before the travis build starts. Here we do cd android so that we can access gradlew for building the apk.
  • script : This section consists of the instruction to be executed for the build. Here we give executable rights to the two scripts that we have written sh and . Then ./generate_apks is called and the project build starts. All the apks get saved to the folder daily.
  • after_success : This section consists the instructions that are run after the script executes successfully. Here we see that we run a script called sh. This script is responsible of pushing the generated apk files in an orphan branch called apk.

Some points of Interest

  • If the user/developer testing the apk is in the offline state and then comes online there will be database inconsistencies as data from the local assets as well as the data from the Api_Link would appear in the app.
  • When the app generator CLI is ready we can use it to trigger the builds instead of just replacing the Api_Link. This would also be effective in testing the app generator simultaneously.

Now we have everything setup to trigger builds for various samples after each commit.

Resources

Continue ReadingUsing Travis CI to Generate Sample Apks for Testing in Open Event Android

Customizing Serializers in Open Event Front-end

Open Event Front-end project primarily uses Ember Data for API requests, which handles sending the request to correct endpoint, serializing and deserializing the request/response. The Open Event API project uses JSON API specs for implementation of the API, supported by Ember data.

While sending request we might want to customize the payload using a custom serializer. While implementing the Users API in the project, we faced a similiar problem. Let’s see how we solved it.

Creating a serializer for model

A serializer is created for a model, in this example we will create a user serializer for the user model. One important thing that we must keep in mind while creating a serializer is to use same name as that of model, so that ember can map the model with the serializer. We can create a serializer using ember-cli command:

ember g serializer user

 
Customizing serializer

In Open Event Front-end project every serializer extends the base serializer application.js which defines basic serialization like omitting readOnly attributes from the payload.

The user serializer provides more customization for the user model on top of application model. We override the serialize function, which lets us manipulate the payload of the request. We use `snapshot.id` to differentiate between a create request & an update request. If `snapshot.id` exists then it is an update request else it is a create request.

While manipulation user properties like email, contact etc we do not need to pass ‘password’ in the payload. We make use of ‘adapterOptions’ property associated with the ‘save()’ method. If the adapterOptions are associated and the ‘includePassword’ is set then we add ‘password’ attribute to the payload.

import ApplicationSerializer from 'open-event-frontend/serializers/application';
import { pick, omit } from 'lodash';

export default ApplicationSerializer.extend({
  serialize(snapshot, options) {
    const json = this._super(...arguments);
    if (snapshot.id) {
      let attributesToOmit = [];
      if (!snapshot.adapterOptions || !snapshot.adapterOptions.includePassword) {
        attributesToOmit.push('password');
      }
      json.data.attributes = omit(json.data.attributes, attributesToOmit);
    } else if (options && options.includeId) {
      json.data.attributes = pick(json.data.attributes, ['email', 'password']);
    }
    return json;
  }
});

If we want to add the password in the payload we can simply add ‘includePassword’ property to the ‘adapterOptions’ and pass it in the save method for operations like changing the password of the user.

user.save({
  adapterOptions: {
    includePassword: true
  }
})

Thank you for reading the blog, you can check the source code for the example here.
Resources

Learn more about how to customize serializers in ember data here

Continue ReadingCustomizing Serializers in Open Event Front-end

Adding Event Overview Route in Open Event Frontend

In Open Event Frontend we have an event overview route which is like a mini dashboard for an event where information regarding event sponsors, general info, roles, tickets, event setup etc. is present. All of the information is present in their corresponding components and this dashboard is made up of those components. To create this dashboard we will first create its components.

To create a component we will use following ember command-

ember -g component <component-name>

This command will give us three files: a template, a component and a test file corresponding to that component. We will use this command to generate all our components.

Now let’s discuss each component separately and see how many of them are combined to form this route-

The event-setup-checklist component contains semantic ui’s steps to maintain checklist of basic-details, sponsors, session & microlocation, call for speakers, session and speakers form customization so that it becomes easy to identify which step is complete and which is not.

Next is general-info component which shows basic information about an event like start-time, end-time, location, number of speakers, number of sponsors etc. It also shows whether the event is live or not.

In manage-roles component, manage the role for a given person, add people and assign different roles to them, edit roles for different people. Also we can see who are invited for a given role and who accepted them.

In event-sponsors component we manage the sponsors for the event, edit an existing sponsor, add a new sponsor with their logo, name, type and level. Also we can delete an existing sponsor.

Next is the ticket component which displays the details of number of orders, number of tickets sold, and total sales. Also it displays the number of types of tickets are sold.

Next is our app-component which has two choices. First is to generate android app for the event and second is to generate webapp of the event.

And finally in our view.index template, we add these components using ui stackable grid layout. Whenever we want to conditionally show or hide a component, we can do that in our event.index template and hence it becomes very easy to manage huge amounts content on a single page.

Resources

Continue ReadingAdding Event Overview Route in Open Event Frontend

Displaying Upcoming Sessions at a Microlocation Open Event Android

When I am attending a session in a room, I don’t get information on what is coming up.”

The issue that the user expressed was that he wanted to know what were the upcoming sessions at a microlocation. While I took up this issue in Open Event Android a few days back, I was thinking of ways about how this can be implemented. The app should be easy-to-use even for non-developers and thus, any new feature shouldn’t be too complex in its implementation. We decided upon doing the following:

  • Adding an “upcoming” option in the options menu of the Location activity.
  • This option’s purpose was to trigger the app to show information about the upcoming session in that microlocation.

Initial changes in LocationActivity.java

First of all, we added a new icon in the options menu of LocationActivity.java. One of the things that we learnt there was to use ifRoom|collapseActionView option for the app:showAsAction  

attribute as frequently as possible. This option ensures that the title in the option’s menu is visible at all times irrespective of the options being visible along with their icons.

So in case, the title is too big and there is very little room for the options to appear individually, then instead of squeezing down the title, the “ifRoom” attribute will collapse the option icons and insert a 3-dotted drop-down option list with all the options appearing in the drop-down.

Something like this:

The icon’s XML element and UI looked something like this:

<item
       android:id="@+id/upcoming_sessions"
       android:icon="@drawable/ic_timeline_white_24dp"
       android:title="@string/upcoming"
       app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="android.support.v7.widget.Button"/>

About the drawable icon that you see in the screenshot above, it was a tough find. Before I talk about how I came across this icon, I will talk about adding an icon in Android Studio.

How to add an icon in Android Studio?

Adding an item in Android studio means adding a drawable at a basic level. You can find all drawables under the app/src/main/res/drawable folder.

To add a new drawable, right-click on the drawable folder and go to new –>Vector asset. A window similar to what is shown below will appear.

Now, on selecting the “icon” option you will be taken to a huge list of icons that you can add in your app and then use them subsequently. But the problem here is that it is tough at times to find the icon that will be fit for your purpose. Like in my case, there was no direct icon for “upcoming”. This is when we had to do something more. We had to browse to this amazing site by Google: https://material.io/icons/ This site shows all the available icons in a much more interactive way and it was a lot more easier for me to come across the icon we wanted using this site.

The vector drawable file for the icon we chose looks like this:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
       android:width="24dp"
       android:height="24dp"
       android:viewportWidth="24.0"
       android:viewportHeight="24.0">
   <path
       android:fillColor="#FFFFFF"
       android:pathData="M23,8c0,1.1 -0.9,2 -2,2 -0.18,0 -0.35,-0.02 -0.51,-0.07l-3.56,3.55c0.05,0.16 0.07,0.34 0.07,0.52 0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-0.18 0.02,-0.36 0.07,-0.52l-2.55,-2.55c-0.16,0.05 -0.34,0.07 -0.52,0.07s-0.36,-0.02 -0.52,-0.07l-4.55,4.56c0.05,0.16 0.07,0.33 0.07,0.51 0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2 0.9,-2 2,-2c0.18,0 0.35,0.02 0.51,0.07l4.56,-4.55C8.02,9.36 8,9.18 8,9c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,0.18 -0.02,0.36 -0.07,0.52l2.55,2.55c0.16,-0.05 0.34,-0.07 0.52,-0.07s0.36,0.02 0.52,0.07l3.55,-3.56C19.02,8.35 19,8.18 19,8c0,-1.1 0.9,-2 2,-2s2,0.9 2,2z"/>
</vector>

What would the upcoming icon do?

Keeping in mind the necessity for the feature to be less complex, I decided that the upcoming icon will lead the user to a dialog box that shows the status of upcoming sessions in that micro location. The implementation for this feature involved 2 main things:

  1. Finding out the upcoming session from the list of sessions in the microlocation.
  2. Generate a dialog box that shows information about that session.

Finding position of upcoming session in Recycler View

Upcoming session will be a session whose starting time comes after the current time. So the approach was simple.

  1. Run a loop on a sorted list of all sessions in a microlocation.
  2. Find out every session’s start time.
  3. Compare the start time of every session with the current time.
  4. Find the first session whose start time comes after the current time.
  5. Store that session’s position, name, ID and other stuff like track name and track color.
  6. Break out of the loop.

This was the basic logic or algorithm, so to say. Here’s the implementation in the upcomingSession() function:

public void upcomingSession(){
   String upcomingTitle = "";
   String track = "";
   String color = null;
   Date current = new Date();
   for (Session sess:sortedSessions){
       try {
           Date start = DateUtils.getDate(sess.getStartsAt());
           if (start.after(current)){
               upcomingTitle = sess.getTitle();
               track = sess.getTrack().getName();
               color = sess.getTrack().getColor();
               break;
           }
       } catch (ParseException e) {
           e.printStackTrace();
       }
   }

Now, displaying a dialog box consisting of all the necessary information is an easy thing to do once you have the required information. So, I’ll just provide some code for it here without explaining much about it.

The initialisations:

public void upcomingSessionsInitial(){
   upcomingDialogBox = new Dialog(this);
           upcomingDialogBox.setContentView(R.layout.upcoming_dialogbox);
           trackImageIcon = (ImageView)upcomingDialogBox.findViewById(R.id.track_image_drawable);
           upcomingSessionText = (TextView) upcomingDialogBox.findViewById(R.id.upcoming_session_textview);
           upcomingSessionTitle = (TextView) upcomingDialogBox.findViewById(R.id.upcoming_Session_title);
           Button dialogButton = (Button) upcomingDialogBox.findViewById(R.id.upcoming_button);
           dialogButton.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View view) {
                   upcomingDialogBox.dismiss();
               }
           });
}

The calling:

switch (item.getItemId()){
       case R.id.action_map_location:
           FragmentManager fragmentManager = getSupportFragmentManager();
           FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

           Bundle bundle = new Bundle();
           bundle.putBoolean(ConstantStrings.IS_MAP_FRAGMENT_FROM_MAIN_ACTIVITY, false);
           bundle.putString(ConstantStrings.LOCATION_NAME, location);

           Fragment mapFragment = ((OpenEventApp)getApplication())
                   .getMapModuleFactory()
                   .provideMapModule()
                   .provideMapFragment();
           mapFragment.setArguments(bundle);
           fragmentTransaction.replace(R.id.content_frame_location, mapFragment, FRAGMENT_TAG_LOCATION).addToBackStack(null).commit();

           sessionRecyclerView.setVisibility(View.GONE);
           noSessionsView.setVisibility(View.GONE);
           menu.setGroupVisible(R.id.menu_group_location_activity, false);
           return true;
       case android.R.id.home:
           onBackPressed();
           getSupportFragmentManager().popBackStack();
           sessionRecyclerView.setVisibility(View.VISIBLE);
           return true;
       case R.id.upcoming_sessions:
           upcomingDialogBox.show();
           return true;
       default:
           return true;
   }
}

Final result:

This is the final result or solution that we generated for the issue that was addressed by one of the users:

Some useful links are:

Continue ReadingDisplaying Upcoming Sessions at a Microlocation Open Event Android

File Upload Validations on Open Event Frontend

In Open Event Frontend we have used semantics ui’s form validations to validate different fields of a form. There are certain instances in our app where the user has to upload a file and it is to be validated against the suggested format before uploading it to the server. Here we will discuss how to perform the validation.

Semantics ui allows us to validate by facilitating pass of an object along with rules for its validation. For fields like email and contact number we can pass type as email and number respectively but for validation of file we have to pass a regular expression with allowed extension.The following walks one through the process.

fields : {
  file: {
    identifier : 'file',
    rules      : [
      {
        type   : 'empty',
        prompt : this.l10n.t('Please upload a file')
      },
      {
        type   : 'regExp',
        value  : '/^(.*.((zip|xml|ical|ics|xcal)$))?[^.]*$/i',
        prompt : this.l10n.t('Please upload a file in suggested format')
      }
    ]
  }
}

Here we have passed file element (which is to be validated) inside our fields object identifier, which for this field is ‘file’, and can be identified by its id, name or data-validate property of the field element. After that we have passed an array of rules against which the field element is validated. First rule gives an error message in the prompt field in case of an empty field.

The next rule checks for allowed file extensions for the file. The type of the rule will be regExp as we are passing a regular expression which is as follows-

/^(.*.((zip|xml|ical|ics|xcal)$))?[^.]*$/i

It is little complex to explain it from the beginning so let us breakdown it from end-

 

$ Matches end of the string
[^.]* Negated set. Match any character not in this set. * represents 0 or more preceding token
( … )? Represents if there is something before (the last ?)
.*.((zip|xml|ical|ics|xcal)$) This is first capturing group ,it contains tocken which are combined to create a capture group ( zip|xml|ical|ics|xcal ) to extract a substring
^ the beginning of the string

Above regular expression filters all the files with zip/xml/ical/xcal extensions which are the allowed format for the event source file.

References

  • Ivaylo Gerchev blog on form validation in semantic ui
  • Drmsite blog on semantic ui form validation
  • Semantic ui form validation docs
  • Stackoverflow regex for file extension
Continue ReadingFile Upload Validations on Open Event Frontend

Adding JSONAPI Support in Open Event Android App

The Open Event API Server exposes a well documented JSONAPI compliant REST API that can be used in The Open Even App Generator and Frontend to access and manipulate data. So it is also needed to add support of JSONAPI in external services like The Open Even App Generator and Frontend. In this post I explain how to add JSONAPI support in Android.

There are many client libraries to implement JSONAPI support in Android or Java like moshi-jsonapi, morpheus etc. You can find the list here. The main problem is most of the libraries require to inherit attributes from Resource model but in the Open Event Android App we already inherit from a RealmObject class and in Java we can’t inherit from more than one model or class. So we will be using the jsonapi-converter library which uses annotation processing to add JSONAPI support.

1. Add dependency

In order to use jsonapi-converter in your app add following dependencies in your app module’s build.gradle file.

dependencies {
	compile 'com.github.jasminb:jsonapi-converter:0.7'
}

2.  Write model class

Models will be used to represent requests and responses. To support JSONAPI we need to take care of followings when writing the models.

  • Each model class must be annotated with com.github.jasminb.jsonapi.annotations.Type annotation
  • Each class must contain a String attribute annotated with com.github.jasminb.jsonapi.annotations.Id annotation
  • All relationships must be annotated with com.github.jasminb.jsonapi.annotations.Relationship annotation

In the Open Event Android we have so many models like event, session, track, microlocation, speaker etc. Here I am only defining track model because of its simplicity and less complexity.

@Type("track")
public class Track extends RealmObject {

        	@Id(IntegerIdHandler.class)
        	private int id;
        	private String name;
        	private String description;
        	private String color;
        	private String fontColor;
        	@Relationship("sessions")
        	private RealmList<Session> sessions;

        	//getters and setters
}

Jsonapi-converter uses Jackson for data parsing. To know how to use Jackson for parsing follow my previous blog.

Type annotation is used to instruct the serialization/deserialization library on how to process given model class. Each resource must have the id attribute. Id annotation is used to flag an attribute of a class as an id attribute. In above class the id attribute is int so we need to specify IntegerIdHandler class which is ResourceHandler in the annotation. Relationship annotation is used to designate other resource types as a relationship. The value in the Relationship annotation should be as per JSONAPI specification of the server. In the Open Event Project each track has the sessions so we need to add a Relationship annotation for it.

3.  Setup API service and retrofit

After defining models, define API service interface as you would usually do with standard JSON APIs.

public interface OpenEventAPI {
    @GET("tracks?include=sessions&fields[session]=title")
    Call<List<Track>> getTracks();
}

Now create an ObjectMapper & a retrofit object and initialize them.

ObjectMapper objectMapper = OpenEventApp.getObjectMapper();
Class[] classes = {Track.class, Session.class};

OpenEventAPI openEventAPI = new Retrofit.Builder()
                    .client(okHttpClient)
                    .baseUrl(Urls.BASE_URL)
                    .addConverterFactory(new JSONAPIConverterFactory(objectMapper, classes))
                    .build()
                    .create(OpenEventAPI.class);

 

The classes array instance contains a list of all the model classes which will be supported by this retrofit builder and API service. Here the main task is to add a JSONAPIConverterFactory which will be used to serialize and deserialize data according to JSONAPI specification. The JSONAPIConverterFactory constructor takes two parameters ObjectMapper and list of classes.

4.  Use API service  

Now after setting up all the things according to above steps, you can use the openEventAPI instance to fetch data from the server.

openEventAPI.getTracks();

Conclusion

JSON API is designed to minimize both the number of requests and the amount of data transmitted between clients and servers

Continue ReadingAdding JSONAPI Support in Open Event Android App

Keep updating Build status in Meilix Generator

One of the problems we faced while working Meilix Generator was to provide user with the status of the custom ISO build in the Meilix Generator web app so we came up with the idea of checking the status of the link generated by the web app. If the link is available the status code would be 200 otherwise it would be 404.

We have used python script for checking the status of URL. For generating URL, we use the tag name which will be used as a variable to generate the URL of the unique event user wants the ISO for and the date will help in generation of link rest of the link remains the same.

tag = os.environ["TRAVIS_TAG"]
date = datetime.datetime.now().strftime('%Y%m%d')
url=https://github.com/xeon-zolt/meilix/releases/download/"+tag+"/meilix-zesty-"+date+"-i386.iso"

 

Now we will use urllib for monitoring the status of link.

req = Request(url)
    try:
        response = urlopen(req)
    except HTTPError as e:
        return('Building Your Iso')
    except URLError as e:
        return('We failed to reach the server.')
    else:
        return('Build Sucessful : ' + url)

 

After monitoring the status the next step was to update the status dynamically on the status page.

So we’llll use a status function in the flask app which is used by JavaScript to get status of the link after intervals of time.

Flask :

@app.route('/now')
def status_url():
    return (status())

 

Javascript:

<script type ="text/javascript">
let url ="/now"
function getstatus(url)
{
    fetch(url).then(function(response){
        return response.text()
    }).then(function(text){
        console.log("status",text)
        document.querySelector("div#status")
        .innerHTML = text
    })
    }
window.onload = function(){
    fetch(url).then(function(response){
        return response.text()
    }).then(function(text){
        console.log("status",text)
        document.querySelector("div#status")
        .innerHTML = text
    })
    window.setInterval(getstatus.bind(null,url),30*1000)
}
/*setInterval(function,interval in millsecs)*/
</script>

 

This covers various steps to prompt user whether the build is ready or not.

Resource

Continue ReadingKeep updating Build status in Meilix Generator

Semantic-UI Validations for Forms in Open Event Frontend

Open Event Frontend requires forms at several places like at the time of login, for creation of events, taking the details of the user, creating discount codes for tickets etc.. Validations for these forms is a must, like in the above picture, we can see that many fields like discount code, amount etc. have been left empty, these null values when stored at backend can induce errors.

Semantic-UI makes our life easier and provides us with it’s own validations. Its form validation behavior checks data against a set of criteria or rules before passing it along to the server.

Let’s now dive deeper into Semantic validations, we’ll take discount code form as our example. The discount code form has many input fields and we have put checks at all of them, these checks are called rules here. We’ll discuss all the rules used in this form one by one

  1. Empty

Here we check if the input box with the identifier discount_amount is empty or not, if it is empty, a prompt is shown with the given message.

         identifier : ‘discount_amount’,
         rules      : [
           {
             type   : ’empty’,
             prompt : this.l10n.t(‘Please enter the discount amount’)
           }
         ]

2. Checked
Here, we validate whether the checkbox is checked or not and if it is not, show corresponding message

rules      : [
   {
     type   : ‘checked’,
     prompt : this.l10n.t(‘Please select the appropriate choices’)
   }]

3. RegExp

These checks are very important in input fields requiring passwords and codes, they specify the allowed input characters

rules      : [{
   type  : ‘regExp’,
   value : ‘^[a-zA-Z0-9_-]*$’
}]

4.Custom rules

Many a times, we require some rules which are by default not given by semantic, here we can create custom rules.

Like here, we want to check whether the user has not set max value lower than min.

$.fn.form.settings.rules.checkMaxMin = () => {
     if (this.get(‘data.minQuantity’) > this.get(‘data.maxQuantity’)) {
       return false;
     }
     return true;
   };

Here, we are creating our own custom rule checkMaxMin which returns boolean value depending upon minQuantity and maxQuantity. Now, this can be directly used as a rule

identifier : ‘min_order’,
optional   : true,
rules      : [
 {
  type   : ‘checkMaxMin’,
  prompt : this.l10n.t(‘Minimum value should not be greater than maximum’)
 }]

You can find the above code here

Additional Resources

Continue ReadingSemantic-UI Validations for Forms in Open Event Frontend

Managing Related Endpoints in Permission Manager of Open Event API Server

Open Event API Server has its permission manager to manage all permission to different endpoints and some of the left gaps were filled by new helper method has_access. The next challenge for permission manager was to incorporate a feature many related endpoints points to the same resource.
Example:

  • /users-events-roles/<int:users_events_role_id>/user or
  • /event-invoices/<int:event_invoice_id>/user

Both endpoints point to Users API where they are fetching the record of a single user and for this, we apply the permission “is_user_itself”. This permission ensures that the logged in user is the same user whose record is asked through the API and for this we need the “user_id” as the “id” in the permission function, “is_user_itself”
Thus there is need to add the ability in permission manager to fetch this user_id from different models for different endpoints. For example, if we consider above endpoints then we need the ability to get user_id from UsersEventsRole and EventInvoice models and pass it to permission function so that it can use it for the check.

Adding support

To add support for multiple keys, we have to look for two things.

  • fetch_key_url
  • model

These two are key attributes to add this feature, fetch_key_url will take the comma separated list which will be matched with view_kwargs and model receives the array of the Model Classes which will be used to fetch the related records from the model
This snippet provides the main logic for this:

for index, mod in enumerate(model):
   if is_multiple(fetch_key_url):
       f_url = fetch_key_url[index]
   else:
       f_url = fetch_key_url
   try:
       data = mod.query.filter(getattr(mod, fetch_key_model) == view_kwargs[f_url]).one()
   except NoResultFound, e:
       pass
   else:
       found = True

if not found:
   return NotFoundError({'source': ''}, 'Object not found.').respond()

From the above snippet we are:

  • We iterate through the models list
  • Check if fetch_key_url has multiple keys or not
  • Get the key from fetch_key_url on the basis of multiple keys or single key in it.
  • We try to attempt to get object from model for the respective iteration
  • If there is any record/object in the database then it’s our data. Skipping further process
  • Else continue iteration till we get the object or to the end.

To use multiple mode

Instead of providing the single model to the model option of permission manager, provide an array of models. Also, it is optional to provide comma separated values to fetch_key_url
Now there can be scenario where you want to fetch resource from database model using different keys present on your view_kwargs
for example, consider these endpoints

  1. `/notifications/<notification_id>/event`
  2. `/orders/<order_id>/event`

Since they point to same resource and if you want to ensure that logged in user is organizer then you can use these two things as:

  1. fetch_key_url=”notification_id, order_id”
  2. model=[Notification, Order]

Permission manager will always match indexes in both options, the first key of fetch_key_url will be only used for the first key of the model and so on.
Also, fetch_key_url is an optional parameter and even in multiple mode you can provide a single value as well.  But if you provide multiple commas separated values make sure you provide all values such that no of values in fetch_key_url and model must be equal.

Resources

Continue ReadingManaging Related Endpoints in Permission Manager of Open Event API Server

Custom Data Layer in Open Event API Server

Open Event API Server uses flask-rest-jsonapi module to implement JSON API. This module provides a good logical abstraction in the data layer.
The data layer is a CRUD interface between resource manager and data. It is a very flexible system to use any ORM or data storage. The default layer you get in flask-rest-jsonapi is the SQLAlchemy ORM Layer and API Server makes use of default alchemy layer almost everywhere except the case where I worked on email verification part.

To add support for adding user’s email verification in API Server, there was need to create an endpoint for POST /v1/users/<int:user_id>/verify
Clearly here we are working on a single resource i.e, specific user record. This requires us to use ResourceDetail and the only issue was there is no any POST method or view in ResourceDetail class. To solve this I created a custom data layer which enables me to redefine all methods and views by inheriting abstract class. A custom data layer must inherit from flask_rest_jsonapi.data_layers.base.Base.

Creating Custom Layer

To solve email verification process, a custom layer was created at app/api/data_layers/VerifyUserLayer.py

def create_object(self, data, view_kwargs):
   user = safe_query(self, User, 'id', view_kwargs['user_id'], 'user_id')
   s = get_serializer()
   try:
       data = s.loads(data['token'])
   except Exception:
       raise UnprocessableEntity({'source': 'token'}, "Invalid Token")

   if user.email == data[0]:
       user.is_verified = True
       save_to_db(user)
       return user
   else:
       raise UnprocessableEntity({'source': 'token'}, "Invalid Token")

Using custom layer in API

We can easily provide custom layer in API Resource using one of the properties of the Resource Class

data_layer = {
   'class': VerifyUserLayer,
   'session': db.session
}

This is all we have to provide in the custom layer, now all CRUD method will be directed to our custom data layer.

Solution to our issue
Setting up custom layer provides us the ability to create our custom resource methods, i.e, modifying the view for POST request and allowing us to verify the registered users in API Server.
On Setting up the data layer all I need to do is create a ResourceList with using this layer and with permissions

class VerifyUser(ResourceList):

   methods = ['POST', ]
   decorators = (jwt_required,)
   schema = VerifyUserSchema
   data_layer = {
       'class': VerifyUserLayer,
       'session': db.session
   }

This enables me to use the custom layer, VerifyUserLayer for ResourceList resource.

Resources

Continue ReadingCustom Data Layer in Open Event API Server