Shopping List: my way to learn stuff

Shopping List: my way to learn stuff

Shopping list

Hi devs, I want to show you a simple app I have create for playing with Ionic 5 and Java Spring.

Let me introduce you to Shopping List

Shopping List is a simple app for manage my stuff to buy from grocery to clothes and other stuff when I go out.

But why create your own app when there are plenty of them? Just for learn some stuff, get my hands dirty and stress a bit my Raspberry :D

Let's take a look to a few key elements for the app and the server.

APP

The app is created with Ionic 5 and Angular 10.

The idea, behind this project, was to write some clean code and play a bit with RxJS operators, interceptors and guards.


The HTTP interceptor is necessary because I have to inject the OAUTH2 Token for all the request to the server.

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        const currentUser = this.authService.currentUserValue;

        if (currentUser != null) {
            const token: string = currentUser.access_token;
            if (token) {
                request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) });
            }
        }

        if (!request.headers.has('Content-Type')) {
            request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
        }

        request = request.clone({ headers: request.headers.set('Accept', 'application/json') });

        return next.handle(request).pipe(
            map((event: HttpEvent<any>) => {
                return event;
            }),
            catchError((error: HttpErrorResponse) => {
                // TODO: Add 401 Handler
                return throwError(error);
            })
        );
    }


I used the RxJS operators for manipulate all the Observables used for calling the server.

I really used and loved a lot the catchError for handling server error response and throw them were I have subscribed to this observables.

Here is an example about an Observable I have created for retrieve the full list of shops.

    getShops(): Observable<IShopsResponse> {

    return this.http.get<IShopsResponse>(this.url + 'getAll').pipe((
      catchError((error: HttpErrorResponse) => {
        let err: IShopsResponse = null;
        err.status = error.status;
        err.message = error.error;
        return throwError(err);
      })
    ))
  }

Here is where I subscribe to the Observable and how I manage the error, and what to show to the user.

getAllShops() {
    this.shopService.getShops().subscribe(
      (response: IShopsResponse) => {
        this.list = response.data;
      },
      (error) => {
        let title = this.langService.getString('ALERT.error');
        let msg = this.langService.getString('ERROR.retrieve_list');
        this.alertService.showAlert(title, msg).then(
          (alertBox) => { alertBox.present(); }
        );
      }
    );
  }

Remember to take a look, if you haven't at RxJS documentation

SERVER

The server is created using Spring Boot framework.

The basic idea was to create a simple server with a basic authentication for the REST using OAuth2 following this tutorial.

After following this tutorial I decided to take a look at Spring Boot potentials and I find out the possibility of retrieve user informations without doing a ping-pong with ids and other private informations using SecurityContextHolder.

Here is a snipping of the function for the current user:

public User getCurrentUser(){
  Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  if ((authentication instanceof AnonymousAuthenticationToken)) {
    return null;
  }

  String email = authentication.getName();
  Optional<User> optUser = userRepo.findByEmail(email);
  if(optUser.isEmpty()){
      return null;
  }

  return optUser.get();
}

Another element that I decide to add was a logger for request and response, a very usefull way to analyze future server errors. For achieve this I had to create a specific class that extends HandlerInterceptorAdapter and with the some extra manipulation I was able to get my logs.

Here are the methods I created for the logs:

public void writeRequestPayloadAudit(ResettableStreamHttpServletRequest wrappedRequest) {
  try {
    String requestHeaders = getRawHeaders(wrappedRequest);
    String requestBody = org.apache.commons.io.IOUtils.toString(wrappedRequest.getReader());

    LOGGER.info("=================================== Request Start ===================================");
    LOGGER.info("Request Method: " + wrappedRequest.getMethod());
    LOGGER.info("Request URL: " + wrappedRequest.getRequestURI());
    LOGGER.info("Request Headers:" + requestHeaders.replace("\n", ","));
    LOGGER.info("Request Body:" + requestBody.replace("\n", ""));
    LOGGER.info("==================================== Request End ====================================");

  } catch (Exception e) {
    LOGGER.error("Exception Request" + e.getMessage());
  }
}

public void writeResponsePayloadAudit(ResettableStreamHttpServletResponse wrappedResponse) {
  String rawHeaders = getRawHeaders(wrappedResponse);

  LOGGER.info("=================================== Response Start ===================================");
  LOGGER.info("Response Status: " + wrappedResponse.getStatus());
  LOGGER.info("Response Headers:" + rawHeaders.replace("\n", ","));

  byte[] data = new byte[wrappedResponse.rawData.size()];
  for (int i = 0; i < data.length; i++) {
          data[i] = (byte) wrappedResponse.rawData.get(i);
  }

  String responseBody = new String(data);
  LOGGER.info("Response body:" + responseBody);
  LOGGER.info("==================================== Response End ====================================");
}

And here is how my logs looks like:

2020-10-30 18:32:35,583 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] =================================== Request Start ===================================
2020-10-30 18:32:35,584 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] Request Method: GET
2020-10-30 18:32:35,585 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] Request Headers:content-type:application/json,authorization:Bearer 6de79b7b-03bd-4e05-a8f8-af7f618d2fbc,user-agent:PostmanRuntime/7.26.5,accept:*/*,postman-token:18287157-4a9d-483f-9031-62cc2b3aa5dd,host:localhost:8080,accept-encoding:gzip, deflate, br,connection:keep-alive,
2020-10-30 18:32:35,585 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] Request body:
2020-10-30 18:32:35,586 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] ==================================== Request End ====================================
2020-10-30 18:32:35,647 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] =================================== Response Start ===================================
2020-10-30 18:32:35,648 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] Response Status: 200
2020-10-30 18:32:35,648 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] Response Headers:Vary:Origin,Vary:Origin,Vary:Origin,
2020-10-30 18:32:35,649 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] Response body:{"status":200,"message":"","data":[[{"id":1,"name":"Supermercato 1","createdAt":"2020-10-29T22:44:33","updatedAt":"2020-10-29T22:44:33","deleted":0,"total":0,"bought":0,"items":[]}],[{"id":2,"name":"Supermercato 2","createdAt":"2020-10-29T22:44:41","updatedAt":"2020-10-29T22:44:41","deleted":0,"total":0,"bought":0,"items":[]}],[{"id":13,"name":"Supermercato 3","createdAt":"2020-10-29T22:49:06","updatedAt":"2020-10-29T22:49:06","deleted":0,"total":0,"bought":0,"items":[]}]]}
2020-10-30 18:32:35,649 INFO com.bebetos.shoppinglist.interceptors.LoggerInterceptor [http-nio-8080-exec-3] ==================================== Response End ====================================

For extra code informations here is the link to the repository.