Fetching Info of All Users and their connected devices for the SUSI.AI Admin Panel

Fetching the data of all users is required for displaying the list of users on the SUSI.AI Admin panel. It was also required to fetch the information of connected devices of the user along with the other user data. The right to fetch the data of all users should only be permitted to user roles “OPERATOR” and above. This blog post explains how the data of connected devices of all users is fetched, which can then be used in the Admin panel. How is user data stored on the server? All the personal accounting information of any user is stored in the user’s accounting object. This is stored in the “accounting.json” file. The structure of this file is as follows: { "email:akjn11@gmail.com": { "devices": { "8C-39-45-23-D8-95": { "name": "Device 2", "room": "Room 2", "geolocation": { "latitude": "54.34567", "longitude": "64.34567" } } }, "lastLoginIP": "127.0.0.1" }, "email:akjn22@gmail.com": { "devices": { "1C-29-46-24-D3-55": { "name": "Device 2", "room": "Room 2", "geolocation": { "latitude": "54.34567", "longitude": "64.34567" } } }, "lastLoginIP": "127.0.0.1" } }   As can be seen from the above sample content of the “accounting.json” file, we need to fetch this data so that it can then be used to display the list of users along with their connected devices on the Admin panel. Implementing API to fetch user data and their connected devices The endpoint of the servlet is “/aaa/getUsers.json” and the minimum user role for this servlet is “OPERATOR”. This is implemented as follows: @Override public String getAPIPath() { return "/aaa/getUsers.json"; } @Override public UserRole getMinimalUserRole() { return UserRole.OPERATOR; }   Let us go over the main method serviceImpl() of the servlet: We need to traverse through the user data of all authorized users. This is done by getting the data using DAO.getAuthorizedClients() and storing them in a Collection. Then we extract all the keys from this collection, which is then used to traverse into the Collection and fetch the user data. The implementation is as follows: Collection<ClientIdentity> authorized = DAO.getAuthorizedClients(); List<String> keysList = new ArrayList<String>(); authorized.forEach(client -> keysList.add(client.toString())); for (Client client : authorized) { // code }   Then we traverse through each client and generate a client identity to get the user role of the client. This is done using the DAO.getAuthorization() method. The user role of the client is also put in the final object which we want to return. This is implemented as follows: JSONObject json = client.toJSON(); ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, client.getName()); Authorization authorization = DAO.getAuthorization(identity); UserRole userRole = authorization.getUserRole(); json.put("userRole", userRole.toString().toLowerCase());   Then the client credentials are generated and it is checked whether the user is verified or not. If the user is verified, then in the final object, “confirmed” is set to true, else it is set to false. ClientCredential clientCredential = new ClientCredential (ClientCredential.Type.passwd_login, identity.getName()); Authentication authentication = DAO.getAuthentication(clientCredential); json.put("confirmed", authentication.getBoolean("activated", false));   Then we fetch the accounting object of the user using DAO.getAccounting(), and extract all the user data and put them in separate key value pairs in the final…

Continue ReadingFetching Info of All Users and their connected devices for the SUSI.AI Admin Panel

Adding Push endpoint to send data from Loklak Search to Loklak Server

To provide enriched and sufficient amount of data to Loklak, Loklak Server should have multiple sources of data. The api/push.json endpoint of loklak server is used in Loklak to post the search result object to server. It will increase the amount and quality of data on server once the Twitter api is supported by Loklak (Work is in progress to add support for twitter api in loklak). Creating Push Service The idea is to create a separate service for making a Post request to server. First step would be to create a new ‘PushService’ under ‘services/’ using: ng g service services/push Creating model for Push Api Response Before starting to write code for push service, create a new model for the type of response data obtained from Post request to ‘api/push.json’. For this, create a new file push.ts under ‘models/’ with the code given below and export the respective push interface method in index file. export interface PushApiResponse { status: string; records: number; mps: number; message: string; } Writing Post request in Push Service Next step would be to create a Post request to api/push.json using HttpClient module. Import necessary dependencies and create an object of HttpClient module in constructor and write a PostData() method which would take the data to be send, makes a Post request and returns the Observable of PushApiResponse created above. import { Injectable } from ‘@angular/core’; import { HttpClient, HttpHeaders, HttpParams } from ‘@angular/common/http’; import { Observable } from ‘rxjs’; import { ApiResponse, PushApiResponse } from ‘../models’; @Injectable({ providedIn: ‘root’ }) export class PushService { constructor( private http: HttpClient ) { } public postData(data: ApiResponse): Observable<PushApiResponse> { const httpUrl = ‘https://api.loklak.org/ api/push.json’; const headers = new HttpHeaders({ ‘Content-Type’: ‘application/ x-www-form-urlencoded’, ‘Accept’: ‘application/json’, ‘cache-control’: ‘no-cache’ }); const {search_metadata, statuses} = data; // Converting the object to JSON string. const dataToSend = JSON.stringify({ search_metadata: search_metadata, statuses}); // Setting the data to send in // HttpParams() with key as ‘data’ const body = new HttpParams() .set(‘data’, dataToSend); // Making a Post request to api/push.json // endpoint. Response Object is converted // to PushApiResponse type. return this.http.post<PushApiResponse>( httpUrl, body, {headers: headers }); } }   Note: Data (dataToSend) send to backend should be exactly in same format as obtained from server. Pushing data into service dynamically Now the main part is to provide the data to be send into the service. To make it dynamic, import the Push Service in ‘api-search.effects.ts’ file under effects and create the object of Push Service in its constructor. import { PushService } from ‘../services’; constructor( … private pushService: PushService ) { }   Now, call the pushService object inside ‘relocateAfterSearchSuccess$’ effect method and pass the search response data (payload value of search success action) inside Push Service’s postData() method. @Effect() relocateAfterSearchSuccess$: Observable<Action> = this.actions$ .pipe( ofType( apiAction.ActionTypes .SEARCH_COMPLETE_SUCCESS, apiAction.ActionTypes .SEARCH_COMPLETE_FAIL ), withLatestFrom(this.store$), map(([action, state]) => { this.pushService .postData(action[‘payload’]); … ); Testing Successful Push to Backend To test the success of Post request, subscribe to the response data and print the response data on…

Continue ReadingAdding Push endpoint to send data from Loklak Search to Loklak Server

Encoding and Decoding Images as Data in UserDefaults in SUSI iOS

In this blog post, I will be explaining how to encode and decode images and save them in UserDefaults so that the image persists even if it is removed from the Photos app. It happens a number of times that images are removed from the gallery by the users which results in the app loosing the image. So, to avoid this, we save the image by encoding it in a data object and save it inside UserDefaults. In SUSI iOS app we simply select an image from the image picker, encode it and save it in UserDefaults. To set the image, we simply fetch the image data from the UserDefaults and decode it to an image. There are two ways we can do the encoding and decoding process: Using Data object Using Base64 string For the scope of this tutorial, we will use the Data object. Implementation Steps To use the image picker, we need to add permissions to `Info.plist` file. <key>NSLocationWhenInUseUsageDescription</key> <string>Susi is requesting to get your current location</string> <key>NSPhotoLibraryUsageDescription</key> <string>Susi needs to request your gallery access to select wallpaper</string> Select image from gallery First, we present an alert which gives an option to select the image from the gallery. // Show wallpaper options to set wallpaper or clear wallpaper func showWallpaperOptions() { let imageDialog = UIAlertController(title: ControllerConstants.wallpaperOptionsTitle, message: nil, preferredStyle: UIAlertControllerStyle.alert) imageDialog.addAction(UIAlertAction(title: ControllerConstants.wallpaperOptionsPickAction, style: .default, handler: { (_: UIAlertAction!) in imageDialog.dismiss(animated: true, completion: nil) self.showImagePicker() })) imageDialog.addAction(UIAlertAction(title: ControllerConstants.wallpaperOptionsNoWallpaperAction, style: .default, handler: { (_: UIAlertAction!) in imageDialog.dismiss(animated: true, completion: nil) self.removeWallpaperFromUserDefaults() })) imageDialog.addAction(UIAlertAction(title: ControllerConstants.dialogCancelAction, style: .cancel, handler: { (_: UIAlertAction!) in imageDialog.dismiss(animated: true, completion: nil) })) self.present(imageDialog, animated: true, completion: nil) } Here, we create and UIAlertController with three options to select, one which presents the image picker controller, the second one removes the background wallpaper and the third dismisses the alert. Set the image as background view // Callback when image is selected from gallery func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { dismiss(animated: true, completion: nil) let chosenImage = info[UIImagePickerControllerOriginalImage] as? UIImage if let image = chosenImage { setBackgroundImage(image: image) } } We use the `didFinishPickingMediaWithInfo` delegate method to set the image as background. First we get the image using the the `info` dictionary using the `UIImagePickerControllerOriginalImage` key. Save the image in UserDefaults (encoding) // Save image selected by user to user defaults func saveWallpaperInUserDefaults(image: UIImage!) {   let imageData = UIImageJPEGRepresentation(image!, 1.0)   let defaults = UserDefaults.standard   defaults.set(imageData, forKey: userDefaultsWallpaperKey) } We first convert the image to a data object using the `UIImageJPEGRepresentation` method followed by saving the data object in UserDefaults with the key `wallpaper`. Decode the data object back to UIImage  Now whenever we need to decode the image, we simply get the data object from the UserDefaults and use it to display the image. // Check if user defaults have an image data saved else return nil/Any func getWallpaperFromUserDefaults() -> Any? {   let defaults = UserDefaults.standard   return defaults.object(forKey: userDefaultsWallpaperKey) } Below is the output when an image is…

Continue ReadingEncoding and Decoding Images as Data in UserDefaults in SUSI iOS

Shrinking Model Classes Boilerplate in Open Event Android Projects Using Jackson and Lombok

JSON is the de facto standard format used for REST API communication, and for consuming any of such API on Android apps like Open Event Android Client and Organiser App, we need Plain Old Java Objects, or POJOs to map the JSON attributes to class properties. These are called models, for they model the API response or request. Basic structure of these models contain Private properties representing JSON attributes Getters and Setters for these properties used to change the object or access its data A toString() method which converts object state to a string, useful for logging and debugging purposes An equals and hashcode method if we want to compare two objects These can be easily and automatically be generated by any modern IDE, but add unnecessarily to the code base for a relatively simple model class, and are also difficult to maintain. If you add, remove, or rename a method, you have to change its getters/setters, toString and other standard data class methods. There are a couple of ways to handle it: Google’s Auto Value: Creates Immutable class builders and creators, with all standard methods. But, as it generates a new class for the model, you need to add a library to make it work with JSON parsers and Retrofit. Secondly, there is no way to change the object attributes as they are immutable. It is generally a good practice to make your models immutable, but if you are manipulating their data in your application and saving it in your database, it won’t be possible except than to create a copy of that object. Secondly, not all database storage libraries support it Kotlin’s Data Classes: Kotlin has a nice way to made models using data classes, but it has certain limitations too. Only parameters in primary constructor will be included in the data methods generated, and for creating a no argument constructor (required for certain database libraries and annotation processors), you either need to assign default value to each property or call the primary constructor filling in all the default values, which is a boilerplate of its own. Secondly, sometimes 3rd party libraries are needed to work correctly with data classes on some JSON parsing frameworks, and you probably don’t want to just include Kotlin in your project just for data classes Lombok: We’ll be talking about it later in this blog Immutables, Xtend Lang, etc This is one kind of boilerplate, and other kind is JSON property names. As we know Java uses camelcase notation for properties, and JSON attributes are mostly: Snake Cased: property_name Kebab Cased: property-name Whether you are using GSON, Jackson or any other JSON parsing framework, it works great for non ambiguous property names which match as same in both JSON and Java, but requires special naming attributes for translating JSON attributes to camelcase and vice versa. Won’t you want your parser to intelligently convert property_name to propertyName without having you write the tedious mapping which is not only a boilerplate, but also error…

Continue ReadingShrinking Model Classes Boilerplate in Open Event Android Projects Using Jackson and Lombok