diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2af7cefb --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..9cc84ea9 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..c3150437 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/.tern-project b/.tern-project new file mode 100644 index 00000000..f030bd16 --- /dev/null +++ b/.tern-project @@ -0,0 +1,17 @@ +{ + "plugins": { + "guess-types": { + + }, + "outline": { + + }, + "angular": { + + } + }, + "libs": [ + "ecma5", + "browser" + ] +} \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 40b752b3..00000000 --- a/README.en.md +++ /dev/null @@ -1,74 +0,0 @@ -# Backend Code Challenge - -Code challenge designed to evaluate technical knowledge of **Backend** candidates. - -## Content -- [The Challenge](#the-challenge) - - [Scenario](#scenario) - - [APIs](#apis) - - [Features](#features) - - [To Take in Consideration](#to-take-in-consideration) -- [What We Would Like To See](#what-we-would-like-to-see) - - [Nice To Have](#nice-to-have) - - [Technology Stack](#technology-stack) -- [Submission Guidelines](#submission-guidelines) -- [Attention](#attention) - -## The Challenge - -### Scenario -You're part of a team that develops a mobile e-commerce for a huge company. -This project will target **powerful devices** and also devices with **low memory and process capabilities**. -The customer already has a established backend containing all business rules and information but, some systems rely on **legacy technologies** and may be **unstable** too. -**You** are responsible to build the **API** that will integrate with these services and provide all information needed by Mobile. -For the next interactions, the team will work on the **Home Screen** -and you must build all required **endpoint(s)** that will integrate with these services and provide the information needed by this screen. - -### APIs -You will need to integrate with the following APIs: -- **Categories**: [https://cs-hsa-api-categories.herokuapp.com/docs](https://cs-hsa-api-categories.herokuapp.com/docs). -- **Coupons**: [https://cs-hsa-api-coupons.herokuapp.com/docs](https://cs-hsa-api-coupons.herokuapp.com/docs) - -### Features -Mobile needs to build a home screen that will show: - -- Carousel with **Top 5** categories; -- Carousel with coupons that the expiration date is still valid; -- Grid with all **remaining** categories; -- Mobile does not need all images from categories model, but only the **icon** and the **smaller image**. You may need to filter some fields in your response, if you like. - -### To Take in Consideration -- Coupons API is **unstable** and sometimes takes too long to respond; -- Categories API returns a model that seems hard to handle. - -## What We Would Like To See -You are free to implement this solution the way you like taking in consideration only -the scenario, project target, performance implications and proposed technology stack described below. -- We are very focused in **quality** in our projects and we would like to a test strategy applied. You can use test type like, for example **Unit Tests**; -- The services are unstable and we don't want the user experience degrading because an API takes too long to respond; -- **1 week to complete the challenge**. If you need more time, no problem, talk to us and we will see what you can do :) - -### Nice To Have -- An API documentation would be good. Suggestion: [Swagger](https://swagger.io/); -- Running with [Docker](https://www.docker.com/). - -### Technology Stack -- **Java 8** or **newer** versions; -- Any Web Framework. Suggestion: [Spring Boot](https://spring.io/projects/spring-boot); -- Any API Client. Suggestion: [Feign](https://github.com/OpenFeign/feign); -- Preferable [Gradle](https://gradle.org/) as the build system; -- You are free to choose the test libraries and frameworks you like. The stack we suggest is: [JUnit](https://junit.org/junit5/), [Mockito](https://site.mockito.org/). - ---- - -## Submission Guidelines -Follow the steps below to implement and submit this code challenge: -- Fork this repository. Check this guide for how to fork on Github: [How to Fork a repository](https://help.github.com/en/articles/fork-a-repo); -- Implement the proposed challenge; -- After completing the challenge, open a **Pull Request** to this repository, using **Github** interface. [Check this guide for more details](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork); -- If possible, let your repository public to make code review easier :) - -## ATTENTION -Do **NOT** try to PUSH direct to THIS repository! - ---- diff --git a/README.md b/README.md index b10df83c..0df4a98a 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,39 @@ - - # Desafio Java Concrete -Desafio técnico para evaluar el conocimiento de los aspirantes a unirse al equipo de **Backend**. +Desafio técnico desarrollado por **José Miguel Lizárraga**.
+joselizarraga.m@gmail.com -## Contenido -- [El Desafio](#el-desafio) - - [Escenario](#escenario) - - [APIs](#apis) - - [Features](#features) - - [Puntos a Considerar](#puntos-a-considerar) -- [¿Qué nos Gustaría Ver?](#qué-nos-gustaría-ver) - - [Deseables](#deseables) - - [Stack/Pool de Tecnologías](#stackpool-de-tecnologías) -- [Forma de Entrega](#forma-de-entrega) -- [Importante](#importante) +# Consideraciones -## El Desafio +Por defecto el puerto utilizado es el 8080. Se cambia en el archivo application.properties

+server.port=8999 +

+Los endpoints para llamar a las apis se encuentran en este archivo también. -### Escenario -Recientemente te has unido a un equipo que esta desarrollando una aplicación mobile para una gran compañía de e-commerce. -Esta aplicación esta dirigida a dispositivos de **bajo rendimiento** hasta dispositivos con **gran poder de procesamiento**. -El cliente ya cuenta con un backend establecido que contiene todas las reglas e información de negocio, pero algunos sistemas dependen de **tecnologías legadas** que pueden ser **inestables**. -**Tu** eres responsable de construir la **API** que integrará con estos servicios y proveerá todas las informaciones que Mobile necesita. -Para las próximas iteraciones, el equipo va a trabajar en la **Pantalla de Início** y tu debes construir todo(s) los **endpoint(s)** que se requieran para integrar con los servicios y proveer la información para la pantalla. +## Probar la aplicación con Swagger -### APIs -Necesitarás consumir los siguientes servicios: -- **Categorias**: [https://cs-hsa-api-categories.herokuapp.com/docs](https://cs-hsa-api-categories.herokuapp.com/docs) -- **Cupones**: [https://cs-hsa-api-coupons.herokuapp.com/docs](https://cs-hsa-api-coupons.herokuapp.com/docs) +**Top 5 categorias**
+http://localhost:8080/swagger-ui.html#/categoria-controller +
-### Features -El equipo Mobile necesita construir una pantalla de inicio que debe mostrar: +**Cupones que no han expirado**
+http://localhost:8080/swagger-ui.html#/cupon-controller -- Carrusel con **Top 5** categorias. -- Carrusel con cupones que no han expirado. -- Tabla (Grid) con las categorias **restantes**. -- Mobile **no** necesita de todas las imagenes del modelo de subcategorías, solamente la **imagen más pequeña**. Tu podrías tener que filtrar la respuesta, si tu quieres. +## Endpoints utilizados -### Puntos a Considerar -- La API de Cupones es **inestable** y a veces demora en responder. ¿Qué podemos hacer para que este problema no se replique en otras capas? ¿Habrá algún patrón que nos ayude? -- La API de Categorias responde un modelo complejo de manejar. ¿Qué podríamos hacer para simplificar este modelo y que las integraciones con su aplicación sean más sencillas? +http://localhost:8080/api/categoria/mobile/getCarrusel +
+http://localhost:8080/api/cupon/mobile/getCarrusel -## ¿Qué nos Gustaría Ver? -Eres libre para implementar la solución de la forma que consideres mejor, -pero debes considerar el escenario, objetivo, implicaciones de performance y stack/pool de tecnología propuesta abajo. -- Nos enfocamos mucho en la **calidad** de nuestros proyectos y nos gustaría ver alguna estrategia aplicada. Puedes usar, por ejemplo, **Pruebas Unitárias**. -- No queremos que la experiencia de usuario se vea afectada por el rendimiento de los servicios; -- **Usted tiene 1 semana para completar el desafio**. Si necesitas mas tiempo, no hay problema, puede hablar con nosotros y veremos que podemos hacer :) -- Documentación de como configurar y ejecutar el proyecto. Puede sobrescribir el **README.md** para eso. +## Pruebas unitarias -### Deseables -- Podría ser bueno una documentación de API. Sugerencia: [Swagger](https://swagger.io/). -- Ejecutando con [Docker](https://www.docker.com/). +Click derecho sobre la clase de pruebas -> run as -> junit test -### Stack/Pool de Tecnologías -- **Java 8** o **superiores**. -- Cualquier Framework Web. Sugerencia: [Spring Boot](https://spring.io/projects/spring-boot). -- Cualquier API Client. Sugerencia: [Feign](https://github.com/OpenFeign/feign). -- De preferencia [Gradle](https://gradle.org/) como sistema de compilación. -- Tu eres libre para escoger librerias y frameworks de pruebas que mas te guste. Nuestra sugerencia es: [JUnit](https://junit.org/junit5/), [Mockito](https://site.mockito.org/). +## Tecnologías ---- +Maven, Spring Boot, Jersey, Swagger, JUnit, Jackson -## Forma de Entrega -Siga los siguientes pasos para implementar y enviar este desafío: -- Haga un **Fork** a este repositorio. Puedes mirar esta guía para mayores informaciones: [Como hacer fork de un repositorio](https://help.github.com/en/articles/fork-a-repo). -- Implemente el desafío. -- Después de completar el desafío, realice un **Pull Request** a este repositorio, utilizando la interface de **Github**. [Creando un Pull Request](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork). -- Si es posible, deja tu repositorio publico para hacer la revisión de código más sencilla. -## Importante -**No** intente hacer un PUSH directo a ESTE repositorio! +## Patrones de diseño utilizados ---- +Patrón Singleton en el servicio de cupones para una carga inicial durante el arranque del proyecto diff --git a/mvnw b/mvnw new file mode 100644 index 00000000..5bf251c0 --- /dev/null +++ b/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 00000000..019bd74d --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..77586a27 --- /dev/null +++ b/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + com.proyecto + Postulacion + 0.0.1-SNAPSHOT + jar + + Postulacion + Postular a trabajo + + + org.springframework.boot + spring-boot-starter-parent + 2.0.1.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + com.proyecto.Inicio + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.sun.jersey + jersey-client + 1.19.4 + + + org.glassfish.jersey.media + jersey-media-json-jackson + 2.22 + + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone + + false + + + + diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000..254272e1 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/src/main/java/com/proyecto/Inicio.java b/src/main/java/com/proyecto/Inicio.java new file mode 100644 index 00000000..784defb5 --- /dev/null +++ b/src/main/java/com/proyecto/Inicio.java @@ -0,0 +1,54 @@ +package com.proyecto; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +import com.proyecto.service.ICuponService; +import com.proyecto.service.impl.CuponServiceImpl; + +@SpringBootApplication +public class Inicio +{ + private static final Logger log = LoggerFactory.getLogger(Inicio.class); + + public static void main(String[] args) throws InterruptedException, ExecutionException + { + log.info("Inicio Aplicación"); + + CompletableFuture cargarCupones = CompletableFuture.supplyAsync(() -> { + + ApplicationContext contexto = SpringApplication.run(Inicio.class, args); + ICuponService servicioCupon = contexto.getBean(CuponServiceImpl.class); + servicioCupon.llamarServicio(); + + return "Carga de cupones terminada"; + }); + + log.info(cargarCupones.get()); + } +} + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/proyecto/SwaggerConfig.java b/src/main/java/com/proyecto/SwaggerConfig.java new file mode 100644 index 00000000..85a0372c --- /dev/null +++ b/src/main/java/com/proyecto/SwaggerConfig.java @@ -0,0 +1,46 @@ +package com.proyecto; + +import java.util.Collections; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +public class SwaggerConfig +{ + @Bean + public Docket apiDocket() + { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.basePackage("com.proyecto.controller")) + .paths(PathSelectors.any()) + .build() + .apiInfo(getApiInfo()); + } + + private ApiInfo getApiInfo() + { + String miPagina = "https://github.com/jmlizarraga"; + + return new ApiInfo( + "Order Service API", + "Order Service API Description", + "1.0", + miPagina, + new Contact("Jose", miPagina, miPagina), + "LICENSE", + "LICENSE URL", + Collections.emptyList() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/proyecto/controller/CategoriaController.java b/src/main/java/com/proyecto/controller/CategoriaController.java new file mode 100644 index 00000000..b90220ad --- /dev/null +++ b/src/main/java/com/proyecto/controller/CategoriaController.java @@ -0,0 +1,65 @@ +package com.proyecto.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.proyecto.dto.Category; +import com.proyecto.service.ICategoriaService; + +import io.swagger.annotations.ApiOperation; + +@RestController +@RequestMapping("/api/categoria/mobile") +public class CategoriaController +{ + @Autowired + private ICategoriaService servicio; + + + @ApiOperation(value = "Carrusel con Top 5 categorias") + @GetMapping("/getCarrusel") + public ResponseEntity getCarrusel() + { + Category categoria = servicio.obtener(); + + if (categoria != null) + { + if (categoria.getSubcategories() != null && ! categoria.getSubcategories().isEmpty()) + servicio.filtrarImagenesMobile( categoria.getSubcategories() ); + + return ResponseEntity.ok(categoria); + } + else + return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/proyecto/controller/CuponController.java b/src/main/java/com/proyecto/controller/CuponController.java new file mode 100644 index 00000000..fa00eec2 --- /dev/null +++ b/src/main/java/com/proyecto/controller/CuponController.java @@ -0,0 +1,36 @@ +package com.proyecto.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.proyecto.dto.Coupon; +import com.proyecto.service.ICuponService; + +import io.swagger.annotations.ApiOperation; + +@RestController +@RequestMapping("/api/cupon/mobile") +public class CuponController +{ + @Autowired + ICuponService servicio; + + @ApiOperation(value = "Carrusel con cupones que no han expirado") + @GetMapping("/getCarrusel") + public ResponseEntity> getCarrusel() + { + List lista = servicio.obtener(); + + if (!lista.isEmpty()) + return ResponseEntity.ok(lista); + else + return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); + } + +} diff --git a/src/main/java/com/proyecto/dto/Category.java b/src/main/java/com/proyecto/dto/Category.java new file mode 100644 index 00000000..20e2751f --- /dev/null +++ b/src/main/java/com/proyecto/dto/Category.java @@ -0,0 +1,84 @@ +package com.proyecto.dto; + +import java.util.List; + +public class Category +{ + private String id; + private String name; + private Integer relevance; + private String largeImageUrl; + private String mediumImageUrl; + private String smallImageUrl; + private String iconImageUrl; + private List subcategories; + + public Category() { + + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getRelevance() { + return relevance; + } + + public void setRelevance(Integer relevance) { + this.relevance = relevance; + } + + public String getLargeImageUrl() { + return largeImageUrl; + } + + public void setLargeImageUrl(String largeImageUrl) { + this.largeImageUrl = largeImageUrl; + } + + public String getMediumImageUrl() { + return mediumImageUrl; + } + + public void setMediumImageUrl(String mediumImageUrl) { + this.mediumImageUrl = mediumImageUrl; + } + + public String getSmallImageUrl() { + return smallImageUrl; + } + + public void setSmallImageUrl(String smallImageUrl) { + this.smallImageUrl = smallImageUrl; + } + + public List getSubcategories() { + return subcategories; + } + + public void setSubcategories(List subcategories) { + this.subcategories = subcategories; + } + + public String getIconImageUrl() { + return iconImageUrl; + } + + public void setIconImageUrl(String iconImageUrl) { + this.iconImageUrl = iconImageUrl; + } + +} diff --git a/src/main/java/com/proyecto/dto/Coupon.java b/src/main/java/com/proyecto/dto/Coupon.java new file mode 100644 index 00000000..645ddcbb --- /dev/null +++ b/src/main/java/com/proyecto/dto/Coupon.java @@ -0,0 +1,63 @@ +package com.proyecto.dto; + +import java.util.Date; + +public class Coupon +{ + private String id; + private String description; + private String seller; + private String image; + private Date expiresAt; + + public Coupon() { + + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getSeller() { + return seller; + } + + public void setSeller(String seller) { + this.seller = seller; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public Date getExpiresAt() { + return expiresAt; + } + + public void setExpiresAt(Date expiresAt) { + this.expiresAt = expiresAt; + } + + @Override + public String toString() { + return "Coupon [id=" + id + ", description=" + description + ", seller=" + seller + ", image=" + image + + ", expiresAt=" + expiresAt + "]"; + } + +} diff --git a/src/main/java/com/proyecto/service/ICategoriaService.java b/src/main/java/com/proyecto/service/ICategoriaService.java new file mode 100644 index 00000000..f947b1f2 --- /dev/null +++ b/src/main/java/com/proyecto/service/ICategoriaService.java @@ -0,0 +1,13 @@ +package com.proyecto.service; + +import java.util.List; + +import com.proyecto.dto.Category; + +public interface ICategoriaService { + + public Category obtener(); + + public void filtrarImagenesMobile(List subcategories); + +} diff --git a/src/main/java/com/proyecto/service/ICuponService.java b/src/main/java/com/proyecto/service/ICuponService.java new file mode 100644 index 00000000..90ad836f --- /dev/null +++ b/src/main/java/com/proyecto/service/ICuponService.java @@ -0,0 +1,10 @@ +package com.proyecto.service; + +import java.util.List; + +import com.proyecto.dto.Coupon; + +public interface ICuponService { + public void llamarServicio(); + public List obtener(); +} diff --git a/src/main/java/com/proyecto/service/impl/CategoriaServiceImpl.java b/src/main/java/com/proyecto/service/impl/CategoriaServiceImpl.java new file mode 100644 index 00000000..17a0dd1a --- /dev/null +++ b/src/main/java/com/proyecto/service/impl/CategoriaServiceImpl.java @@ -0,0 +1,58 @@ +package com.proyecto.service.impl; + +import java.util.List; + +import javax.ws.rs.core.MediaType; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.proyecto.dto.Category; +import com.proyecto.service.ICategoriaService; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; + +@Service +public class CategoriaServiceImpl implements ICategoriaService { + + @Value("${servicio.url.categorias}") + private String urlServicio; + + @Override + public Category obtener() { + try { + Client client = Client.create(); + WebResource webResource = client.resource(urlServicio); + ClientResponse response = webResource.type(MediaType.APPLICATION_JSON).get(ClientResponse.class); + + if (response.getStatus() == 200) { + ObjectMapper mapper = new ObjectMapper(); + String jsonResponse = response.getEntity(String.class); + + Category respuestaApi = mapper.readValue(jsonResponse, Category.class); + return respuestaApi; + } else { + return null; + } + } catch (Exception ex) { + return null; + } + } + + @Override + public void filtrarImagenesMobile(List subcategories) { + + for (Category c : subcategories) { + c.setLargeImageUrl(null); + c.setMediumImageUrl(null); + c.setIconImageUrl(null); + + if (c.getSubcategories() != null && !c.getSubcategories().isEmpty()) + this.filtrarImagenesMobile(c.getSubcategories()); + } + + } + +} diff --git a/src/main/java/com/proyecto/service/impl/CuponServiceImpl.java b/src/main/java/com/proyecto/service/impl/CuponServiceImpl.java new file mode 100644 index 00000000..8e2ecf96 --- /dev/null +++ b/src/main/java/com/proyecto/service/impl/CuponServiceImpl.java @@ -0,0 +1,88 @@ +package com.proyecto.service.impl; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import javax.ws.rs.core.MediaType; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import com.proyecto.dto.Coupon; +import com.proyecto.service.ICuponService; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.GenericType; +import com.sun.jersey.api.client.WebResource; + +@Service +@Scope("singleton") +public class CuponServiceImpl implements ICuponService { + + @Value("${servicio.url.cupones}") + private String urlServicio; + + private Date fechaUltimoLlamado; + private List lista; + + public CuponServiceImpl() { + this.fechaUltimoLlamado = new Date(); + this.lista = new ArrayList<>(); + } + + @Override + public void llamarServicio() { + + Client client = Client.create(); + WebResource webresource = client.resource(urlServicio); + + List resultado = webresource.type(MediaType.APPLICATION_JSON).get(new GenericType>() {}); + + // Obtiene solo cupones que no han expirado + this.lista = resultado.stream().filter(x -> x.getExpiresAt().after(new Date())).collect(Collectors.toList()); + } + + @Override + public List obtener() { + + SimpleDateFormat s = new SimpleDateFormat("dd-MM-yyyy"); + + if (lista.isEmpty() || !s.format(fechaUltimoLlamado).equals(s.format(new Date()))) // Si se carga por primera vez, u hoy es un nuevo dia + llamarServicio(); + + return this.lista; + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 00000000..10c72d62 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,6 @@ + +# Esto es para no incluir valores nulos dentro de un retorno json +spring.jackson.default-property-inclusion=non_null + +servicio.url.categorias=https://cs-hsa-api-categories-rest.herokuapp.com/categories +servicio.url.cupones=https://cs-hsa-api-coupons-rest.herokuapp.com/coupons \ No newline at end of file diff --git a/src/test/java/com/proyecto/test/PruebasUnitarias.java b/src/test/java/com/proyecto/test/PruebasUnitarias.java new file mode 100644 index 00000000..375c78e5 --- /dev/null +++ b/src/test/java/com/proyecto/test/PruebasUnitarias.java @@ -0,0 +1,16 @@ +package com.proyecto.test; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class PruebasUnitarias { + + @Test + public void contextLoads() { + } + +} diff --git a/src/test/java/com/proyecto/test/categorias/TestJson.java b/src/test/java/com/proyecto/test/categorias/TestJson.java new file mode 100644 index 00000000..dcc3d0a0 --- /dev/null +++ b/src/test/java/com/proyecto/test/categorias/TestJson.java @@ -0,0 +1,39 @@ +package com.proyecto.test.categorias; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import com.proyecto.test.PruebasUnitarias; + +public class TestJson extends PruebasUnitarias +{ + @Autowired + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + } + + @Test + public void validarCategoriaCarrusel() throws Exception + { + mockMvc.perform(get("/api/categoria/mobile/getCarrusel")).andExpect(status().isOk()) + .andExpect(content().contentType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.id").value("ROOT")) + .andExpect(jsonPath("$.name").value("LEGACY_NAVIGATION")); + } + +} + diff --git a/src/test/java/com/proyecto/test/categorias/TestService.java b/src/test/java/com/proyecto/test/categorias/TestService.java new file mode 100644 index 00000000..85e6b694 --- /dev/null +++ b/src/test/java/com/proyecto/test/categorias/TestService.java @@ -0,0 +1,26 @@ +package com.proyecto.test.categorias; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.proyecto.dto.Category; +import com.proyecto.service.ICategoriaService; +import com.proyecto.test.PruebasUnitarias; + + +public class TestService extends PruebasUnitarias { + + @Autowired + private ICategoriaService servicio; + + @Test + public void testInsertar() { + Category categoria = servicio.obtener(); + + assertNotNull(categoria); + assertNotNull(categoria.getId()); + assertNotNull("LEGACY_NAVIGATION", categoria.getName()); + } +}