Wednesday, August 12, 2020

Math Test: Simple Minus

 40 Simple Minus Questions


See the Pen Simple Minus by DevilKazuya99 (@devilkazuya99) on CodePen.

Math Test: Simple Addition

40 Simple Addition Questions

See the Pen Simple Plus by DevilKazuya99 (@devilkazuya99) on CodePen.

Tuesday, August 11, 2020

Math Test Generator 1

I created this snippet to generate math questions for my kids. Click the [Print] button to print the questions and answers. It should fit nicely in 2 A4 pages. Refresh the page to regenerate a new set of Q/A.


See the Pen More 2 Steps Plus Multiply by DevilKazuya99 (@devilkazuya99) on CodePen.

Thursday, April 2, 2020

Quarkus REST Service integrate with Firestore

Recently I got a request to access data from Google Cloud Firestore. so I'm gonna give Quarkus a try because it is so badass fast.

First I bootstrap my project according to the guides here.

mvn io.quarkus:quarkus-maven-plugin:1.3.0.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json-quickstart \
    -DclassName="org.acme.rest.json.FruitResource" \
    -Dpath="/fruits" \
    -Dextensions="resteasy-jackson"
cd rest-json-quickstart

Now I got my FruitResource.java, will start to make changes.


My plan is simple. I had 1 GET endpoint which will call a service function and fetch data from Firestore. Then 1 POST endpoint to call a service function to write data into Firestore.

Too easy, right? Normally to integrate with Firestore you just need to add the "firebase-admin" library into your maven pom.xml.
However, my goal, in the end, is to package the application into a native binary. Including an external library as above will make Graalvm complain. So I need to go for plan B --> Firestore REST API.
The design looks a bit weird but it is just for proof of concept, please bear with me.

Google Cloud Firestore itself already comes with REST endpoint. Read here for more info.

To access the REST endpoint, I need to generate and sign a JWT token to be used in the request header.

Quarkus guide saves the day again. See Quarkus - Using JWT RBAC for example. I uncluded quarkus-smallrye-jwt library into my project.


Then I copy the TokenUtils.java code into my project, made some small modifications in order to generate a JWT token that can be used by Cloud Firestore REST endpoint.

Looking back at Cloud Firestore REST API documentation, I am using Google Identity OAuth2 Token to authenticate my request. According to the Google OAuth2 guide, the token needs to look like this:


One more things need to do is go to GCP console > IAM > Service Accounts, create an account for firebase admin (it should exist if you are using Firestore), then create an API Key and download the private key as privateKey.pem. Then generate a publicKey.pem using openssh

  openssl rsa -in privateKey.pem -pubout -out publicKey.pem

Put both files in src/main/resources folder. When calling TokenUtils.generateTokenString(kid, timeClaims); it should give me a signed JWT token.


Now let's look at my service class.

in my firestore I had a collection "stores". So here is my REST client look like:

This code is so beautiful. I like it so much. RestTemplate in S*****g can go to the bin now.

Initially, I assigned the header at the class level with annotation buy it has a problem when compiling into native binary using GraalVM. Therefore I have to inject the token into the header every time I call the function (as function parameter). Calling the REST client will look like this:


To make it all works, I have these 3 files in src/main/resources folder.

-- application.properties

"firestore-api/mp-rest/url" is used as the base path for the REST client in FirestoreService.java.
"mp.jwt.verify.publickey.location" is needed for JWT. 
"quarkus.native.additional-build-args" is used for compiling to native binary. 

-- reflection-config.json (required for compiling to native binary)


-- resources-config.json (files from classpath to include when generating native binary)


Finally, run command
    ./mvnw package -Pnative

to generate the native binary. Here is the screen when running the binary. Please take note, the application starts up in 0.019s. I just broke my personal record of starting up a java web application.



Try it out and have fun.
You can see the code in Github.

Sunday, November 30, 2014

Alor Setar Cafe

Just want to keep track of these Cafe in Alor Setar. Wonder how long they will last...


  1. Cube Coffee 
  2. Coffeebooks & Rain 
  3. 1973 Espresso Cafe 
  4. Coffee Lips Cafe 
  5. Parrot Espresso Cafe 
  6. Coffee Diem
  7. Oh Bella Cafe
  8. Coffee D
  9. Bon Bon Ya 
  10. Coffee Lunatic 
  11. Mc Cafe 
  12. Habit Art Cafe 
  13. Bites Cafe & Galary
  14. Le' Medina Patisserie
  15. Secret Recipes 
  16. Coffee Lap
  17. Green Leaf 
  18. Uncle Jonh
  19. Georgetown Cafe 
  20. Oldtown Cafe 
  21. Coffebean ( Coming Soon )
  22. Starbucks ( Coming Soon )
  23. Deco Cafe ( Coming Soon )
  24. Coffee Tiffz Gallery ( Coming Soon )
  25. Paper Plane Cafe ( Coming Soon )
  26. The Hat Cabin ( Coming Soon )
  27. Coffee Lips ( Comimg Soon )

Thursday, November 13, 2014

What did "cordova create" do?

Just for curiosity, I ran this command to find out.

$ cordova create myapp1 com.otccomputing.myapp1 "My App 1"
Creating a new cordova project with name "My App 1" and id "com.otccomputing.myapp1" at location "/Users/guest/apps/myapp1"

And here we go.  I expand all the sub folder in myapp1.


Now let's take a look at those files.




config.xml, used by cordova to do compile/build/plugin whatever task. Without it, it is just not a cordova project anymore.
The content in config.xml is simple and easy to digest. 
id = a unique identity used to identify your app
version = as named. etc. 1.0.0, 2.0.0, 3.0.4...
description: write anything you want
author: change it to your detail - email, website
content: which file cordova will start with - don't change this one.

We now look at index.html

The most important 2 lines are:

cordova.js MUST be there. Otherwise your apps just won't work. I don't know what black magic behind it but - leave it there.
index.js is not that important, your apps may still work if it doesn't exist but lets have a look.

The main thing here is event listening. index.js listen to 'deviceready' event, which is fired by cordova after some backend initialization is ready. You might consider trigger some function in your app AFTER 'deviceready' is triggered or they may not work. Most plugin in GitHub will mention this requirement in their documentation. 
the receivedEvent() implementation can be ignored or you can put in your own login here.

So for development strategy, I usually work on my HTML, JS,CSS in an IDE, test them on the browser, looks good, then move then into the www folder and do further integration. 

Bottom line, as long as config.xml is there, cordova.js in included in your index.html, things will work. 





Tuesday, October 14, 2014

Slim3 REST Controller Example

I was playing with AngularJS and slim3, then bum into REST. So here is an example for sharing.

At the client side I have angular code calling PUT:
$http.put('organization', {name: $scope.name}).success(
 function(data){
  alert('Success');
 });
the GET, POST and DELETE will be more or less the same. $http.get(), $http.post(), $http.delete().

One thing I found interesting about slim3 is the URL rewrite with AppRouter.java. Here is what I do (with evil smile):
addRouting(
 "/secured/{entity}",
 "/secured/rest?entity={entity}");
addRouting(
 "/secured/{entity}/{resourceId}",
 "/secured/rest?entity={entity}&resourceId={resourceId}");

I route both URL to the same Controller. So here is my Controller. My root package is "smis". (I know, I know...)


package smis.controller.secured;

import java.io.BufferedReader;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.log4j.Logger;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.datastore.Datastore;
import org.slim3.datastore.ModelMeta;

import com.google.appengine.api.datastore.Key;

public class RestController extends Controller {

 private Logger logger = Logger.getLogger(getClass());

 @Override
 public Navigation run() throws Exception {
  String entityName = asString("entity");
  String resourceId = asString("resourceId");

  // hack class name. :p
entityName = entityName.substring(0, 1).toUpperCase()
    + entityName.substring(1);

  Class entityClass = Class.forName("smis.model." + entityName);
  Class modelMetaClass = Class.forName("smis.meta." + entityName
    + "Meta");
  Method method = modelMetaClass.getMethod("get");
  Object modelMetaInstance = method.invoke(null);

  logger.debug(entityClass);
  logger.debug(modelMetaClass);
  logger.debug(modelMetaInstance);

  logger.debug("Method = " + request.getMethod());

  if (isGet()) {
   if (resourceId == null) {
    return handleList((ModelMeta) modelMetaInstance);
   } else {
    return handleDetail(entityClass,
      (ModelMeta) modelMetaInstance);
   }
  } else if (isPost()) {
   return handleCreate((ModelMeta) modelMetaInstance);
  } else if (isPut()) {
   return handleCreate((ModelMeta) modelMetaInstance);
  } else if (isDelete()) {
   return handleDelete(entityClass);
  }

  return null;
 }

 private Navigation handleDelete(Class entityClass) throws Exception {
  Key key = asKey("resourceId");
  Datastore.delete(key);
  Object t = Datastore.get(entityClass, key);
  logger.debug(t == null);
  response.setContentType("text/plain");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(t == null);
  response.flushBuffer();
  return null;
 }

 private Navigation handleCreate(ModelMeta modelMeta) throws Exception {
  StringBuffer jb = new StringBuffer();
  String line = null;
  try {
   BufferedReader reader = request.getReader();
   while ((line = reader.readLine()) != null)
    jb.append(line);
  } catch (Exception e) { /* report an error */
   e.printStackTrace();
  }

  String json = jb.toString();
  logger.debug(json);
  Object t = modelMeta.jsonToModel(json);
  boolean isComplete = Datastore.put(t).isComplete();
  logger.debug(isComplete);
  response.setContentType("text/plain");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(isComplete);
  response.flushBuffer();
  return null;
 }

 private Navigation handleDetail(Class entityClass, ModelMeta modelMeta)
   throws Exception {
  Key key = asKey("resourceId");
  logger.debug(key);
  Object t = Datastore.get(entityClass, key);
  String json = modelMeta.modelToJson(t);
  logger.debug(json);
  response.setContentType("application/json");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(json);
  response.flushBuffer();
  return null;
 }

 private Navigation handleList(ModelMeta modelMeta) throws Exception {
  List list = Datastore.query(modelMeta).asList();
  String json = modelMeta.modelsToJson(list);
  logger.debug(json);
  response.setContentType("application/json");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(json);
  response.flushBuffer();
  return null;
 }
}
Because my lazy nature, I use reflection to create the Class for my entity and also the instance of the Meta class. Thanks to how slim3 name the model and meta class. There is a high security risk here! Not recommended for production, OK?

Then based on the isXXX() functions I call the handleXXXXX() accordingly. The implementation explains itself.

Some more interesting would be the asKey("my_key"), it does the same thing like:
 request.getParameter("my_key") + parse it into a Key.
Very handy.

Last thing to point out is how to digest the request data:

StringBuffer jb = new StringBuffer();
String line = null;
try {
 BufferedReader reader = request.getReader();
 while ((line = reader.readLine()) != null)
  jb.append(line);
} catch (Exception e) { /* report an error */
 e.printStackTrace();
}

The experiment was fun. I enjoy writing and playing with it. Hope it is the same for you too.