[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report-or-feature-request.md",
    "content": "---\nname: Bug report or feature request\nabout: Suggest an idea or report a bug\n\n---\n\n- If you have troubles to run the components, or you'd like to ask a question - please use [PiggyMetrics gitter chat](https://gitter.im/sqshq/PiggyMetrics)\n\n- If you'd like to propose an improvement or report a bug, please provide a clear and concise description, attach logs and screenshots if possible. The issue should be ready to be implemented without any additional questions. PiggyMetrics is open source, nobody maintains it during the work hours. If you'd like the issue to be fixed, please consider to contibute your time to do that.\n\nThank you.\n"
  },
  {
    "path": ".gitignore",
    "content": "# Intellij\n.idea/\n*.iml\n*.iws\n\n# Mac\n.DS_Store\n\n# Maven\nlog/\ntarget/\n\n# Eclipse\n**/*.project\n**/*.classpath\n**/*.settings"
  },
  {
    "path": ".travis.yml",
    "content": "dist: trusty\nsudo: required\n\nservices:\n  - docker\n\nlanguage: java\njdk: oraclejdk8\n\nenv:\n  global:\n    - secure: \"GeONVsTD48Y88CKoqupo/FC1Gy0eCrT1UNylvMzz5VYcLhWcUcu/d4870ZJznBqslGHbFaRc6VCXFLnbKNyR5Chgg127ouWOv/NFubJ5cO0UjHlBwoAxQ/SIrxG9W4B6Y2VeWRO0SIPHF6wUVeELiaPmIIe/jF15/SWJA926G2dF0+zTbn1KNWoVRi9pxZ0bIbEvVcVfHjWHsPtho916KRZ7ToV27f5E9DPLVMraK2HROOplJlXKNLuUor9xSmtOB/787yNrZmdsUaQDMHVfru9yEubfg4tF5ydoluB+JPzeUqIK2PrDgAiwmkEdoJFfbAOqwdy8vZkgpYd/t5vkhbOn3yA0QO1pnyhidx1YgyoJN3HwfMeVNQwUVg8jw7bltVe/DxsNo/AW11Tu7jKteuxewRHCxORzaUSmy5rnLq3AKIXT6h8Kq5VxP5tGbQypa7/z+Zu4vfOBdKGMiLllQ0vhhU0osgYzk/jT4KknuWQtOkX+QTS9iViIUwQgcn3kIMlz4eO83Mbt+IkNvgjF0DyE64mfV2ThTjXDV6959g3Nl/Fl95VMSTg95xtl0tbf733Lj+9HCIfLetlBz4ZzOqaDgD5fRL5HA8jR3FFHVdMd+dx/JELjSRHxO6ZFrCVG/2hBldIakO31/FbpODnDIkKO+86Oqz9DPKVnLJlmZp4=\" # DOCKER_EMAIL\n    - secure: \"Gl6a03cI88dKHV4rjP1IkYqCdVe7IM0XNcEzFDCxmvXHWSpqlotntOYtgvtRKsYOzb0cfdQwgFuwj80glUbFX5vbX1y5r+qR5sQSrJVi61e8Pijn0MT7rE/d2gCJwaRTkR2lvtRRTIX41LA+aZ6F7+xFSd+ni82IlaXtDywQJWpCEmxcSqSUd5nVhHzH5JZmkYQmQ8fjxzGUhpeePfapYThPxXsHGxmJeoIlEDM1TEFtxVf3Zo9D10812uesa7EwNSKL6MOBU/Me7+liIHdRRpRwVmOjaFAZLeHsUfZQWcLWer0ODULov1U/YMdF860gV1X8PPYxAYNnWqevOGZIsYTX60yem/dCq90Lx7cIiK/TBZIS7x9k65QwP/shnO/RK8PPANBt8bJ4FBxmDPRMPRvgCPp4wQIYTyaiXd0M6BmK+LysS5cRgOOg7YF+ZRqKjNGY9rkNeGs0x8LIaE4Vz138tJCVJ0U+r2OOZFLCIu4dmvuOo1rWf4Hzh+Xt/nx8RrAwwqHKWRyayHkZEbQv2dNGcJsZyAzXsxA6NTDGQfKYloX5oY4qq3BMCkRoid9sLHoJvZrfkN6mjbZkEd2Ed3RzyTIxasmRUeD45vfr7go9ts1a6ppDrRYJWLi26pqyPhimTI0ljEYXp4QnK5JMya2y6H0wo/hJmzrqfXj4+C0=\" # DOCKER_USER\n    - secure: \"VRlJyPOz7fUmtFdpTdO51BVcjKUGP5t7KF5bG7TSJPXsal7SSxjt6desLQ6zv31tKBp/SrgbnH4hloeAe38hL1I5gQKLPuNjOYhtolgLHlCO3aJiJ0vn0o6mgxOok4S/ul6EMy8VLbKzwt7GrruoaDNVadwc98NY+Grr1eLUzD5CVxa4luSahtKASheCtM29OQOj42Ivnc/MUUMYMymYa/zgIkROqI1ZbQK12NGwx13FzjjF2C9WyTR8IMFOeiuL/Ha8wxT2VeYExWkNw8TQKg35WT26axSoI92BBjPHY+l4IDQ0g8N176sAG52gbz4WXx60kRgqHrn+b5cjO/v1PknqXCqwWVrskm/mgGxJ4IVmpqa7ItDYtoVPzW4hyPsEHWyPMjii5280VdqVRueHyxSBH/uF3iQCDwR0hRdVnPPvgrt40/jbiD3pBhnDQErHCu54FA7uzfFT8LUvj3PgHn19KWAb4gKMpP0AZA3aDOD/3+db1x25ozhDXoCLPFk+kK0lp5mwTKka910lX4L25wp2P3RDdbGQTeVjBBp5+IxzfBuF7m2aEww7HgpB/TmHGaxo61cZfLFwcU2tfIQnPVRiomMqh85xFHIkDETNJJMdV1o+Im5maOzg3u5iy7E6dJec6jTYSCIzh6BemM+scYVZzLkYZVRyrUQkUj7ldoI=\" # DOCKER_PASS\n    - COMMIT=${TRAVIS_COMMIT::7}\n\nafter_success:\n  - bash <(curl -s https://codecov.io/bash)\n  - docker login -u $DOCKER_USER -p $DOCKER_PASS\n\n  #TAG\n  - export TAG=`if [ \"$TRAVIS_BRANCH\" == \"master\" ]; then echo \"latest\"; else echo $TRAVIS_BRANCH ; fi`\n\n  # CONFIG SERVICE\n  - export CONFIG=sqshq/piggymetrics-config\n  - docker build -t $CONFIG:$COMMIT ./config\n  - docker tag $CONFIG:$COMMIT $CONFIG:$TAG\n  - docker push $CONFIG\n\n  # REGISTRY\n  - export REGISTRY=sqshq/piggymetrics-registry\n  - docker build -t $REGISTRY:$COMMIT ./registry\n  - docker tag $REGISTRY:$COMMIT $REGISTRY:$TAG\n  - docker push $REGISTRY\n\n  # GATEWAY\n  - export GATEWAY=sqshq/piggymetrics-gateway\n  - docker build -t $GATEWAY:$COMMIT ./gateway\n  - docker tag $GATEWAY:$COMMIT $GATEWAY:$TAG\n  - docker push $GATEWAY\n\n  # AUTH SERVICE\n  - export AUTH_SERVICE=sqshq/piggymetrics-auth-service\n  - docker build -t $AUTH_SERVICE:$COMMIT ./auth-service\n  - docker tag $AUTH_SERVICE:$COMMIT $AUTH_SERVICE:$TAG\n  - docker push $AUTH_SERVICE\n\n  # ACCOUNT SERVICE\n  - export ACCOUNT_SERVICE=sqshq/piggymetrics-account-service\n  - docker build -t $ACCOUNT_SERVICE:$COMMIT ./account-service\n  - docker tag $ACCOUNT_SERVICE:$COMMIT $ACCOUNT_SERVICE:$TAG\n  - docker push $ACCOUNT_SERVICE\n\n  # STATISTICS SERVICE\n  - export STATISTICS_SERVICE=sqshq/piggymetrics-statistics-service\n  - docker build -t $STATISTICS_SERVICE:$COMMIT ./statistics-service\n  - docker tag $STATISTICS_SERVICE:$COMMIT $STATISTICS_SERVICE:$TAG\n  - docker push $STATISTICS_SERVICE\n\n  # NOTIFICATION_SERVICE\n  - export NOTIFICATION_SERVICE=sqshq/piggymetrics-notification-service\n  - docker build -t $NOTIFICATION_SERVICE:$COMMIT ./notification-service\n  - docker tag $NOTIFICATION_SERVICE:$COMMIT $NOTIFICATION_SERVICE:$TAG\n  - docker push $NOTIFICATION_SERVICE\n\n  # MONITORING\n  - export MONITORING=sqshq/piggymetrics-monitoring\n  - docker build -t $MONITORING:$COMMIT ./monitoring\n  - docker tag $MONITORING:$COMMIT $MONITORING:$TAG\n  - docker push $MONITORING\n\n  # TURBINE STREAM SERVICE\n  - export TURBINE=sqshq/piggymetrics-turbine-stream-service\n  - docker build -t $TURBINE:$COMMIT ./turbine-stream-service\n  - docker tag $TURBINE:$COMMIT $TURBINE:$TAG\n  - docker push $TURBINE\n\n  # MONGO DB\n  - export MONGO_DB=sqshq/piggymetrics-mongodb\n  - docker build -t $MONGO_DB:$COMMIT ./mongodb\n  - docker tag $MONGO_DB:$COMMIT $MONGO_DB:$TAG\n  - docker push $MONGO_DB\n"
  },
  {
    "path": "LICENCE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Alexander Lukyanchikov, http://sqshq.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.org/sqshq/PiggyMetrics.svg?branch=master)](https://travis-ci.org/sqshq/PiggyMetrics)\n[![codecov.io](https://codecov.io/github/sqshq/PiggyMetrics/coverage.svg?branch=master)](https://codecov.io/github/sqshq/PiggyMetrics?branch=master)\n[![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/sqshq/PiggyMetrics/blob/master/LICENCE)\n[![Join the chat at https://gitter.im/sqshq/PiggyMetrics](https://badges.gitter.im/sqshq/PiggyMetrics.svg)](https://gitter.im/sqshq/PiggyMetrics?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n\n# Piggy Metrics\n\nPiggy Metrics is a simple financial advisor app built to demonstrate the [Microservice Architecture Pattern](http://martinfowler.com/microservices/) using Spring Boot, Spring Cloud and Docker. The project is intended as a tutorial, but you are welcome to fork it and turn it into something else!\n\n<br>\n\n![](https://cloud.githubusercontent.com/assets/6069066/13864234/442d6faa-ecb9-11e5-9929-34a9539acde0.png)\n![Piggy Metrics](https://cloud.githubusercontent.com/assets/6069066/13830155/572e7552-ebe4-11e5-918f-637a49dff9a2.gif)\n\n## Functional services\n\nPiggy Metrics is decomposed into three core microservices. All of them are independently deployable applications organized around certain business domains.\n\n<img width=\"880\" alt=\"Functional services\" src=\"https://cloud.githubusercontent.com/assets/6069066/13900465/730f2922-ee20-11e5-8df0-e7b51c668847.png\">\n\n#### Account service\nContains general input logic and validation: incomes/expenses items, savings and account settings.\n\nMethod\t| Path\t| Description\t| User authenticated\t| Available from UI\n------------- | ------------------------- | ------------- |:-------------:|:----------------:|\nGET\t| /accounts/{account}\t| Get specified account data\t|  | \t\nGET\t| /accounts/current\t| Get current account data\t| × | ×\nGET\t| /accounts/demo\t| Get demo account data (pre-filled incomes/expenses items, etc)\t|   | \t×\nPUT\t| /accounts/current\t| Save current account data\t| × | ×\nPOST\t| /accounts/\t| Register new account\t|   | ×\n\n\n#### Statistics service\nPerforms calculations on major statistics parameters and captures time series for each account. Datapoint contains values normalized to base currency and time period. This data is used to track cash flow dynamics during the account lifetime.\n\nMethod\t| Path\t| Description\t| User authenticated\t| Available from UI\n------------- | ------------------------- | ------------- |:-------------:|:----------------:|\nGET\t| /statistics/{account}\t| Get specified account statistics\t          |  | \t\nGET\t| /statistics/current\t| Get current account statistics\t| × | × \nGET\t| /statistics/demo\t| Get demo account statistics\t|   | × \nPUT\t| /statistics/{account}\t| Create or update time series datapoint for specified account\t|   | \n\n\n#### Notification service\nStores user contact information and notification settings (reminders, backup frequency etc). Scheduled worker collects required information from other services and sends e-mail messages to subscribed customers.\n\nMethod\t| Path\t| Description\t| User authenticated\t| Available from UI\n------------- | ------------------------- | ------------- |:-------------:|:----------------:|\nGET\t| /notifications/settings/current\t| Get current account notification settings\t| × | ×\t\nPUT\t| /notifications/settings/current\t| Save current account notification settings\t| × | ×\n\n#### Notes\n- Each microservice has its own database, so there is no way to bypass API and access persistence data directly.\n- MongoDB is used as a primary database for each of the services.\n- All services are talking to each other via the Rest API\n\n## Infrastructure\n[Spring cloud](https://spring.io/projects/spring-cloud) provides powerful tools for developers to quickly implement common distributed systems patterns -\n<img width=\"880\" alt=\"Infrastructure services\" src=\"https://cloud.githubusercontent.com/assets/6069066/13906840/365c0d94-eefa-11e5-90ad-9d74804ca412.png\">\n### Config service\n[Spring Cloud Config](http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html) is horizontally scalable centralized configuration service for the distributed systems. It uses a pluggable repository layer that currently supports local storage, Git, and Subversion.\n\nIn this project, we are going to use `native profile`, which simply loads config files from the local classpath. You can see `shared` directory in [Config service resources](https://github.com/sqshq/PiggyMetrics/tree/master/config/src/main/resources). Now, when Notification-service requests its configuration, Config service responses with `shared/notification-service.yml` and `shared/application.yml` (which is shared between all client applications).\n\n##### Client side usage\nJust build Spring Boot application with `spring-cloud-starter-config` dependency, autoconfiguration will do the rest.\n\nNow you don't need any embedded properties in your application. Just provide `bootstrap.yml` with application name and Config service url:\n```yml\nspring:\n  application:\n    name: notification-service\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n```\n\n##### With Spring Cloud Config, you can change application config dynamically. \nFor example, [EmailService bean](https://github.com/sqshq/PiggyMetrics/blob/master/notification-service/src/main/java/com/piggymetrics/notification/service/EmailServiceImpl.java) is annotated with `@RefreshScope`. That means you can change e-mail text and subject without rebuild and restart the Notification service.\n\nFirst, change required properties in Config server. Then make a refresh call to the Notification service:\n`curl -H \"Authorization: Bearer #token#\" -XPOST http://127.0.0.1:8000/notifications/refresh`\n\nYou could also use Repository [webhooks to automate this process](http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html#_push_notifications_and_spring_cloud_bus)\n\n##### Notes\n- `@RefreshScope` doesn't work with `@Configuration` classes and doesn't ignores `@Scheduled` methods\n- `fail-fast` property means that Spring Boot application will fail startup immediately, if it cannot connect to the Config Service.\n\n### Auth service\nAuthorization responsibilities are extracted to a separate server, which grants [OAuth2 tokens](https://tools.ietf.org/html/rfc6749) for the backend resource services. Auth Server is used for user authorization as well as for secure machine-to-machine communication inside the perimeter.\n\nIn this project, I use [`Password credentials`](https://tools.ietf.org/html/rfc6749#section-4.3) grant type for users authorization (since it's used only by the UI) and [`Client Credentials`](https://tools.ietf.org/html/rfc6749#section-4.4) grant for service-to-service communciation.\n\nSpring Cloud Security provides convenient annotations and autoconfiguration to make this really easy to implement on both server and client side. You can learn more about that in [documentation](http://cloud.spring.io/spring-cloud-security/spring-cloud-security.html).\n\nOn the client side, everything works exactly the same as with traditional session-based authorization. You can retrieve `Principal` object from the request, check user roles using the expression-based access control and `@PreAuthorize` annotation.\n\nEach PiggyMetrics client has a scope: `server` for backend services and `ui` - for the browser. We can use `@PreAuthorize` annotation to protect controllers from  an external access:\n\n``` java\n@PreAuthorize(\"#oauth2.hasScope('server')\")\n@RequestMapping(value = \"accounts/{name}\", method = RequestMethod.GET)\npublic List<DataPoint> getStatisticsByAccountName(@PathVariable String name) {\n\treturn statisticsService.findByAccountName(name);\n}\n```\n\n### API Gateway\nAPI Gateway is a single entry point into the system, used to handle requests and routing them to the appropriate backend service or by [aggregating results from a scatter-gather call](http://techblog.netflix.com/2013/01/optimizing-netflix-api.html). Also, it can be used for authentication, insights, stress and canary testing, service migration, static response handling and active traffic management.\n\nNetflix opensourced [such an edge service](http://techblog.netflix.com/2013/06/announcing-zuul-edge-service-in-cloud.html) and Spring Cloud allows to use it with a single `@EnableZuulProxy` annotation. In this project, we use Zuul to store some static content (the UI application) and to route requests to appropriate the microservices. Here's a simple prefix-based routing configuration for the Notification service:\n\n```yml\nzuul:\n  routes:\n    notification-service:\n        path: /notifications/**\n        serviceId: notification-service\n        stripPrefix: false\n\n```\n\nThat means all requests starting with `/notifications` will be routed to the Notification service. There is no hardcoded addresses, as you can see. Zuul uses [Service discovery](https://github.com/sqshq/PiggyMetrics/blob/master/README.md#service-discovery) mechanism to locate Notification service instances and also [Circuit Breaker and Load Balancer](https://github.com/sqshq/PiggyMetrics/blob/master/README.md#http-client-load-balancer-and-circuit-breaker), described below.\n\n### Service Discovery\n\nService Discovery allows automatic detection of the network locations for all registered services. These locations might have dynamically assigned addresses due to auto-scaling, failures or upgrades.\n\nThe key part of Service discovery is the Registry. In this project, we use Netflix Eureka. Eureka is a good example of the client-side discovery pattern, where client is responsible for looking up the locations of available service instances and load balancing between them.\n\nWith Spring Boot, you can easily build Eureka Registry using the `spring-cloud-starter-eureka-server` dependency, `@EnableEurekaServer` annotation and simple configuration properties.\n\nClient support enabled with `@EnableDiscoveryClient` annotation a `bootstrap.yml` with application name:\n``` yml\nspring:\n  application:\n    name: notification-service\n```\n\nThis service will be registered with the Eureka Server and provided with metadata such as host, port, health indicator URL, home page etc. Eureka receives heartbeat messages from each instance belonging to the service. If the heartbeat fails over a configurable timetable, the instance will be removed from the registry.\n\nAlso, Eureka provides a simple interface where you can track running services and a number of available instances: `http://localhost:8761`\n\n### Load balancer, Circuit breaker and Http client\n\n#### Ribbon\nRibbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Compared to a traditional load balancer, there is no need in additional network hop - you can contact desired service directly.\n\nOut of the box, it natively integrates with Spring Cloud and Service Discovery. [Eureka Client](https://github.com/sqshq/PiggyMetrics#service-discovery) provides a dynamic list of available servers so Ribbon could balance between them.\n\n#### Hystrix\nHystrix is the implementation of [Circuit Breaker Pattern](http://martinfowler.com/bliki/CircuitBreaker.html), which gives us a control over latency and network failures while communicating with other services. The main idea is to stop cascading failures in the distributed environment - that helps to fail fast and recover as soon as possible - important aspects of a fault-tolerant system that can self-heal.\n\nMoreover, Hystrix generates metrics on execution outcomes and latency for each command, that we can use to [monitor system's behavior](https://github.com/sqshq/PiggyMetrics#monitor-dashboard).\n\n#### Feign\nFeign is a declarative Http client which seamlessly integrates with Ribbon and Hystrix. Actually, a single `spring-cloud-starter-feign` dependency and `@EnableFeignClients` annotation gives us a full set of tools, including Load balancer, Circuit Breaker and Http client with reasonable default configuration.\n\nHere is an example from the Account Service:\n\n``` java\n@FeignClient(name = \"statistics-service\")\npublic interface StatisticsServiceClient {\n\n\t@RequestMapping(method = RequestMethod.PUT, value = \"/statistics/{accountName}\", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)\n\tvoid updateStatistics(@PathVariable(\"accountName\") String accountName, Account account);\n\n}\n```\n\n- Everything you need is just an interface\n- You can share `@RequestMapping` part between Spring MVC controller and Feign methods\n- Above example specifies just a desired service id - `statistics-service`, thanks to auto-discovery through Eureka\n\n### Monitor dashboard\n\nIn this project configuration, each microservice with Hystrix on board pushes metrics to Turbine via Spring Cloud Bus (with AMQP broker). The Monitoring project is just a small Spring boot application with the [Turbine](https://github.com/Netflix/Turbine) and [Hystrix Dashboard](https://github.com/Netflix-Skunkworks/hystrix-dashboard).\n\nLet's see observe the behavior of our system under load: Statistics Service imitates a delay during the request processing. The response timeout is set to 1 second:\n\n<img width=\"880\" src=\"https://cloud.githubusercontent.com/assets/6069066/14194375/d9a2dd80-f7be-11e5-8bcc-9a2fce753cfe.png\">\n\n<img width=\"212\" src=\"https://cloud.githubusercontent.com/assets/6069066/14127349/21e90026-f628-11e5-83f1-60108cb33490.gif\">\t| <img width=\"212\" src=\"https://cloud.githubusercontent.com/assets/6069066/14127348/21e6ed40-f628-11e5-9fa4-ed527bf35129.gif\"> | <img width=\"212\" src=\"https://cloud.githubusercontent.com/assets/6069066/14127346/21b9aaa6-f628-11e5-9bba-aaccab60fd69.gif\"> | <img width=\"212\" src=\"https://cloud.githubusercontent.com/assets/6069066/14127350/21eafe1c-f628-11e5-8ccd-a6b6873c046a.gif\">\n--- |--- |--- |--- |\n| `0 ms delay` | `500 ms delay` | `800 ms delay` | `1100 ms delay`\n| Well behaving system. Throughput is about 22 rps. Small number of active threads in the Statistics service. Median service time is about 50 ms. | The number of active threads is growing. We can see purple number of thread-pool rejections and therefore about 40% of errors, but the circuit is still closed. | Half-open state: the ratio of failed commands is higher than 50%, so the circuit breaker kicks in. After sleep window amount of time, the next request goes through. | 100 percent of the requests fail. The circuit is now permanently open. Retry after sleep time won't close the circuit again because a single request is too slow.\n\n### Log analysis\n\nCentralized logging can be very useful while attempting to identify problems in a distributed environment. Elasticsearch, Logstash and Kibana stack lets you search and analyze your logs, utilization and network activity data with ease.\n\n### Distributed tracing\n\nAnalyzing problems in distributed systems can be difficult, especially trying to trace requests that propagate from one microservice to another.\n\n[Spring Cloud Sleuth](https://cloud.spring.io/spring-cloud-sleuth/) solves this problem by providing support for the distributed tracing. It adds two types of IDs to the logging: `traceId` and `spanId`. `spanId` represents a basic unit of work, for example sending an HTTP request. The traceId contains a set of spans forming a tree-like structure. For example, with a distributed big-data store, a trace might be formed by a PUT request. Using `traceId` and `spanId` for each operation we know when and where our application is as it processes a request, making reading logs much easier. \n\nThe logs are as follows, notice the `[appname,traceId,spanId,exportable]` entries from the Slf4J MDC:\n\n```text\n2018-07-26 23:13:49.381  WARN [gateway,3216d0de1384bb4f,3216d0de1384bb4f,false] 2999 --- [nio-4000-exec-1] o.s.c.n.z.f.r.s.AbstractRibbonCommand    : The Hystrix timeout of 20000ms for the command account-service is set lower than the combination of the Ribbon read and connect timeout, 80000ms.\n2018-07-26 23:13:49.562  INFO [account-service,3216d0de1384bb4f,404ff09c5cf91d2e,false] 3079 --- [nio-6000-exec-1] c.p.account.service.AccountServiceImpl   : new account has been created: test\n```\n\n- *`appname`*: The name of the application that logged the span from the property `spring.application.name`\n- *`traceId`*: This is an ID that is assigned to a single request, job, or action\n- *`spanId`*: The ID of a specific operation that took place\n- *`exportable`*: Whether the log should be exported to [Zipkin](https://zipkin.io/)\n\n## Infrastructure automation\n\nDeploying microservices, with their interdependence, is much more complex process than deploying a monolithic application. It is really important to have a fully automated infrastructure. We can achieve following benefits with Continuous Delivery approach:\n\n- The ability to release software anytime\n- Any build could end up being a release\n- Build artifacts once - deploy as needed\n\nHere is a simple Continuous Delivery workflow, implemented in this project:\n\n<img width=\"880\" src=\"https://cloud.githubusercontent.com/assets/6069066/14159789/0dd7a7ce-f6e9-11e5-9fbb-a7fe0f4431e3.png\">\n\nIn this [configuration](https://github.com/sqshq/PiggyMetrics/blob/master/.travis.yml), Travis CI builds tagged images for each successful git push. So, there are always the `latest` images for each microservice on [Docker Hub](https://hub.docker.com/r/sqshq/) and older images, tagged with git commit hash. It's easy to deploy any of them and quickly rollback, if needed.\n\n## Let's try it out\n\nNote that starting 8 Spring Boot applications, 4 MongoDB instances and a RabbitMq requires at least 4Gb of RAM.\n\n#### Before you start\n- Install Docker and Docker Compose.\n- Change environment variable values in `.env` file for more security or leave it as it is.\n- Build the project: `mvn package [-DskipTests]`\n\n#### Production mode\nIn this mode, all latest images will be pulled from Docker Hub.\nJust copy `docker-compose.yml` and hit `docker-compose up`\n\n#### Development mode\nIf you'd like to build images yourself, you have to clone the repository and build artifacts using maven. After that, run `docker-compose -f docker-compose.yml -f docker-compose.dev.yml up`\n\n`docker-compose.dev.yml` inherits `docker-compose.yml` with additional possibility to build images locally and expose all containers ports for convenient development.\n\nIf you'd like to start applications in Intellij Idea you need to either use [EnvFile plugin](https://plugins.jetbrains.com/plugin/7861-envfile) or manually export environment variables listed in `.env` file (make sure they were exported: `printenv`)\n\n#### Important endpoints\n- http://localhost:80 - Gateway\n- http://localhost:8761 - Eureka Dashboard\n- http://localhost:9000/hystrix - Hystrix Dashboard (Turbine stream link: `http://turbine-stream-service:8080/turbine/turbine.stream`)\n- http://localhost:15672 - RabbitMq management (default login/password: guest/guest)\n\n## Contributions are welcome!\n\nPiggyMetrics is open source, and would greatly appreciate your help. Feel free to suggest and implement any improvements.\n"
  },
  {
    "path": "account-service/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/account-service.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/account-service.jar\"]\n\nEXPOSE 6000"
  },
  {
    "path": "account-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>account-service</artifactId>\n\t<version>1.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>account-service</name>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-oauth2</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-security</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-openfeign</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-sleuth</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-data-mongodb</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-bus-amqp</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>de.flapdoodle.embed</groupId>\n\t\t\t<artifactId>de.flapdoodle.embed.mongo</artifactId>\n\t\t\t<version>1.50.3</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.jayway.jsonpath</groupId>\n\t\t\t<artifactId>json-path</artifactId>\n\t\t\t<version>2.2.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\t\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>account-service</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t<version>0.7.6.201602180812</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>report</id>\n\t\t\t\t\t\t<phase>test</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>report</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/AccountApplication.java",
    "content": "package com.piggymetrics.account;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.cloud.openfeign.EnableFeignClients;\nimport org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;\n\n@SpringBootApplication\n@EnableDiscoveryClient\n@EnableOAuth2Client\n@EnableFeignClients\n@EnableCircuitBreaker\n@EnableGlobalMethodSecurity(prePostEnabled = true)\npublic class AccountApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(AccountApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/client/AuthServiceClient.java",
    "content": "package com.piggymetrics.account.client;\n\nimport com.piggymetrics.account.domain.User;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n@FeignClient(name = \"auth-service\")\npublic interface AuthServiceClient {\n\n\t@RequestMapping(method = RequestMethod.POST, value = \"/uaa/users\", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)\n\tvoid createUser(User user);\n\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/client/StatisticsServiceClient.java",
    "content": "package com.piggymetrics.account.client;\n\nimport com.piggymetrics.account.domain.Account;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n@FeignClient(name = \"statistics-service\", fallback = StatisticsServiceClientFallback.class)\npublic interface StatisticsServiceClient {\n\n\t@RequestMapping(method = RequestMethod.PUT, value = \"/statistics/{accountName}\", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)\n\tvoid updateStatistics(@PathVariable(\"accountName\") String accountName, Account account);\n\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/client/StatisticsServiceClientFallback.java",
    "content": "package com.piggymetrics.account.client;\n\nimport com.piggymetrics.account.domain.Account;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author cdov\n */\n@Component\npublic class StatisticsServiceClientFallback implements StatisticsServiceClient {\n    private static final Logger LOGGER = LoggerFactory.getLogger(StatisticsServiceClientFallback.class);\n    @Override\n    public void updateStatistics(String accountName, Account account) {\n        LOGGER.error(\"Error during update statistics for account: {}\", accountName);\n    }\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/config/ResourceServerConfig.java",
    "content": "package com.piggymetrics.account.config;\n\nimport com.piggymetrics.account.service.security.CustomUserInfoTokenServices;\nimport feign.RequestInterceptor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;\nimport org.springframework.security.oauth2.client.OAuth2RestTemplate;\nimport org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;\nimport org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;\n\n/**\n * @author cdov\n */\n@Configuration\n@EnableResourceServer\npublic class ResourceServerConfig extends ResourceServerConfigurerAdapter {\n\n    private final ResourceServerProperties sso;\n\n    @Autowired\n    public ResourceServerConfig(ResourceServerProperties sso) {\n        this.sso = sso;\n    }\n\n    @Bean\n    @ConfigurationProperties(prefix = \"security.oauth2.client\")\n    public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {\n        return new ClientCredentialsResourceDetails();\n    }\n\n    @Bean\n    public RequestInterceptor oauth2FeignRequestInterceptor(){\n        return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());\n    }\n\n    @Bean\n    public OAuth2RestTemplate clientCredentialsRestTemplate() {\n        return new OAuth2RestTemplate(clientCredentialsResourceDetails());\n    }\n\n    @Bean\n    public ResourceServerTokenServices tokenServices() {\n        return new CustomUserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());\n    }\n\n    @Override\n    public void configure(HttpSecurity http) throws Exception {\n        http.authorizeRequests()\n                .antMatchers(\"/\" , \"/demo\").permitAll()\n                .anyRequest().authenticated();\n    }\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/controller/AccountController.java",
    "content": "package com.piggymetrics.account.controller;\n\nimport com.piggymetrics.account.domain.Account;\nimport com.piggymetrics.account.domain.User;\nimport com.piggymetrics.account.service.AccountService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.validation.Valid;\nimport java.security.Principal;\n\n@RestController\npublic class AccountController {\n\n\t@Autowired\n\tprivate AccountService accountService;\n\n\t@PreAuthorize(\"#oauth2.hasScope('server') or #name.equals('demo')\")\n\t@RequestMapping(path = \"/{name}\", method = RequestMethod.GET)\n\tpublic Account getAccountByName(@PathVariable String name) {\n\t\treturn accountService.findByName(name);\n\t}\n\n\t@RequestMapping(path = \"/current\", method = RequestMethod.GET)\n\tpublic Account getCurrentAccount(Principal principal) {\n\t\treturn accountService.findByName(principal.getName());\n\t}\n\n\t@RequestMapping(path = \"/current\", method = RequestMethod.PUT)\n\tpublic void saveCurrentAccount(Principal principal, @Valid @RequestBody Account account) {\n\t\taccountService.saveChanges(principal.getName(), account);\n\t}\n\n\t@RequestMapping(path = \"/\", method = RequestMethod.POST)\n\tpublic Account createNewAccount(@Valid @RequestBody User user) {\n\t\treturn accountService.create(user);\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/controller/ErrorHandler.java",
    "content": "package com.piggymetrics.account.controller;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseStatus;\n\n@ControllerAdvice\npublic class ErrorHandler {\n\n\tprivate final Logger log = LoggerFactory.getLogger(getClass());\n\n\t// TODO add MethodArgumentNotValidException handler\n\t// TODO remove such general handler\n\t@ExceptionHandler(IllegalArgumentException.class)\n\t@ResponseStatus(HttpStatus.BAD_REQUEST)\n\tpublic void processValidationError(IllegalArgumentException e) {\n\t\tlog.info(\"Returning HTTP 400 Bad Request\", e);\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/domain/Account.java",
    "content": "package com.piggymetrics.account.domain;\n\nimport org.codehaus.jackson.annotate.JsonIgnoreProperties;\nimport org.hibernate.validator.constraints.Length;\nimport org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotNull;\nimport java.util.Date;\nimport java.util.List;\n\n@Document(collection = \"accounts\")\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class Account {\n\n\t@Id\n\tprivate String name;\n\n\tprivate Date lastSeen;\n\n\t@Valid\n\tprivate List<Item> incomes;\n\n\t@Valid\n\tprivate List<Item> expenses;\n\n\t@Valid\n\t@NotNull\n\tprivate Saving saving;\n\n\t@Length(min = 0, max = 20_000)\n\tprivate String note;\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic Date getLastSeen() {\n\t\treturn lastSeen;\n\t}\n\n\tpublic void setLastSeen(Date lastSeen) {\n\t\tthis.lastSeen = lastSeen;\n\t}\n\n\tpublic List<Item> getIncomes() {\n\t\treturn incomes;\n\t}\n\n\tpublic void setIncomes(List<Item> incomes) {\n\t\tthis.incomes = incomes;\n\t}\n\n\tpublic List<Item> getExpenses() {\n\t\treturn expenses;\n\t}\n\n\tpublic void setExpenses(List<Item> expenses) {\n\t\tthis.expenses = expenses;\n\t}\n\n\tpublic Saving getSaving() {\n\t\treturn saving;\n\t}\n\n\tpublic void setSaving(Saving saving) {\n\t\tthis.saving = saving;\n\t}\n\n\tpublic String getNote() {\n\t\treturn note;\n\t}\n\n\tpublic void setNote(String note) {\n\t\tthis.note = note;\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/domain/Currency.java",
    "content": "package com.piggymetrics.account.domain;\n\npublic enum Currency {\n\n\tUSD, EUR, RUB;\n\n\tpublic static Currency getDefault() {\n\t\treturn USD;\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/domain/Item.java",
    "content": "package com.piggymetrics.account.domain;\n\nimport org.hibernate.validator.constraints.Length;\n\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\npublic class Item {\n\n\t@NotNull\n\t@Length(min = 1, max = 20)\n\tprivate String title;\n\n\t@NotNull\n\tprivate BigDecimal amount;\n\n\t@NotNull\n\tprivate Currency currency;\n\n\t@NotNull\n\tprivate TimePeriod period;\n\n\t@NotNull\n\tprivate String icon;\n\n\tpublic String getTitle() {\n\t\treturn title;\n\t}\n\n\tpublic void setTitle(String title) {\n\t\tthis.title = title;\n\t}\n\n\tpublic BigDecimal getAmount() {\n\t\treturn amount;\n\t}\n\n\tpublic void setAmount(BigDecimal amount) {\n\t\tthis.amount = amount;\n\t}\n\n\tpublic Currency getCurrency() {\n\t\treturn currency;\n\t}\n\n\tpublic void setCurrency(Currency currency) {\n\t\tthis.currency = currency;\n\t}\n\n\tpublic TimePeriod getPeriod() {\n\t\treturn period;\n\t}\n\n\tpublic void setPeriod(TimePeriod period) {\n\t\tthis.period = period;\n\t}\n\n\tpublic String getIcon() {\n\t\treturn icon;\n\t}\n\n\tpublic void setIcon(String icon) {\n\t\tthis.icon = icon;\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/domain/Saving.java",
    "content": "package com.piggymetrics.account.domain;\n\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\npublic class Saving {\n\n\t@NotNull\n\tprivate BigDecimal amount;\n\n\t@NotNull\n\tprivate Currency currency;\n\n\t@NotNull\n\tprivate BigDecimal interest;\n\n\t@NotNull\n\tprivate Boolean deposit;\n\n\t@NotNull\n\tprivate Boolean capitalization;\n\n\tpublic BigDecimal getAmount() {\n\t\treturn amount;\n\t}\n\n\tpublic void setAmount(BigDecimal amount) {\n\t\tthis.amount = amount;\n\t}\n\n\tpublic Currency getCurrency() {\n\t\treturn currency;\n\t}\n\n\tpublic void setCurrency(Currency currency) {\n\t\tthis.currency = currency;\n\t}\n\n\tpublic BigDecimal getInterest() {\n\t\treturn interest;\n\t}\n\n\tpublic void setInterest(BigDecimal interest) {\n\t\tthis.interest = interest;\n\t}\n\n\tpublic Boolean getDeposit() {\n\t\treturn deposit;\n\t}\n\n\tpublic void setDeposit(Boolean deposit) {\n\t\tthis.deposit = deposit;\n\t}\n\n\tpublic Boolean getCapitalization() {\n\t\treturn capitalization;\n\t}\n\n\tpublic void setCapitalization(Boolean capitalization) {\n\t\tthis.capitalization = capitalization;\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/domain/TimePeriod.java",
    "content": "package com.piggymetrics.account.domain;\n\npublic enum TimePeriod {\n\n\tYEAR, QUARTER, MONTH, DAY, HOUR\n\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/domain/User.java",
    "content": "package com.piggymetrics.account.domain;\n\nimport org.hibernate.validator.constraints.Length;\n\nimport javax.validation.constraints.NotNull;\n\npublic class User {\n\n\t@NotNull\n\t@Length(min = 3, max = 20)\n\tprivate String username;\n\n\t@NotNull\n\t@Length(min = 6, max = 40)\n\tprivate String password;\n\n\tpublic String getUsername() {\n\t\treturn username;\n\t}\n\n\tpublic void setUsername(String username) {\n\t\tthis.username = username;\n\t}\n\n\tpublic String getPassword() {\n\t\treturn password;\n\t}\n\n\tpublic void setPassword(String password) {\n\t\tthis.password = password;\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/repository/AccountRepository.java",
    "content": "package com.piggymetrics.account.repository;\n\nimport com.piggymetrics.account.domain.Account;\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface AccountRepository extends CrudRepository<Account, String> {\n\n\tAccount findByName(String name);\n\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/service/AccountService.java",
    "content": "package com.piggymetrics.account.service;\n\nimport com.piggymetrics.account.domain.Account;\nimport com.piggymetrics.account.domain.User;\n\npublic interface AccountService {\n\n\t/**\n\t * Finds account by given name\n\t *\n\t * @param accountName\n\t * @return found account\n\t */\n\tAccount findByName(String accountName);\n\n\t/**\n\t * Checks if account with the same name already exists\n\t * Invokes Auth Service user creation\n\t * Creates new account with default parameters\n\t *\n\t * @param user\n\t * @return created account\n\t */\n\tAccount create(User user);\n\n\t/**\n\t * Validates and applies incoming account updates\n\t * Invokes Statistics Service update\n\t *\n\t * @param name\n\t * @param update\n\t */\n\tvoid saveChanges(String name, Account update);\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/service/AccountServiceImpl.java",
    "content": "package com.piggymetrics.account.service;\n\nimport com.piggymetrics.account.client.AuthServiceClient;\nimport com.piggymetrics.account.client.StatisticsServiceClient;\nimport com.piggymetrics.account.domain.Account;\nimport com.piggymetrics.account.domain.Currency;\nimport com.piggymetrics.account.domain.Saving;\nimport com.piggymetrics.account.domain.User;\nimport com.piggymetrics.account.repository.AccountRepository;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.Assert;\n\nimport java.math.BigDecimal;\nimport java.util.Date;\n\n@Service\npublic class AccountServiceImpl implements AccountService {\n\n\tprivate final Logger log = LoggerFactory.getLogger(getClass());\n\n\t@Autowired\n\tprivate StatisticsServiceClient statisticsClient;\n\n\t@Autowired\n\tprivate AuthServiceClient authClient;\n\n\t@Autowired\n\tprivate AccountRepository repository;\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic Account findByName(String accountName) {\n\t\tAssert.hasLength(accountName);\n\t\treturn repository.findByName(accountName);\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic Account create(User user) {\n\n\t\tAccount existing = repository.findByName(user.getUsername());\n\t\tAssert.isNull(existing, \"account already exists: \" + user.getUsername());\n\n\t\tauthClient.createUser(user);\n\n\t\tSaving saving = new Saving();\n\t\tsaving.setAmount(new BigDecimal(0));\n\t\tsaving.setCurrency(Currency.getDefault());\n\t\tsaving.setInterest(new BigDecimal(0));\n\t\tsaving.setDeposit(false);\n\t\tsaving.setCapitalization(false);\n\n\t\tAccount account = new Account();\n\t\taccount.setName(user.getUsername());\n\t\taccount.setLastSeen(new Date());\n\t\taccount.setSaving(saving);\n\n\t\trepository.save(account);\n\n\t\tlog.info(\"new account has been created: \" + account.getName());\n\n\t\treturn account;\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic void saveChanges(String name, Account update) {\n\n\t\tAccount account = repository.findByName(name);\n\t\tAssert.notNull(account, \"can't find account with name \" + name);\n\n\t\taccount.setIncomes(update.getIncomes());\n\t\taccount.setExpenses(update.getExpenses());\n\t\taccount.setSaving(update.getSaving());\n\t\taccount.setNote(update.getNote());\n\t\taccount.setLastSeen(new Date());\n\t\trepository.save(account);\n\n\t\tlog.debug(\"account {} changes has been saved\", name);\n\n\t\tstatisticsClient.updateStatistics(name, account);\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/java/com/piggymetrics/account/service/security/CustomUserInfoTokenServices.java",
    "content": "package com.piggymetrics.account.service.security;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.FixedAuthoritiesExtractor;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.oauth2.client.OAuth2RestOperations;\nimport org.springframework.security.oauth2.client.OAuth2RestTemplate;\nimport org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;\nimport org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;\nimport org.springframework.security.oauth2.common.OAuth2AccessToken;\nimport org.springframework.security.oauth2.common.exceptions.InvalidTokenException;\nimport org.springframework.security.oauth2.provider.OAuth2Authentication;\nimport org.springframework.security.oauth2.provider.OAuth2Request;\nimport org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;\n\nimport java.util.*;\n\n/**\n * Extended implementation of {@link org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices}\n *\n * By default, it designed to return only user details. This class provides {@link #getRequest(Map)} method, which\n * returns clientId and scope of calling service. This information used in controller's security checks.\n */\n\npublic class CustomUserInfoTokenServices implements ResourceServerTokenServices {\n\n\tprotected final Log logger = LogFactory.getLog(getClass());\n\n\tprivate static final String[] PRINCIPAL_KEYS = new String[] { \"user\", \"username\",\n\t\t\t\"userid\", \"user_id\", \"login\", \"id\", \"name\" };\n\n\tprivate final String userInfoEndpointUrl;\n\n\tprivate final String clientId;\n\n\tprivate OAuth2RestOperations restTemplate;\n\n\tprivate String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;\n\n\tprivate AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();\n\n\tpublic CustomUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {\n\t\tthis.userInfoEndpointUrl = userInfoEndpointUrl;\n\t\tthis.clientId = clientId;\n\t}\n\n\tpublic void setTokenType(String tokenType) {\n\t\tthis.tokenType = tokenType;\n\t}\n\n\tpublic void setRestTemplate(OAuth2RestOperations restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tpublic void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {\n\t\tthis.authoritiesExtractor = authoritiesExtractor;\n\t}\n\n\t@Override\n\tpublic OAuth2Authentication loadAuthentication(String accessToken)\n\t\t\tthrows AuthenticationException, InvalidTokenException {\n\t\tMap<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);\n\t\tif (map.containsKey(\"error\")) {\n\t\t\tthis.logger.debug(\"userinfo returned error: \" + map.get(\"error\"));\n\t\t\tthrow new InvalidTokenException(accessToken);\n\t\t}\n\t\treturn extractAuthentication(map);\n\t}\n\n\tprivate OAuth2Authentication extractAuthentication(Map<String, Object> map) {\n\t\tObject principal = getPrincipal(map);\n\t\tOAuth2Request request = getRequest(map);\n\t\tList<GrantedAuthority> authorities = this.authoritiesExtractor\n\t\t\t\t.extractAuthorities(map);\n\t\tUsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(\n\t\t\t\tprincipal, \"N/A\", authorities);\n\t\ttoken.setDetails(map);\n\t\treturn new OAuth2Authentication(request, token);\n\t}\n\n\tprivate Object getPrincipal(Map<String, Object> map) {\n\t\tfor (String key : PRINCIPAL_KEYS) {\n\t\t\tif (map.containsKey(key)) {\n\t\t\t\treturn map.get(key);\n\t\t\t}\n\t\t}\n\t\treturn \"unknown\";\n\t}\n\n\t@SuppressWarnings({ \"unchecked\" })\n\tprivate OAuth2Request getRequest(Map<String, Object> map) {\n\t\tMap<String, Object> request = (Map<String, Object>) map.get(\"oauth2Request\");\n\n\t\tString clientId = (String) request.get(\"clientId\");\n\t\tSet<String> scope = new LinkedHashSet<>(request.containsKey(\"scope\") ?\n\t\t\t\t(Collection<String>) request.get(\"scope\") : Collections.<String>emptySet());\n\n\t\treturn new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),\n\t\t\t\tnull, null, null, null);\n\t}\n\n\t@Override\n\tpublic OAuth2AccessToken readAccessToken(String accessToken) {\n\t\tthrow new UnsupportedOperationException(\"Not supported: read access token\");\n\t}\n\n\t@SuppressWarnings({ \"unchecked\" })\n\tprivate Map<String, Object> getMap(String path, String accessToken) {\n\t\tthis.logger.debug(\"Getting user info from: \" + path);\n\t\ttry {\n\t\t\tOAuth2RestOperations restTemplate = this.restTemplate;\n\t\t\tif (restTemplate == null) {\n\t\t\t\tBaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();\n\t\t\t\tresource.setClientId(this.clientId);\n\t\t\t\trestTemplate = new OAuth2RestTemplate(resource);\n\t\t\t}\n\t\t\tOAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()\n\t\t\t\t\t.getAccessToken();\n\t\t\tif (existingToken == null || !accessToken.equals(existingToken.getValue())) {\n\t\t\t\tDefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(\n\t\t\t\t\t\taccessToken);\n\t\t\t\ttoken.setTokenType(this.tokenType);\n\t\t\t\trestTemplate.getOAuth2ClientContext().setAccessToken(token);\n\t\t\t}\n\t\t\treturn restTemplate.getForEntity(path, Map.class).getBody();\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthis.logger.info(\"Could not fetch user details: \" + ex.getClass() + \", \"\n\t\t\t\t\t+ ex.getMessage());\n\t\t\treturn Collections.<String, Object>singletonMap(\"error\",\n\t\t\t\t\t\"Could not fetch user details\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "account-service/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: account-service\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user\n"
  },
  {
    "path": "account-service/src/test/java/com/piggymetrics/account/AccountServiceApplicationTests.java",
    "content": "package com.piggymetrics.account;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class AccountServiceApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\n\t}\n\n}\n"
  },
  {
    "path": "account-service/src/test/java/com/piggymetrics/account/client/StatisticsServiceClientFallbackTest.java",
    "content": "package com.piggymetrics.account.client;\n\nimport com.piggymetrics.account.domain.Account;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.rule.OutputCapture;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport static org.hamcrest.Matchers.containsString;\n\n/**\n * @author cdov\n */\n@RunWith(SpringRunner.class)\n@SpringBootTest(properties = {\n        \"feign.hystrix.enabled=true\"\n})\npublic class StatisticsServiceClientFallbackTest {\n    @Autowired\n    private StatisticsServiceClient statisticsServiceClient;\n\n    @Rule\n    public final OutputCapture outputCapture = new OutputCapture();\n\n    @Before\n    public void setup() {\n        outputCapture.reset();\n    }\n\n    @Test\n    public void testUpdateStatisticsWithFailFallback(){\n        statisticsServiceClient.updateStatistics(\"test\", new Account());\n\n        outputCapture.expect(containsString(\"Error during update statistics for account: test\"));\n\n    }\n\n}\n\n"
  },
  {
    "path": "account-service/src/test/java/com/piggymetrics/account/controller/AccountControllerTest.java",
    "content": "package com.piggymetrics.account.controller;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.collect.ImmutableList;\nimport com.piggymetrics.account.domain.*;\nimport com.piggymetrics.account.service.AccountService;\nimport com.sun.security.auth.UserPrincipal;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\n\nimport java.math.BigDecimal;\nimport java.util.Date;\n\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class AccountControllerTest {\n\n\tprivate static final ObjectMapper mapper = new ObjectMapper();\n\n\t@InjectMocks\n\tprivate AccountController accountController;\n\n\t@Mock\n\tprivate AccountService accountService;\n\n\tprivate MockMvc mockMvc;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t\tthis.mockMvc = MockMvcBuilders.standaloneSetup(accountController).build();\n\t}\n\n\t@Test\n\tpublic void shouldGetAccountByName() throws Exception {\n\n\t\tfinal Account account = new Account();\n\t\taccount.setName(\"test\");\n\n\t\twhen(accountService.findByName(account.getName())).thenReturn(account);\n\n\t\tmockMvc.perform(get(\"/\" + account.getName()))\n\t\t\t\t.andExpect(jsonPath(\"$.name\").value(account.getName()))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldGetCurrentAccount() throws Exception {\n\n\t\tfinal Account account = new Account();\n\t\taccount.setName(\"test\");\n\n\t\twhen(accountService.findByName(account.getName())).thenReturn(account);\n\n\t\tmockMvc.perform(get(\"/current\").principal(new UserPrincipal(account.getName())))\n\t\t\t\t.andExpect(jsonPath(\"$.name\").value(account.getName()))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldSaveCurrentAccount() throws Exception {\n\n\t\tSaving saving = new Saving();\n\t\tsaving.setAmount(new BigDecimal(1500));\n\t\tsaving.setCurrency(Currency.USD);\n\t\tsaving.setInterest(new BigDecimal(\"3.32\"));\n\t\tsaving.setDeposit(true);\n\t\tsaving.setCapitalization(false);\n\n\t\tItem grocery = new Item();\n\t\tgrocery.setTitle(\"Grocery\");\n\t\tgrocery.setAmount(new BigDecimal(10));\n\t\tgrocery.setCurrency(Currency.USD);\n\t\tgrocery.setPeriod(TimePeriod.DAY);\n\t\tgrocery.setIcon(\"meal\");\n\n\t\tItem salary = new Item();\n\t\tsalary.setTitle(\"Salary\");\n\t\tsalary.setAmount(new BigDecimal(9100));\n\t\tsalary.setCurrency(Currency.USD);\n\t\tsalary.setPeriod(TimePeriod.MONTH);\n\t\tsalary.setIcon(\"wallet\");\n\n\t\tfinal Account account = new Account();\n\t\taccount.setName(\"test\");\n\t\taccount.setNote(\"test note\");\n\t\taccount.setLastSeen(new Date());\n\t\taccount.setSaving(saving);\n\t\taccount.setExpenses(ImmutableList.of(grocery));\n\t\taccount.setIncomes(ImmutableList.of(salary));\n\n\t\tString json = mapper.writeValueAsString(account);\n\n\t\tmockMvc.perform(put(\"/current\").principal(new UserPrincipal(account.getName())).contentType(MediaType.APPLICATION_JSON).content(json))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldFailOnValidationTryingToSaveCurrentAccount() throws Exception {\n\n\t\tfinal Account account = new Account();\n\t\taccount.setName(\"test\");\n\n\t\tString json = mapper.writeValueAsString(account);\n\n\t\tmockMvc.perform(put(\"/current\").principal(new UserPrincipal(account.getName())).contentType(MediaType.APPLICATION_JSON).content(json))\n\t\t\t\t.andExpect(status().isBadRequest());\n\t}\n\n\t@Test\n\tpublic void shouldRegisterNewAccount() throws Exception {\n\n\t\tfinal User user = new User();\n\t\tuser.setUsername(\"test\");\n\t\tuser.setPassword(\"password\");\n\n\t\tString json = mapper.writeValueAsString(user);\n\t\tSystem.out.println(json);\n\t\tmockMvc.perform(post(\"/\").principal(new UserPrincipal(\"test\")).contentType(MediaType.APPLICATION_JSON).content(json))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldFailOnValidationTryingToRegisterNewAccount() throws Exception {\n\n\t\tfinal User user = new User();\n\t\tuser.setUsername(\"t\");\n\n\t\tString json = mapper.writeValueAsString(user);\n\n\t\tmockMvc.perform(post(\"/\").principal(new UserPrincipal(\"test\")).contentType(MediaType.APPLICATION_JSON).content(json))\n\t\t\t\t.andExpect(status().isBadRequest());\n\t}\n}\n"
  },
  {
    "path": "account-service/src/test/java/com/piggymetrics/account/repository/AccountRepositoryTest.java",
    "content": "package com.piggymetrics.account.repository;\n\nimport com.piggymetrics.account.domain.Account;\nimport com.piggymetrics.account.domain.Currency;\nimport com.piggymetrics.account.domain.Item;\nimport com.piggymetrics.account.domain.Saving;\nimport com.piggymetrics.account.domain.TimePeriod;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.math.BigDecimal;\nimport java.util.Arrays;\nimport java.util.Date;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(SpringRunner.class)\n@DataMongoTest\npublic class AccountRepositoryTest {\n\n\t@Autowired\n\tprivate AccountRepository repository;\n\n\t@Test\n\tpublic void shouldFindAccountByName() {\n\n\t\tAccount stub = getStubAccount();\n\t\trepository.save(stub);\n\n\t\tAccount found = repository.findByName(stub.getName());\n\t\tassertEquals(stub.getLastSeen(), found.getLastSeen());\n\t\tassertEquals(stub.getNote(), found.getNote());\n\t\tassertEquals(stub.getIncomes().size(), found.getIncomes().size());\n\t\tassertEquals(stub.getExpenses().size(), found.getExpenses().size());\n\t}\n\n\tprivate Account getStubAccount() {\n\n\t\tSaving saving = new Saving();\n\t\tsaving.setAmount(new BigDecimal(1500));\n\t\tsaving.setCurrency(Currency.USD);\n\t\tsaving.setInterest(new BigDecimal(\"3.32\"));\n\t\tsaving.setDeposit(true);\n\t\tsaving.setCapitalization(false);\n\n\t\tItem vacation = new Item();\n\t\tvacation.setTitle(\"Vacation\");\n\t\tvacation.setAmount(new BigDecimal(3400));\n\t\tvacation.setCurrency(Currency.EUR);\n\t\tvacation.setPeriod(TimePeriod.YEAR);\n\t\tvacation.setIcon(\"tourism\");\n\n\t\tItem grocery = new Item();\n\t\tgrocery.setTitle(\"Grocery\");\n\t\tgrocery.setAmount(new BigDecimal(10));\n\t\tgrocery.setCurrency(Currency.USD);\n\t\tgrocery.setPeriod(TimePeriod.DAY);\n\t\tgrocery.setIcon(\"meal\");\n\n\t\tItem salary = new Item();\n\t\tsalary.setTitle(\"Salary\");\n\t\tsalary.setAmount(new BigDecimal(9100));\n\t\tsalary.setCurrency(Currency.USD);\n\t\tsalary.setPeriod(TimePeriod.MONTH);\n\t\tsalary.setIcon(\"wallet\");\n\n\t\tAccount account = new Account();\n\t\taccount.setName(\"test\");\n\t\taccount.setNote(\"test note\");\n\t\taccount.setLastSeen(new Date());\n\t\taccount.setSaving(saving);\n\t\taccount.setExpenses(Arrays.asList(grocery, vacation));\n\t\taccount.setIncomes(Arrays.asList(salary));\n\n\t\treturn account;\n\t}\n}\n"
  },
  {
    "path": "account-service/src/test/java/com/piggymetrics/account/service/AccountServiceTest.java",
    "content": "package com.piggymetrics.account.service;\n\nimport com.piggymetrics.account.client.AuthServiceClient;\nimport com.piggymetrics.account.client.StatisticsServiceClient;\nimport com.piggymetrics.account.domain.*;\nimport com.piggymetrics.account.repository.AccountRepository;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.math.BigDecimal;\nimport java.util.Arrays;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.*;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class AccountServiceTest {\n\n\t@InjectMocks\n\tprivate AccountServiceImpl accountService;\n\n\t@Mock\n\tprivate StatisticsServiceClient statisticsClient;\n\n\t@Mock\n\tprivate AuthServiceClient authClient;\n\n\t@Mock\n\tprivate AccountRepository repository;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t}\n\n\t@Test\n\tpublic void shouldFindByName() {\n\n\t\tfinal Account account = new Account();\n\t\taccount.setName(\"test\");\n\n\t\twhen(accountService.findByName(account.getName())).thenReturn(account);\n\t\tAccount found = accountService.findByName(account.getName());\n\n\t\tassertEquals(account, found);\n\t}\n\n\t@Test(expected = IllegalArgumentException.class)\n\tpublic void shouldFailWhenNameIsEmpty() {\n\t\taccountService.findByName(\"\");\n\t}\n\n\t@Test\n\tpublic void shouldCreateAccountWithGivenUser() {\n\n\t\tUser user = new User();\n\t\tuser.setUsername(\"test\");\n\n\t\tAccount account = accountService.create(user);\n\n\t\tassertEquals(user.getUsername(), account.getName());\n\t\tassertEquals(0, account.getSaving().getAmount().intValue());\n\t\tassertEquals(Currency.getDefault(), account.getSaving().getCurrency());\n\t\tassertEquals(0, account.getSaving().getInterest().intValue());\n\t\tassertEquals(false, account.getSaving().getDeposit());\n\t\tassertEquals(false, account.getSaving().getCapitalization());\n\t\tassertNotNull(account.getLastSeen());\n\n\t\tverify(authClient, times(1)).createUser(user);\n\t\tverify(repository, times(1)).save(account);\n\t}\n\n\t@Test\n\tpublic void shouldSaveChangesWhenUpdatedAccountGiven() {\n\n\t\tItem grocery = new Item();\n\t\tgrocery.setTitle(\"Grocery\");\n\t\tgrocery.setAmount(new BigDecimal(10));\n\t\tgrocery.setCurrency(Currency.USD);\n\t\tgrocery.setPeriod(TimePeriod.DAY);\n\t\tgrocery.setIcon(\"meal\");\n\n\t\tItem salary = new Item();\n\t\tsalary.setTitle(\"Salary\");\n\t\tsalary.setAmount(new BigDecimal(9100));\n\t\tsalary.setCurrency(Currency.USD);\n\t\tsalary.setPeriod(TimePeriod.MONTH);\n\t\tsalary.setIcon(\"wallet\");\n\n\t\tSaving saving = new Saving();\n\t\tsaving.setAmount(new BigDecimal(1500));\n\t\tsaving.setCurrency(Currency.USD);\n\t\tsaving.setInterest(new BigDecimal(\"3.32\"));\n\t\tsaving.setDeposit(true);\n\t\tsaving.setCapitalization(false);\n\n\t\tfinal Account update = new Account();\n\t\tupdate.setName(\"test\");\n\t\tupdate.setNote(\"test note\");\n\t\tupdate.setIncomes(Arrays.asList(salary));\n\t\tupdate.setExpenses(Arrays.asList(grocery));\n\t\tupdate.setSaving(saving);\n\n\t\tfinal Account account = new Account();\n\n\t\twhen(accountService.findByName(\"test\")).thenReturn(account);\n\t\taccountService.saveChanges(\"test\", update);\n\n\t\tassertEquals(update.getNote(), account.getNote());\n\t\tassertNotNull(account.getLastSeen());\n\n\t\tassertEquals(update.getSaving().getAmount(), account.getSaving().getAmount());\n\t\tassertEquals(update.getSaving().getCurrency(), account.getSaving().getCurrency());\n\t\tassertEquals(update.getSaving().getInterest(), account.getSaving().getInterest());\n\t\tassertEquals(update.getSaving().getDeposit(), account.getSaving().getDeposit());\n\t\tassertEquals(update.getSaving().getCapitalization(), account.getSaving().getCapitalization());\n\n\t\tassertEquals(update.getExpenses().size(), account.getExpenses().size());\n\t\tassertEquals(update.getIncomes().size(), account.getIncomes().size());\n\n\t\tassertEquals(update.getExpenses().get(0).getTitle(), account.getExpenses().get(0).getTitle());\n\t\tassertEquals(0, update.getExpenses().get(0).getAmount().compareTo(account.getExpenses().get(0).getAmount()));\n\t\tassertEquals(update.getExpenses().get(0).getCurrency(), account.getExpenses().get(0).getCurrency());\n\t\tassertEquals(update.getExpenses().get(0).getPeriod(), account.getExpenses().get(0).getPeriod());\n\t\tassertEquals(update.getExpenses().get(0).getIcon(), account.getExpenses().get(0).getIcon());\n\t\t\n\t\tassertEquals(update.getIncomes().get(0).getTitle(), account.getIncomes().get(0).getTitle());\n\t\tassertEquals(0, update.getIncomes().get(0).getAmount().compareTo(account.getIncomes().get(0).getAmount()));\n\t\tassertEquals(update.getIncomes().get(0).getCurrency(), account.getIncomes().get(0).getCurrency());\n\t\tassertEquals(update.getIncomes().get(0).getPeriod(), account.getIncomes().get(0).getPeriod());\n\t\tassertEquals(update.getIncomes().get(0).getIcon(), account.getIncomes().get(0).getIcon());\n\t\t\n\t\tverify(repository, times(1)).save(account);\n\t\tverify(statisticsClient, times(1)).updateStatistics(\"test\", account);\n\t}\n\n\t@Test(expected = IllegalArgumentException.class)\n\tpublic void shouldFailWhenNoAccountsExistedWithGivenName() {\n\t\tfinal Account update = new Account();\n\t\tupdate.setIncomes(Arrays.asList(new Item()));\n\t\tupdate.setExpenses(Arrays.asList(new Item()));\n\n\t\twhen(accountService.findByName(\"test\")).thenReturn(null);\n\t\taccountService.saveChanges(\"test\", update);\n\t}\n}\n"
  },
  {
    "path": "account-service/src/test/resources/application.yml",
    "content": "spring:\n  data:\n    mongodb:\n      database: piggymetrics\n      port: 0"
  },
  {
    "path": "account-service/src/test/resources/bootstrap.yml",
    "content": "eureka:\n  client:\n    enabled: false"
  },
  {
    "path": "auth-service/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/auth-service.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/auth-service.jar\"]\n\nEXPOSE 5000"
  },
  {
    "path": "auth-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>auth-service</artifactId>\n\t<version>1.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>auth-service</name>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-data-mongodb</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-security</artifactId>\n\t\t</dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-oauth2</artifactId>\n        </dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-sleuth</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>de.flapdoodle.embed</groupId>\n\t\t\t<artifactId>de.flapdoodle.embed.mongo</artifactId>\n\t\t\t<version>1.50.3</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.jayway.jsonpath</groupId>\n\t\t\t<artifactId>json-path</artifactId>\n\t\t\t<version>2.2.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\t\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>auth-service</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t<version>0.7.6.201602180812</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>report</id>\n\t\t\t\t\t\t<phase>test</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>report</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/AuthApplication.java",
    "content": "package com.piggymetrics.auth;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;\n\n@SpringBootApplication\n@EnableResourceServer\n@EnableDiscoveryClient\n@EnableGlobalMethodSecurity(prePostEnabled = true)\npublic class AuthApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(AuthApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/config/OAuth2AuthorizationConfig.java",
    "content": "package com.piggymetrics.auth.config;\n\nimport com.piggymetrics.auth.service.security.MongoUserDetailsService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.env.Environment;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.crypto.password.NoOpPasswordEncoder;\nimport org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;\nimport org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;\nimport org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;\nimport org.springframework.security.oauth2.provider.token.TokenStore;\nimport org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;\n\n/**\n * @author cdov\n */\n@Configuration\n@EnableAuthorizationServer\npublic class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {\n\n    private TokenStore tokenStore = new InMemoryTokenStore();\n    private final String NOOP_PASSWORD_ENCODE = \"{noop}\";\n\n    @Autowired\n    @Qualifier(\"authenticationManagerBean\")\n    private AuthenticationManager authenticationManager;\n\n    @Autowired\n    private MongoUserDetailsService userDetailsService;\n\n    @Autowired\n    private Environment env;\n\n    @Override\n    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {\n\n        // TODO persist clients details\n\n        // @formatter:off\n        clients.inMemory()\n                .withClient(\"browser\")\n                .authorizedGrantTypes(\"refresh_token\", \"password\")\n                .scopes(\"ui\")\n                .and()\n                .withClient(\"account-service\")\n                .secret(env.getProperty(\"ACCOUNT_SERVICE_PASSWORD\"))\n                .authorizedGrantTypes(\"client_credentials\", \"refresh_token\")\n                .scopes(\"server\")\n                .and()\n                .withClient(\"statistics-service\")\n                .secret(env.getProperty(\"STATISTICS_SERVICE_PASSWORD\"))\n                .authorizedGrantTypes(\"client_credentials\", \"refresh_token\")\n                .scopes(\"server\")\n                .and()\n                .withClient(\"notification-service\")\n                .secret(env.getProperty(\"NOTIFICATION_SERVICE_PASSWORD\"))\n                .authorizedGrantTypes(\"client_credentials\", \"refresh_token\")\n                .scopes(\"server\");\n        // @formatter:on\n    }\n\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        endpoints\n                .tokenStore(tokenStore)\n                .authenticationManager(authenticationManager)\n                .userDetailsService(userDetailsService);\n    }\n\n    @Override\n    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {\n        oauthServer\n                .tokenKeyAccess(\"permitAll()\")\n                .checkTokenAccess(\"isAuthenticated()\")\n                .passwordEncoder(NoOpPasswordEncoder.getInstance());\n    }\n\n}\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/config/WebSecurityConfig.java",
    "content": "package com.piggymetrics.auth.config;\n\nimport com.piggymetrics.auth.service.security.MongoUserDetailsService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\n\n/**\n * @author cdov\n */\n@Configuration\npublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Autowired\n    private MongoUserDetailsService userDetailsService;\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        // @formatter:off\n        http\n                .authorizeRequests().anyRequest().authenticated()\n                .and()\n                .csrf().disable();\n        // @formatter:on\n    }\n\n    @Override\n    protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n        auth.userDetailsService(userDetailsService)\n                .passwordEncoder(new BCryptPasswordEncoder());\n    }\n\n    @Override\n    @Bean\n    public AuthenticationManager authenticationManagerBean() throws Exception {\n        return super.authenticationManagerBean();\n    }\n}"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java",
    "content": "package com.piggymetrics.auth.controller;\n\nimport com.piggymetrics.auth.domain.User;\nimport com.piggymetrics.auth.service.UserService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.validation.Valid;\nimport java.security.Principal;\n\n@RestController\n@RequestMapping(\"/users\")\npublic class UserController {\n\n\t@Autowired\n\tprivate UserService userService;\n\n\t@RequestMapping(value = \"/current\", method = RequestMethod.GET)\n\tpublic Principal getUser(Principal principal) {\n\t\treturn principal;\n\t}\n\n\t@PreAuthorize(\"#oauth2.hasScope('server')\")\n\t@RequestMapping(method = RequestMethod.POST)\n\tpublic void createUser(@Valid @RequestBody User user) {\n\t\tuserService.create(user);\n\t}\n}\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/domain/User.java",
    "content": "package com.piggymetrics.auth.domain;\n\nimport org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.core.userdetails.UserDetails;\n\nimport java.util.List;\n\n@Document(collection = \"users\")\npublic class User implements UserDetails {\n\n\t@Id\n\tprivate String username;\n\n\tprivate String password;\n\n\t@Override\n\tpublic String getPassword() {\n\t\treturn password;\n\t}\n\n\t@Override\n\tpublic String getUsername() {\n\t\treturn username;\n\t}\n\n\t@Override\n\tpublic List<GrantedAuthority> getAuthorities() {\n\t\treturn null;\n\t}\n\n\tpublic void setUsername(String username) {\n\t\tthis.username = username;\n\t}\n\n\tpublic void setPassword(String password) {\n\t\tthis.password = password;\n\t}\n\n\t@Override\n\tpublic boolean isAccountNonExpired() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean isAccountNonLocked() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean isCredentialsNonExpired() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean isEnabled() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/repository/UserRepository.java",
    "content": "package com.piggymetrics.auth.repository;\n\nimport com.piggymetrics.auth.domain.User;\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface UserRepository extends CrudRepository<User, String> {\n\n}\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/service/UserService.java",
    "content": "package com.piggymetrics.auth.service;\n\nimport com.piggymetrics.auth.domain.User;\n\npublic interface UserService {\n\n\tvoid create(User user);\n\n}\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/service/UserServiceImpl.java",
    "content": "package com.piggymetrics.auth.service;\n\nimport com.piggymetrics.auth.domain.User;\nimport com.piggymetrics.auth.repository.UserRepository;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.Assert;\n\nimport java.util.Optional;\n\n@Service\npublic class UserServiceImpl implements UserService {\n\n\tprivate final Logger log = LoggerFactory.getLogger(getClass());\n\n\tprivate static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();\n\n\t@Autowired\n\tprivate UserRepository repository;\n\n\t@Override\n\tpublic void create(User user) {\n\n\t\tOptional<User> existing = repository.findById(user.getUsername());\n\t\texisting.ifPresent(it-> {throw new IllegalArgumentException(\"user already exists: \" + it.getUsername());});\n\n\t\tString hash = encoder.encode(user.getPassword());\n\t\tuser.setPassword(hash);\n\n\t\trepository.save(user);\n\n\t\tlog.info(\"new user has been created: {}\", user.getUsername());\n\t}\n}\n"
  },
  {
    "path": "auth-service/src/main/java/com/piggymetrics/auth/service/security/MongoUserDetailsService.java",
    "content": "package com.piggymetrics.auth.service.security;\n\nimport com.piggymetrics.auth.repository.UserRepository;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class MongoUserDetailsService implements UserDetailsService {\n\n\t@Autowired\n\tprivate UserRepository repository;\n\n\t@Override\n\tpublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n\n\t\treturn repository.findById(username).orElseThrow(()->new UsernameNotFoundException(username));\n\t}\n}\n"
  },
  {
    "path": "auth-service/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: auth-service\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user\n"
  },
  {
    "path": "auth-service/src/test/java/com/piggymetrics/auth/AuthServiceApplicationTests.java",
    "content": "package com.piggymetrics.auth;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class AuthServiceApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "auth-service/src/test/java/com/piggymetrics/auth/controller/UserControllerTest.java",
    "content": "package com.piggymetrics.auth.controller;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.piggymetrics.auth.domain.User;\nimport com.piggymetrics.auth.service.UserService;\nimport com.sun.security.auth.UserPrincipal;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\n\nimport static org.mockito.MockitoAnnotations.initMocks;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class UserControllerTest {\n\n\tprivate static final ObjectMapper mapper = new ObjectMapper();\n\n\t@InjectMocks\n\tprivate UserController accountController;\n\n\t@Mock\n\tprivate UserService userService;\n\n\tprivate MockMvc mockMvc;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t\tthis.mockMvc = MockMvcBuilders.standaloneSetup(accountController).build();\n\t}\n\n\t@Test\n\tpublic void shouldCreateNewUser() throws Exception {\n\n\t\tfinal User user = new User();\n\t\tuser.setUsername(\"test\");\n\t\tuser.setPassword(\"password\");\n\n\t\tString json = mapper.writeValueAsString(user);\n\n\t\tmockMvc.perform(post(\"/users\").contentType(MediaType.APPLICATION_JSON).content(json))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldFailWhenUserIsNotValid() throws Exception {\n\n\t\tfinal User user = new User();\n\t\tuser.setUsername(\"t\");\n\t\tuser.setPassword(\"p\");\n\n\t\tmockMvc.perform(post(\"/users\"))\n\t\t\t\t.andExpect(status().isBadRequest());\n\t}\n\n\t@Test\n\tpublic void shouldReturnCurrentUser() throws Exception {\n\t\tmockMvc.perform(get(\"/users/current\").principal(new UserPrincipal(\"test\")))\n\t\t\t\t.andExpect(jsonPath(\"$.name\").value(\"test\"))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n}\n"
  },
  {
    "path": "auth-service/src/test/java/com/piggymetrics/auth/repository/UserRepositoryTest.java",
    "content": "package com.piggymetrics.auth.repository;\n\nimport com.piggymetrics.auth.domain.User;\nimport com.piggymetrics.auth.service.security.MongoUserDetailsService;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;\nimport org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.Optional;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(SpringRunner.class)\n@DataMongoTest\npublic class UserRepositoryTest {\n\n\t@Autowired\n\tprivate UserRepository repository;\n\n\t@Test\n\tpublic void shouldSaveAndFindUserByName() {\n\n\t\tUser user = new User();\n\t\tuser.setUsername(\"name\");\n\t\tuser.setPassword(\"password\");\n\t\trepository.save(user);\n\n\t\tOptional<User> found = repository.findById(user.getUsername());\n\t\tassertTrue(found.isPresent());\n\t\tassertEquals(user.getUsername(), found.get().getUsername());\n\t\tassertEquals(user.getPassword(), found.get().getPassword());\n\t}\n}\n"
  },
  {
    "path": "auth-service/src/test/java/com/piggymetrics/auth/service/UserServiceTest.java",
    "content": "package com.piggymetrics.auth.service;\n\nimport com.piggymetrics.auth.domain.User;\nimport com.piggymetrics.auth.repository.UserRepository;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.util.Optional;\n\nimport static org.mockito.Mockito.*;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class UserServiceTest {\n\n\t@InjectMocks\n\tprivate UserServiceImpl userService;\n\n\t@Mock\n\tprivate UserRepository repository;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t}\n\n\t@Test\n\tpublic void shouldCreateUser() {\n\n\t\tUser user = new User();\n\t\tuser.setUsername(\"name\");\n\t\tuser.setPassword(\"password\");\n\n\t\tuserService.create(user);\n\t\tverify(repository, times(1)).save(user);\n\t}\n\n\t@Test(expected = IllegalArgumentException.class)\n\tpublic void shouldFailWhenUserAlreadyExists() {\n\n\t\tUser user = new User();\n\t\tuser.setUsername(\"name\");\n\t\tuser.setPassword(\"password\");\n\n\t\twhen(repository.findById(user.getUsername())).thenReturn(Optional.of(new User()));\n\t\tuserService.create(user);\n\t}\n}\n"
  },
  {
    "path": "auth-service/src/test/java/com/piggymetrics/auth/service/security/MongoUserDetailsServiceTest.java",
    "content": "package com.piggymetrics.auth.service.security;\n\nimport com.piggymetrics.auth.domain.User;\nimport com.piggymetrics.auth.repository.UserRepository;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\n\nimport java.util.Optional;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Matchers.any;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class MongoUserDetailsServiceTest {\n\n\t@InjectMocks\n\tprivate MongoUserDetailsService service;\n\n\t@Mock\n\tprivate UserRepository repository;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t}\n\n\t@Test\n\tpublic void shouldLoadByUsernameWhenUserExists() {\n\n\t\tfinal User user = new User();\n\n\t\twhen(repository.findById(any())).thenReturn(Optional.of(user));\n\t\tUserDetails loaded = service.loadUserByUsername(\"name\");\n\n\t\tassertEquals(user, loaded);\n\t}\n\n\t@Test(expected = UsernameNotFoundException.class)\n\tpublic void shouldFailToLoadByUsernameWhenUserNotExists() {\n\t\tservice.loadUserByUsername(\"name\");\n\t}\n}"
  },
  {
    "path": "auth-service/src/test/resources/application.yml",
    "content": "spring:\n  data:\n    mongodb:\n      database: piggymetrics\n      port: 0"
  },
  {
    "path": "auth-service/src/test/resources/bootstrap.yml",
    "content": "eureka:\n  client:\n    enabled: false"
  },
  {
    "path": "config/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/config.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/config.jar\"]\n\nHEALTHCHECK --interval=30s --timeout=30s CMD curl -f http://localhost:8888/actuator/health || exit 1\n\nEXPOSE 8888"
  },
  {
    "path": "config/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>config</artifactId>\n\t<version>1.0.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>config</name>\n\t<description>Configuration Server</description>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-config-server</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-security</artifactId>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>config</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\t\n</project>\n"
  },
  {
    "path": "config/src/main/java/com/piggymetrics/config/ConfigApplication.java",
    "content": "package com.piggymetrics.config;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.config.server.EnableConfigServer;\n\n@SpringBootApplication\n@EnableConfigServer\npublic class ConfigApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(ConfigApplication.class, args);\n\t}\n}\n"
  },
  {
    "path": "config/src/main/java/com/piggymetrics/config/SecurityConfig.java",
    "content": "package com.piggymetrics.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;\n\n/**\n * @author cdov\n */\n@Configuration\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        http.csrf().disable();\n        http\n            .authorizeRequests()\n                .antMatchers(\"/actuator/**\").permitAll()\n                .anyRequest().authenticated()\n            .and()\n                .httpBasic()\n                ;\n    }\n}\n"
  },
  {
    "path": "config/src/main/resources/application.yml",
    "content": "spring:\n  cloud:\n    config:\n      server:\n        native:\n          search-locations: classpath:/shared\n  profiles:\n     active: native\n  security:\n    user:\n      password: ${CONFIG_SERVICE_PASSWORD}\n\nserver:\n  port: 8888\n\n"
  },
  {
    "path": "config/src/main/resources/shared/account-service.yml",
    "content": "security:\n  oauth2:\n    client:\n      clientId: account-service\n      clientSecret: ${ACCOUNT_SERVICE_PASSWORD}\n      accessTokenUri: http://auth-service:5000/uaa/oauth/token\n      grant-type: client_credentials\n      scope: server\n\nspring:\n  data:\n    mongodb:\n      host: account-mongodb\n      username: user\n      password: ${MONGODB_PASSWORD}\n      database: piggymetrics\n      port: 27017\n\nserver:\n  servlet:\n    context-path: /accounts\n  port: 6000\n\nfeign:\n  hystrix:\n    enabled: true"
  },
  {
    "path": "config/src/main/resources/shared/application.yml",
    "content": "logging:\n  level:\n    org.springframework.security: INFO\n\nhystrix:\n  command:\n    default:\n      execution:\n        isolation:\n          thread:\n            timeoutInMilliseconds: 10000\n\neureka:\n  instance:\n    prefer-ip-address: true\n  client:\n    serviceUrl:\n      defaultZone: http://registry:8761/eureka/\n\nsecurity:\n  oauth2:\n    resource:\n      user-info-uri: http://auth-service:5000/uaa/users/current\n\nspring:\n  rabbitmq:\n    host: rabbitmq"
  },
  {
    "path": "config/src/main/resources/shared/auth-service.yml",
    "content": "spring:\n  data:\n    mongodb:\n      host: auth-mongodb\n      username: user\n      password: ${MONGODB_PASSWORD}\n      database: piggymetrics\n      port: 27017\n\nserver:\n  servlet:\n    context-path: /uaa\n  port: 5000\n"
  },
  {
    "path": "config/src/main/resources/shared/gateway.yml",
    "content": "hystrix:\n  command:\n    default:\n      execution:\n        isolation:\n          thread:\n            timeoutInMilliseconds: 20000\n\nribbon:\n  ReadTimeout: 20000\n  ConnectTimeout: 20000\n\nzuul:\n  ignoredServices: '*'\n  host:\n    connect-timeout-millis: 20000\n    socket-timeout-millis: 20000\n\n  routes:\n    auth-service:\n        path: /uaa/**\n        url: http://auth-service:5000\n        stripPrefix: false\n        sensitiveHeaders:\n\n    account-service:\n        path: /accounts/**\n        serviceId: account-service\n        stripPrefix: false\n        sensitiveHeaders:\n\n    statistics-service:\n        path: /statistics/**\n        serviceId: statistics-service\n        stripPrefix: false\n        sensitiveHeaders:\n\n    notification-service:\n        path: /notifications/**\n        serviceId: notification-service\n        stripPrefix: false\n        sensitiveHeaders:\n\nserver:\n  port: 4000\n"
  },
  {
    "path": "config/src/main/resources/shared/monitoring.yml",
    "content": ""
  },
  {
    "path": "config/src/main/resources/shared/notification-service.yml",
    "content": "security:\n  oauth2:\n    client:\n      clientId: notification-service\n      clientSecret: ${NOTIFICATION_SERVICE_PASSWORD}\n      accessTokenUri: http://auth-service:5000/uaa/oauth/token\n      grant-type: client_credentials\n      scope: server\n\nserver:\n  servlet:\n    context-path: /notifications\n  port: 8000\n\nremind:\n  cron: 0 0 0 * * *\n  email:\n    text: \"Hey, {0}! We''ve missed you here on PiggyMetrics. It''s time to check your budget statistics.\\r\\n\\r\\nCheers,\\r\\nPiggyMetrics team\"\n    subject: PiggyMetrics reminder\n\nbackup:\n  cron: 0 0 12 * * *\n  email:\n    text: \"Howdy, {0}. Your account backup is ready.\\r\\n\\r\\nCheers,\\r\\nPiggyMetrics team\"\n    subject: PiggyMetrics account backup\n    attachment: backup.json\n\nspring:\n  data:\n    mongodb:\n      host: notification-mongodb\n      username: user\n      password: ${MONGODB_PASSWORD}\n      database: piggymetrics\n      port: 27017\n  mail:\n    host: smtp.gmail.com\n    port: 465\n    username: dev-user\n    password: dev-password\n    properties:\n      mail:\n        smtp:\n          auth: true\n          socketFactory:\n            port: 465\n            class: javax.net.ssl.SSLSocketFactory\n            fallback: false\n          ssl:\n            enable: true\n"
  },
  {
    "path": "config/src/main/resources/shared/registry.yml",
    "content": "server:\n  port: 8761"
  },
  {
    "path": "config/src/main/resources/shared/statistics-service.yml",
    "content": "security:\n  oauth2:\n    client:\n      clientId: statistics-service\n      clientSecret: ${STATISTICS_SERVICE_PASSWORD}\n      accessTokenUri: http://auth-service:5000/uaa/oauth/token\n      grant-type: client_credentials\n      scope: server\n\nspring:\n  data:\n    mongodb:\n      host: statistics-mongodb\n      username: user\n      password: ${MONGODB_PASSWORD}\n      database: piggymetrics\n      port: 27017\n\nserver:\n  servlet:\n    context-path: /statistics\n  port: 7000\n\nrates:\n  url: https://api.exchangeratesapi.io"
  },
  {
    "path": "config/src/main/resources/shared/turbine-stream-service.yml",
    "content": ""
  },
  {
    "path": "docker-compose.dev.yml",
    "content": "version: '2.1'\nservices:\n  rabbitmq:\n    ports:\n      - 5672:5672\n\n  config:\n    build: config\n    ports:\n      - 8888:8888\n\n  registry:\n    build: registry\n\n  gateway:\n    build: gateway\n\n  auth-service:\n    build: auth-service\n    ports:\n      - 5000:5000\n\n  auth-mongodb:\n    build: mongodb\n    ports:\n      - 25000:27017\n\n  account-service:\n    build: account-service\n    ports:\n      - 6000:6000\n\n  account-mongodb:\n    build: mongodb\n    ports:\n      - 26000:27017\n\n  statistics-service:\n    build: statistics-service\n    ports:\n      - 7000:7000\n\n  statistics-mongodb:\n    build: mongodb\n    ports:\n      - 27000:27017\n\n  notification-service:\n    build: notification-service\n    ports:\n      - 8000:8000\n\n  notification-mongodb:\n    build: mongodb\n    ports:\n      - 28000:27017\n\n  monitoring:\n    build: monitoring\n\n  turbine-stream-service:\n    build: turbine-stream-service"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '2.1'\nservices:\n  rabbitmq:\n    image: rabbitmq:3-management\n    restart: always\n    ports:\n      - 15672:15672\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  config:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n    image: sqshq/piggymetrics-config\n    restart: always\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  registry:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n    image: sqshq/piggymetrics-registry\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    ports:\n      - 8761:8761\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  gateway:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n    image: sqshq/piggymetrics-gateway\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    ports:\n      - 80:4000\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  auth-service:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n      NOTIFICATION_SERVICE_PASSWORD: $NOTIFICATION_SERVICE_PASSWORD\n      STATISTICS_SERVICE_PASSWORD: $STATISTICS_SERVICE_PASSWORD\n      ACCOUNT_SERVICE_PASSWORD: $ACCOUNT_SERVICE_PASSWORD\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n    image: sqshq/piggymetrics-auth-service\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  auth-mongodb:\n    environment:\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n    image: sqshq/piggymetrics-mongodb\n    restart: always\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  account-service:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n      ACCOUNT_SERVICE_PASSWORD: $ACCOUNT_SERVICE_PASSWORD\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n    image: sqshq/piggymetrics-account-service\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  account-mongodb:\n    environment:\n      INIT_DUMP: account-service-dump.js\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n    image: sqshq/piggymetrics-mongodb\n    restart: always\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  statistics-service:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n      STATISTICS_SERVICE_PASSWORD: $STATISTICS_SERVICE_PASSWORD\n    image: sqshq/piggymetrics-statistics-service\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  statistics-mongodb:\n    environment:\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n    image: sqshq/piggymetrics-mongodb\n    restart: always\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  notification-service:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n      NOTIFICATION_SERVICE_PASSWORD: $NOTIFICATION_SERVICE_PASSWORD\n    image: sqshq/piggymetrics-notification-service\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  notification-mongodb:\n    image: sqshq/piggymetrics-mongodb\n    restart: always\n    environment:\n      MONGODB_PASSWORD: $MONGODB_PASSWORD\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  monitoring:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n    image: sqshq/piggymetrics-monitoring\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    ports:\n      - 9000:8080\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n\n  turbine-stream-service:\n    environment:\n      CONFIG_SERVICE_PASSWORD: $CONFIG_SERVICE_PASSWORD\n    image: sqshq/piggymetrics-turbine-stream-service\n    restart: always\n    depends_on:\n      config:\n        condition: service_healthy\n    ports:\n    - 8989:8989\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"10\"\n"
  },
  {
    "path": "gateway/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/gateway.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/gateway.jar\"]\n\nEXPOSE 4000"
  },
  {
    "path": "gateway/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>gateway</artifactId>\n\t<version>1.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>gateway</name>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-zuul</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-sleuth</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\t\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>${project.name}</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "gateway/src/main/java/com/piggymetrics/gateway/GatewayApplication.java",
    "content": "package com.piggymetrics.gateway;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.cloud.netflix.zuul.EnableZuulProxy;\n\n@SpringBootApplication\n@EnableDiscoveryClient\n@EnableZuulProxy\npublic class GatewayApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(GatewayApplication.class, args);\n\t}\n}\n"
  },
  {
    "path": "gateway/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: gateway\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user\n"
  },
  {
    "path": "gateway/src/main/resources/static/attribution.html",
    "content": "<!DOCTYPE html>\n\n<htmL>\n<head>\n  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n  <meta name=\"viewport\" content=\"width=1150, user-scalable=no\">\n  <title>Piggy Metrics</title>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"css/launch.css\">\n</head>\n<body>\n\n<a href=\"index.html\"><div id=\"piggy\"></div></a>\n<div class=\"CCattribution\">\n  <a href=\"http://creativecommons.org\">Creative Commons</a> – Attribution (CC BY 3.0)\n  <br>\n  Thanks a lot for icons from <a href=\"http://thenounproject.com\">The Noun Project</a> collection.\n  <br>\n  <br>\n  Here's the list of all used icons:\n  <br>\n  Piggy Bank designed by Jezmael Basilio\n  <br>\n  Arrow designed by Jardson A.\n  <br>\n  Wallet designed by Luis Prado\n  <br>\n  Analytics designed by Aneeque Ahmed\n  <br>\n  Piggy Bank designed by Michelle Ann\n  <br>\n  Light Bulb designed by Chris Brunskill\n  <br>\n  Speech Bubble designed by Cengiz SARI\n  <br>\n  Bag designed by Agus Purwanto\n  <br>\n  Analytics designed by Luboš Volkov\n  <br>\n  College Tuition designed by Rediffusion\n  <br>\n  Marijuana designed by Gareth\n  <br>\n  Stroller designed by Edward Boatman\n  <br>\n  Television designed by Piero Borgo\n  <br>\n  Island designed by Bohdan Burmich\n  <br>\n  Light Bulb designed by Rémy Médard\n  <br>\n  Shirt designed by Megan Sheehan\n  <br>\n  Telephone designed by Ian Mawle\n  <br>\n  Shopping Cart designed by Megan Sheehan\n  <br>\n  Gas designed by Jon Testa\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "gateway/src/main/resources/static/css/animation.css",
    "content": "/* ZOOM AVATAR */\n@-webkit-keyframes zoomavatar {\n\t0% {\n\t\t-webkit-transform: scale(0.1);\n\t}\n\t60% {\n\t\t-webkit-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-webkit-transform: scale(1);\n\t}\n}\n@-moz-keyframes zoomavatar {\n\t0% {\n\t\t-moz-transform: scale(0.1);\n\t}\n\t80% {\n\t\t-moz-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-moz-transform: scale(1);\n\t}\n}\n@-o-keyframes zoomavatar {\n\t0% {\n\t\t-o-transform: scale(0.1);\n\t}\n\t60% {\n\t\t-o-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-o-transform: scale(1);\n\t}\n}\n@-ms-keyframes zoomavatar {\n\t0% {\n\t\t-ms-transform: scale(0.1);\n\t}\n\t60% {\n\t\t-ms-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-ms-transform: scale(1);\n\t}\n}\n@keyframes zoomavatar {\n\t0% {\n\t\ttransform: scale(0.1);\n\t}\n\t60% {\n\t\ttransform: scale(1.3);\n\t}\n\t100% {\n\t\ttransform: scale(1);\n\t}\n}\n/* UNZOOM AVATAR */\n@-webkit-keyframes unzoomavatar {\n\t0% {\n\t\t-webkit-transform: scale(1);\n\t}\n\t40% {\n\t\t-webkit-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-webkit-transform: scale(0.1);\n\t}\n}\n@-moz-keyframes unzoomavatar {\n\t0% {\n\t\t-moz-transform: scale(1);\n\t}\n\t40% {\n\t\t-moz-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-moz-transform: scale(0.1);\n\t}\n}\n@-o-keyframes unzoomavatar {\n\t0% {\n\t\t-o-transform: scale(1);\n\t}\n\t40% {\n\t\t-o-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-o-transform: scale(0.1);\n\t}\n}\n@-ms-keyframes unzoomavatar {\n\t0% {\n\t\t-ms-transform: scale(1);\n\t}\n\t40% {\n\t\t-ms-transform: scale(1.3);\n\t}\n\t100% {\n\t\t-ms-transform: scale(0.1);\n\t}\n}\n@keyframes unzoomavatar {\n\t0% {\n\t\ttransform: scale(1);\n\t}\n\t40% {\n\t\ttransform: scale(1.3);\n\t}\n\t100% {\n\t\ttransform: scale(0.1);\n\t}\n}\n/* GO LEFT */\n@-webkit-keyframes goleft {\n\t0% {\n\t\tright: 100px; left:0%;\n\t}\n\t60% {\n\t\tright: 300px; left:-20%;\n\t}\n\t100% {\n\t\tright: 300px; left:-18%;\n\t}\n}\n@-moz-keyframes goleft {\n\t0% {\n\t\tright: 100px; left:0%;\n\t}\n\t60% {\n\t\tright: 300px; left:-20%;\n\t}\n\t100% {\n\t\tright: 300px; left:-18%;\n\t}\n}\n@-o-keyframes goleft {\n\t0% {\n\t\tright: 100px; left:0%;\n\t}\n\t60% {\n\t\tright: 300px; left:-20%;\n\t}\n\t100% {\n\t\tright: 300px; left:-18%;\n\t}\n}\n@-ms-keyframes goleft {\n\t0% {\n\t\tright: 100px; left:0%;\n\t}\n\t60% {\n\t\tright: 300px; left:-20%;\n\t}\n\t100% {\n\t\tright: 300px; left:-18%;\n\t}\n}\n@keyframes goleft {\n\t0% {\n\t\tright: 100px; left:0%;\n\t}\n\t60% {\n\t\tright: 300px; left:-20%;\n\t}\n\t100% {\n\t\tright: 300px; left:-18%;\n\t}\n}\n/* GO RIGHT */\n@-webkit-keyframes goright {\n\t0% {\n\t\tright: -100px; left:0%;\n\t}\n\t60% {\n\t\tright: -300px; left:20%;\n\t}\n\t100% {\n\t\tright: -300px; left:18%;\n\t}\n}\n@-moz-keyframes goright {\n\t0% {\n\t\tright: -100px; left:0%;\n\t}\n\t60% {\n\t\tright: -300px; left:20%;\n\t}\n\t100% {\n\t\tright: -300px; left:18%;\n\t}\n}\n@-o-keyframes goright {\n\t0% {\n\t\tright: -100px; left:0%;\n\t}\n\t60% {\n\t\tright: -300px; left:20%;\n\t}\n\t100% {\n\t\tright: -300px; left:18%;\n\t}\n}\n@-ms-keyframes goright {\n\t0% {\n\t\tright: -100px; left:0%;\n\t}\n\t60% {\n\t\tright: -300px; left:20%;\n\t}\n\t100% {\n\t\tright: -300px; left:18%;\n\t}\n}\n@keyframes goright {\n\t0% {\n\t\tright: -100px; left:0%;\n\t}\n\t60% {\n\t\tright: -300px; left:20%;\n\t}\n\t100% {\n\t\tright: -300px; left:18%;\n\t}\n}\n/* GO DOWN */\n@-webkit-keyframes godown {\n\t0% {\n\t\tbottom:20%;\n\t}\n\t60% {\n\t\tbottom:4%;\n\t}\n\t100% {\n\t\tbottom:8%;\n\t}\n}\n@-moz-keyframes godown {\n\t0% {\n\t\tbottom:20%;\n\t}\n\t60% {\n\t\tbottom:4%;\n\t}\n\t100% {\n\t\tbottom:8%;\n\t}\n}\n@-o-keyframes godown {\n\t0% {\n\t\tbottom:20%;\n\t}\n\t60% {\n\t\tbottom:4%;\n\t}\n\t100% {\n\t\tbottom:8%;\n\t}\n}\n@-ms-keyframes godown {\n\t0% {\n\t\tbottom:20%;\n\t}\n\t60% {\n\t\tbottom:4%;\n\t}\n\t100% {\n\t\tbottom:8%;\n\t}\n}\n@keyframes godown {\n\t0% {\n\t\tbottom:20%;\n\t}\n\t60% {\n\t\tbottom:4%;\n\t}\n\t100% {\n\t\tbottom:8%;\n\t}\n}\n/* PLUS */\n@-webkit-keyframes plus {\n\t0% {\n\t\t-webkit-transform: scale(0.85);\n\t}\n\t8% {\n\t\t-webkit-transform: scale(1);\n\t}\n\t80% {\n\t\t-webkit-transform: scale(0.85);\n\t}\n\t100% {\n\t\t-webkit-transform: scale(0.85);\n\t}\n}\n@-moz-keyframes plus {\n\t0% {\n\t\t-moz-transform: scale(0.85);\n\t}\n\t8% {\n\t\t-moz-transform: scale(1);\n\t}\n\t80% {\n\t\t-moz-transform: scale(0.85);\n\t}\n\t100% {\n\t\t-moz-transform: scale(0.85);\n\t}\n}\n@-o-keyframes plus {\n\t0% {\n\t\t-o-transform: scale(0.85);\n\t}\n\t8% {\n\t\t-o-transform: scale(1);\n\t}\n\t80% {\n\t\t-o-transform: scale(0.85);\n\t}\n\t100% {\n\t\t-o-transform: scale(0.85);\n\t}\n}\n@-ms-keyframes plus {\n\t0% {\n\t\t-ms-transform: scale(0.85);\n\t}\n\t8% {\n\t\t-ms-transform: scale(1);\n\t}\n\t80% {\n\t\t-ms-transform: scale(0.85);\n\t}\n\t100% {\n\t\t-ms-transform: scale(0.85);\n\t}\n}\n@keyframes plus {\n\t0% {\n\t\ttransform: scale(0.85);\n\t}\n\t8% {\n\t\ttransform: scale(1);\n\t}\n\t80% {\n\t\ttransform: scale(0.85);\n\t}\n\t100% {\n\t\ttransform: scale(0.85);\n\t}\n}\n\n/* SLIDER */\n@-webkit-keyframes endoflist {\n\t0% {\n\t\t-webkit-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-webkit-transform: translateY(-60px);\n\t}\n\t100% {\n\t\t-webkit-transform: translateY(0px);\n\t}\n}\n@-moz-keyframes endoflist {\n\t0% {\n\t\t-moz-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-moz-transform: translateY(-60px);\n\t}\n\t100% {\n\t\t-moz-transform: translateY(0px);\n\t}\n}\n@-o-keyframes endoflist {\n\t0% {\n\t\t-o-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-o-transform: translateY(-60px);\n\t}\n\t100% {\n\t\t-o-transform: translateY(0px);\n\t}\n}\n@-ms-keyframes endoflist {\n\t0% {\n\t\t-ms-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-ms-transform: translateY(-60px);\n\t}\n\t100% {\n\t\t-ms-transform: translateY(0px);\n\t}\n}\n@keyframes endoflist {\n\t0% {\n\t\ttransform: translateY(0px);\n\t}\n\t20% {\n\t\ttransform: translateY(-60px);\n\t}\n\t100% {\n\t\ttransform: translateY(0px);\n\t}\n}\n\n@-webkit-keyframes startoflist {\n\t0% {\n\t\t-webkit-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-webkit-transform: translateY(60px);\n\t}\n\t100% {\n\t\t-webkit-transform: translateY(0px);\n\t}\n}\n@-moz-keyframes startoflist {\n\t0% {\n\t\t-moz-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-moz-transform: translateY(60px);\n\t}\n\t100% {\n\t\t-moz-transform: translateY(0px);\n\t}\n}\n@-o-keyframes startoflist {\n\t0% {\n\t\t-o-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-o-transform: translateY(60px);\n\t}\n\t100% {\n\t\t-o-transform: translateY(0px);\n\t}\n}\n@-ms-keyframes startoflist {\n\t0% {\n\t\t-ms-transform: translateY(0px);\n\t}\n\t20% {\n\t\t-ms-transform: translateY(60px);\n\t}\n\t100% {\n\t\t-ms-transform: translateY(0px);\n\t}\n}\n@keyframes startoflist {\n\t0% {\n\t\ttransform: translateY(0px);\n\t}\n\t20% {\n\t\ttransform: translateY(60px);\n\t}\n\t100% {\n\t\ttransform: translateY(0px);\n\t}\n}\n\n\n@-webkit-keyframes frameanimate {\n\t0% {\n\t\t-webkit-transform: translateY(-284px);\n\t}\n\t50% {\n\t\t-webkit-transform: translateY(50px);\n\t}\n\t100% {\n\t\t-webkit-transform: translateY(0px);\n\t}\n}\n@-moz-keyframes frameanimate {\n\t0% {\n\t\t-moz-transform: translateY(-284px);\n\t}\n\t50% {\n\t\t-moz-transform: translateY(100px);\n\t}\n\t100% {\n\t\t-moz-transform: translateY(0px);\n\t}\n}\n@-o-keyframes frameanimate {\n\t0% {\n\t\t-o-transform: translateY(-284px);\n\t}\n\t50% {\n\t\t-o-transform: translateY(100px);\n\t}\n\t100% {\n\t\t-o-transform: translateY(0px);\n\t}\n}\n@-ms-keyframes frameanimate {\n\t0% {\n\t\t-ms-transform: translateY(-284px);\n\t}\n\t50% {\n\t\t-ms-transform: translateY(100px);\n\t}\n\t100% {\n\t\t-ms-transform: translateY(0px);\n\t}\n}\n@keyframes frameanimate {\n\t0% {\n\t\ttransform: translateY(-284px);\n\t}\n\t50% {\n\t\ttransform: translateY(100px);\n\t}\n\t100% {\n\t\ttransform: translateY(0px);\n\t}\n}\n\n/*MODAL WINDOWS*/\n\n/* MODAL FORWARD */\n@-webkit-keyframes modalforward {\n\t0% {\n\t\t-webkit-transform: scale(0.4);\n\t}\n\t60% {\n\t\t-webkit-transform: scale(1.1);\n\t}\n\t100% {\n\t\t-webkit-transform: scale(1);\n\t}\n}\n@-moz-keyframes modalforward {\n\t0% {\n\t\t-moz-transform: scale(0.4);\n\t}\n\t80% {\n\t\t-moz-transform: scale(1.1);\n\t}\n\t100% {\n\t\t-moz-transform: scale(1);\n\t}\n}\n@-o-keyframes modalforward {\n\t0% {\n\t\t-o-transform: scale(0.4);\n\t}\n\t60% {\n\t\t-o-transform: scale(1.1);\n\t}\n\t100% {\n\t\t-o-transform: scale(1);\n\t}\n}\n@-ms-keyframes modalforward {\n\t0% {\n\t\t-ms-transform: scale(0.4);\n\t}\n\t60% {\n\t\t-ms-transform: scale(1.1);\n\t}\n\t100% {\n\t\t-ms-transform: scale(1);\n\t}\n}\n@keyframes modalforward {\n\t0% {\n\t\ttransform: scale(0.4);\n\t}\n\t60% {\n\t\ttransform: scale(1.1);\n\t}\n\t100% {\n\t\ttransform: scale(1);\n\t}\n}\n/* UNZOOM REVERSE */\n@-webkit-keyframes modalreverse {\n\t0% {\n\t\t-webkit-transform: scale(1);\n\t}\n\t40% {\n\t\t-webkit-transform: scale(1.06);\n\t}\n\t100% {\n\t\t-webkit-transform: scale(0.6);\n\t}\n}\n@-moz-keyframes modalreverse {\n\t0% {\n\t\t-moz-transform: scale(1);\n\t}\n\t40% {\n\t\t-moz-transform: scale(1.06);\n\t}\n\t100% {\n\t\t-moz-transform: scale(0.6);\n\t}\n}\n@-o-keyframes modalreverse {\n\t0% {\n\t\t-o-transform: scale(1);\n\t}\n\t40% {\n\t\t-o-transform: scale(1.06);\n\t}\n\t100% {\n\t\t-o-transform: scale(0.6);\n\t}\n}\n@-ms-keyframes modalreverse {\n\t0% {\n\t\t-ms-transform: scale(1);\n\t}\n\t40% {\n\t\t-ms-transform: scale(1.06);\n\t}\n\t100% {\n\t\t-ms-transform: scale(0.6);\n\t}\n}\n@keyframes modalreverse {\n\t0% {\n\t\ttransform: scale(1);\n\t}\n\t40% {\n\t\ttransform: scale(1.06);\n\t}\n\t100% {\n\t\ttransform: scale(0.6);\n\t}\n}\n/* MODAL VALUE ERROR */\n@-webkit-keyframes modalvalueerror {\n\t0% {\n\t\t-webkit-transform: scale(1);\n\t}\n\t50% {\n\t\t-webkit-transform: scale(1.5);\n\t}\n\t100% {\n\t\t-webkit-transform: scale(1);\n\t}\n}\n@-moz-keyframes modalvalueerror {\n\t0% {\n\t\t-moz-transform: scale(1);\n\t}\n\t40% {\n\t\t-moz-transform: scale(1.5);\n\t}\n\t100% {\n\t\t-moz-transform: scale(1);\n\t}\n}\n@-o-keyframes modalvalueerror {\n\t0% {\n\t\t-o-transform: scale(1);\n\t}\n\t40% {\n\t\t-o-transform: scale(1.5);\n\t}\n\t100% {\n\t\t-o-transform: scale(1);\n\t}\n}\n@-ms-keyframes modalvalueerror {\n\t0% {\n\t\t-ms-transform: scale(1);\n\t}\n\t40% {\n\t\t-ms-transform: scale(1.5);\n\t}\n\t100% {\n\t\t-ms-transform: scale(1);\n\t}\n}\n@keyframes modalvalueerror {\n\t0% {\n\t\ttransform: scale(1);\n\t}\n\t40% {\n\t\ttransform: scale(1.5);\n\t}\n\t100% {\n\t\ttransform: scale(1);\n\t}\n}\n/* NEW ITEM ADDED */\n@-webkit-keyframes newitemadded {\n\t20% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t70% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t100% {\n\t\tbackground-color: white;\n\t}\n}\n@-moz-keyframes newitemadded {\n\t30% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t70% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t100% {\n\t\tbackground-color: white;\n\t}\n}\n@-o-keyframes newitemadded {\n\t30% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t70% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t100% {\n\t\tbackground-color: white;\n\t}\n}\n@-ms-keyframes newitemadded {\n\t30% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t70% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t100% {\n\t\tbackground-color: white;\n\t}\n}\n@keyframes newitemadded {\n\t30% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t70% {\n\t\tbackground-color: #f2f2f2;\n\t}\n\t100% {\n\t\tbackground-color: white;\n\t}\n}\n/* BUBBLE */\n\n@-webkit-keyframes bubble {\n\t5% {\n\t\topacity: 0;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n\t10% {\n\t\topacity: 0;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t40% {\n\t\topacity: 1;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t80% {\n\t\topacity: 1;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t90% {\n\t\topacity: 0;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t95% {\n\t\topacity: 0;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n\t100% {\n\t\topacity: 1;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n}\n@-moz-keyframes bubble {\n\t5% {\n\t\topacity: 0;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n\t10% {\n\t\topacity: 0;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t40% {\n\t\topacity: 1;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t80% {\n\t\topacity: 1;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t90% {\n\t\topacity: 0;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t95% {\n\t\topacity: 0;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n\t100% {\n\t\topacity: 1;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n}\n@keyframes bubble {\n\t5% {\n\t\topacity: 0;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n\t10% {\n\t\topacity: 0;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t40% {\n\t\topacity: 1;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t80% {\n\t\topacity: 1;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t90% {\n\t\topacity: 0;\n\t\tbackground-position: -240px 0;\n\t\ttop: 12px; right: 12px;\n\t}\n\t95% {\n\t\topacity: 0;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n\t100% {\n\t\topacity: 1;\n\t\tbackground-position: -280px 0;\n\t\ttop: 21px; right: 10px;\n\t}\n}\n@-webkit-keyframes spincircle {\n\t0% {\n\t\t-webkit-transform: rotate(0deg);\n\t}\n\t95% {\n\t\t-webkit-transform: rotate(360deg);\n\t}\n\t100% {\n\t\t-webkit-transform: rotate(370deg);\n\t}\n}\n@-moz-keyframes spincircle {\n\t0% {\n\t\t-moz-transform: rotate(0deg);\n\t}\n\t95% {\n\t\t-moz-transform: rotate(360deg);\n\t}\n\t100% {\n\t\t-moz-transform: rotate(370deg);\n\t}\n}\n@-o-keyframes spincircle {\n\t0% {\n\t\t-o-transform: rotate(0deg);\n\t}\n\t95% {\n\t\t-o-transform: rotate(360deg);\n\t}\n\t100% {\n\t\t-o-transform: rotate(370deg);\n\t}\n}\n@-ms-keyframes spincircle {\n\t0% {\n\t\t-ms-transform: rotate(0deg);\n\t}\n\t95% {\n\t\t-ms-transform: rotate(360deg);\n\t}\n\t100% {\n\t\t-ms-transform: rotate(370deg);\n\t}\n}\n@keyframes spincircle {\n\t0% {\n\t\ttransform: rotate(0deg);\n\t}\n\t95% {\n\t\t-transform: rotate(360deg);\n\t}\n\t100% {\n\t\ttransform: rotate(370deg);\n\t}\n}\n"
  },
  {
    "path": "gateway/src/main/resources/static/css/launch.css",
    "content": "#createaccount, #enter, #secondenter, #info, #backlogin, #backpassword, #plusavatar, .columnimage  {\n\tbackground: url(\"../images/1pagesprites.png\") no-repeat;\n\tbackground-size: 650px 197px;\n}\n\n@media only screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 144dpi) {\n\t#createaccount, #enter, #secondenter, #info, #backlogin, #backpassword, #plusavatar, .columnimage {\n\t\tbackground: url(\"../images/1pagesprites@2x.png\") no-repeat;\n\t\tbackground-size: 650px 197px;\n\t}\n}\n\n/*========================== CARDS WRAPPER ===========================*/\n\n#loginpage {\n\ttop:0; bottom: 0; left:0; right: 0;\n\tposition: absolute;\n\tmargin: auto;\n\twidth: 300px;\n\theight: 500px;\n\t-webkit-perspective: 1000px;\n\t-moz-perspective: 1000px;\n\t-ms-perspective: 1000px;\n\t-o-perspective: 1000px;\n\tperspective: 1000px;\n\tdisplay: none;\n}\n.CCattribution {\n\tposition: relative;\n\ttop: 50px;\n\ttext-align: center;\n\tfont-family: \"Helvetica\", \"Arial\";\n\tfont-size: 12px;\n\tline-height: 30px;\n\tcolor: #525252;\n\tpadding-bottom: 90px;\n}\n.CCattribution a {\n\tcolor: #7ba1b4;\n}\n\n#loginpage, .front, .back {\n\twidth: 300px;\n\theight: 500px;\n}\n#flipper {\n\t-webkit-transform-style: preserve-3d;\n\t-moz-transform-style: preserve-3d;\n\t-ms-transform-style: preserve-3d;\n\t-o-transform-style: preserve-3d;\n\ttransform-style: preserve-3d;\n\t-moz-backface-visibility: hidden;\n\t-webkit-transition: 1000ms;\n\t-moz-transition: 1000ms;\n\t-ms-transition: 1000ms;\n\t-o-transition: 1000ms;\n\ttransition: 1000ms;\n\tposition: relative;\n\toverflow: visible;\n}\n.flippedcardinfo {\n\t-webkit-transform: rotateY(180deg);\n\t-moz-transform: rotateY(180deg);\n\t-ms-transform: rotateY(180deg);\n\t-o-transform: rotateY(180deg);\n\ttransform: rotateY(180deg);\n}\n.FRONTCARD, .BACKCARD {\n\t-webkit-backface-visibility: hidden;\n\t-moz-backface-visibility: hidden;\n\t-ms-backface-visibility: hidden;\n\t-o-backface-visibility: hidden;\n\tbackface-visibility: hidden;\n\toverflow: visible;\n\ttop:0; bottom: 0; left:0; right: 0;\n\tposition: absolute;\n\ttext-align: center;\n\twidth: 300px;\n}\n.fliptext {\n\tposition: absolute;\n\ttext-transform: uppercase;\n\tleft: 0px; right: 0px; bottom: -10px;\n\theight: 30px;\n\tfont-size: 11px;\n\tcolor: #6e6e6e;\n\tline-height: 0.95em;\n\tdisplay: inline-block;\n\tfont-family: Arial;\n\tbackground-color: white;\n}\n.fliptext a{\n\tcolor: #96bcce;\n\ttext-decoration: none;\n\tborder-bottom: 1px dashed #96bcce;\n}\n.fliptext a:hover{\n\tcolor: #7ba1b4;\n\ttext-decoration: none;\n\tborder-bottom: 1px dashed #7ba1b4;\n}\n\n/*================================= FIRST CARD =================================*/\n.FRONTCARD {\n\tz-index: 2;\n\theight: 450px;\n\tbackground-color: white;\n\t-webkit-transform: rotateY(0deg);\n\t-moz-transform: rotateY(0deg);\n\t-ms-transform: rotateY(0deg);\n\t-o-transform: rotateY(0deg);\n\ttransform: rotateY(0deg);\n}\n.auto-shake{\n\t-webkit-animation: 1s auto-shake ease-out;\n\t-moz-animation: 1s auto-shake ease-out;\n\t-o-animation: 1s auto-shake ease-out;\n\t-ms-animation: 1s auto-shake ease-out;\n\tanimation: 1s auto-shake ease-out;\n}\n.hover-shake{\n\t-webkit-animation: 1.6s hover-shake ease-out;\n\t-moz-animation: 1.6s hover-shake ease-out;\n\t-o-animation: 1.6s hover-shake ease-out;\n\t-ms-animation: 1.6s hover-shake ease-out;\n\tanimation: 1.6s hover-shake ease-out;\n}\n.loadingspin{\n\t-webkit-animation: 2s loadingspin;\n\t-moz-animation: 2s loadingspin;\n\t-o-animation: 2s loadingspin;\n\t-ms-animation: 2s loadingspin;\n\tanimation: 2s loadingspin;\n}\n#piggy {\n\tdisplay: block;\n\tbackground: url(\"../images/piggy_large.gif\") no-repeat;\n\tbackground-size: 178px 178px;\n\twidth: 178px;\n\theight: 178px;\n\tposition: relative;\n\tmargin: auto;\n\tz-index: 50;\n}\n#enter, #secondenter {\n\t-webkit-animation: enterarrow 2s infinite;\n\t-moz-animation: enterarrow 2s infinite;\n\t-o-animation: enterarrow 2s infinite;\n\t-ms-animation: enterarrow 2s infinite;\n\tanimation: enterarrow 2s infinite;\n\tbackground-position: -190px -60px;\n\tmargin-left: 222px;\n\tmargin-top: 18px;\n\twidth: 52px;\n\theight: 12px;\n\tposition: absolute;\n\tcursor: pointer;\n\tdisplay: none;\n\tz-index: 7;\n}\n#secondenter{\n\tposition: absolute;\n\tbottom: 85px;\n\tz-index: 4;\n}\n#preloader{\n\tbackground: url(\"../images/preloader.gif\") center no-repeat;\n\tbackground-size: 18px 18px;\n\tmargin-left: 210px;\n\tbottom: 69px;\n\tposition: absolute;\n\tdisplay: none;\n\twidth: 52px;\n\theight: 46px;\n\tz-index: 5;\n}\n\n/*FLIP BUTTON*/\n#wrapper {\n\tdisplay: block;\n\tfont-size: 13px;\n\tcolor: black;\n\tposition: absolute;\n\ttop: 335px;\n\twidth: 300px;\n\theight: 46px;\n\t-webkit-perspective: 1100px;\n\t-moz-perspective: 1100px;\n\t-ms-perspective: 1100px;\n\t-o-perspective: 1100px;\n\tperspective: 1100px;\n\t-ms-transform: rotated3(1,0,0,0);\n\t-webkit-transform-origin: 50% 46px 0;\n\t-moz-transform-origin: 50% 46px 0;\n\t-ms-transform-origin: 50% 46px 0;\n\t-o-transform-origin: 50% 46px 0;\n\ttransform-origin: 10% 46px 0;\n\tz-index: 3;\n}\n#cube {\n\tposition: absolute;\n\twidth: 300px;\n\theight: 46px;\n\t-webkit-transform-style: preserve-3d;\n\t-moz-transform-style: preserve-3d;\n\t-ms-transform-style: preserve-3d;\n\t-o-transform-style: preserve-3d;\n\ttransform-style: preserve-3d;\n\n\t-webkit-transition: all 400ms cubic-bezier(0.67, 0.07, 0.50, 0.88);\n\t-moz-transition: all 400ms cubic-bezier(0.67, 0.07, 0.50, 0.88);\n\t-ms-transition: all 400ms cubic-bezier(0.67, 0.07, 0.50, 0.88);\n\t-o-transition: all 400ms cubic-bezier(0.67, 0.07, 0.50, 0.88);\n\ttransition: all 400ms cubic-bezier(0.67, 0.07, 0.50, 0.88);\n}\n.flippedform{\n\t-webkit-transform-origin: 0 46px -46px;\n\t-moz-transform-origin: 0 46px -46px;\n\t-ms-transform-origin: 0 46px -46px;\n\t-o-transform-origin: 0 46px -46px;\n\ttransform-origin: 0 46px -46px;\n\t-webkit-transform: rotate3d(1,0,0,90deg);\n\t-moz-transform: rotate3d(1,0,0,90deg);\n\t-ms-transform: rotate3d(1,0,0,90deg);\n\t-o-transform: rotate3d(1,0,0,90deg);\n\ttransform: rotate3d(1,0,0,90deg);\n}\n.side {\n\toutline: 3px double #a6ccd9;\n\ttext-indent: 15px;\n\twidth: 300px;\n\theight: 46px;\n\tline-height: 48px;\n\tposition: absolute;\n}\n#backlogin, #backpassword  {\n\tdisplay: block;\n\tposition: absolute;\n\tleft: 66px;\n\ttop: 12px;\n\tborder-color: 1px solid;\n\twidth: 22px;\n\theight: 20px;\n}\n#backlogin {\n\tbackground-position: -250px -24px;\n}\n#backpassword {\n\tbackground-position: -250px 0;\n}\n#side1 {\n\tbackground-position: -250px -24px;\n\tbackground-color: white;\n\twidth: 300px;\n\theight: 46px;\n}\n#side2 {\n\tbackground-color: white;\n\t-webkit-transform: translateZ(-23px) translateY(23px) rotateX(-90deg);\n\t-moz-transform: translateZ(-23px) translateY(23px) rotateX(-90deg);\n\t-ms-transform: translateZ(-23px) translateY(23px) rotateX(-90deg);\n\t-o-transform: translateZ(-23px) translateY(23px) rotateX(-90deg);\n\ttransform: translateZ(-23px) translateY(23px) rotateX(-90deg);\n}\n\n/* END OF FLIP BUTTON */\n\n#logotext {\n\tbackground: url(\"../images/logotext_large.gif\") no-repeat;\n\tbackground-size: 172px 65px;\n\twidth: 172px;\n\theight: 65px;\n\tposition: relative;\n\tmargin: auto;\n\tmargin-top: 36px;\n}\n#info {\n\tbackground-position: -190px 0;\n\tcursor: pointer;\n\twidth: 49px;\n\theight: 49px;\n\tposition: absolute;\n\tmargin: auto;\n\ttop:-10px;\n\tright: -20px;\n}\n\n/* FIRST CARD FORMS */\n\ninput[class=\"frontforms\"]{\n\tborder: 0px solid;\n\tposition: relative;\n\tcolor: #4c4c4c;\n\tfont-family: Arial;\n\tfont-size: 15px;\n}\ninput[id=\"frontloginform\"]{\n\tmargin-left: 20px;\n\theight: 25px;\n\twidth: 110px;\n}\ninput[id=\"frontpasswordform\"]{\n\tmargin-left: 25px;\n\theight: 25px;\n\twidth: 95px;\n}\n.ghostform {\n\tdisplay: none;\n}\n\ninput[class=\"backforms\"]:focus { outline: none; }\ninput[class=\"backforms\"]::-webkit-input-placeholder{font-family: Arial; font-size: 15px; color: #cdcdcd;}\ninput[class=\"backforms\"]:-ms-input-placeholder{font-family: Arial; font-size: 15px; color: #cdcdcd;}\ninput[class=\"backforms\"]:-moz-placeholder{font-family: Arial; font-size: 15px; color: #cdcdcd;}\ninput[type=\"text\"]:focus, input[type=\"password\"]:focus { outline: none; }\ninput[class=\"frontforms\"]::-webkit-input-placeholder{font-family: Arial; font-size: 15px; color: #cdcdcd;}\ninput[class=\"frontforms\"]:-ms-input-placeholder{font-family: Arial; font-size: 15px; color: #cdcdcd;}\ninput[class=\"frontforms\"]:-moz-placeholder{font-family: Arial; font-size: 15px; color: #cdcdcd;}\n\n/*================================= SECOND CARD =================================*/\n\n.BACKCARD {\n\t-webkit-transform: rotateY(180deg);\n\t-moz-transform: rotateY(180deg);\n\t-ms-transform: rotateY(180deg);\n\t-o-transform: rotateY(180deg);\n\ttransform: rotateY(180deg);\n\tbackground-color: white;\n\tz-index: 1;\n\theight: 500px;\n}\n#franklin{\n\tborder: 1px solid #b9d6dc;\n\t-webkit-border-radius: 90px;\n\t-ms-border-radius: 90px;\n\t-o-border-radius: 90px;\n\t-moz-border-radius: 90px;\n\tborder-radius: 90px;\n\ttop:0; left: 0; right: 0; bottom: 0;\n\tposition: relative;\n\tmargin: auto;\n\twidth: 162px;\n\theight: 162px;\n\n}\n#plusavatar {\n\t-webkit-transition: 0.4s;\n\t-moz-transition: 0.4s;\n\t-o-transition: 0.4s;\n\t-ms-transition: 0.4s;\n\ttransition: 0.4s;\n\topacity: 0;\n\tfilter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);\n\twidth: 180px;\n\theight: 180px;\n\tposition: absolute;\n\ttop:10px; left:0; right: 0;\n\tbackground-position: 0 -40px;\n\tmargin: auto;\n\tz-index: 2;\n}\n.avataranimation {\n\t-webkit-animation: 2.5s plusavatar;\n\t-moz-animation: 2.5s plusavatar;\n\t-o-animation: 2.5s plusavatar;\n\t-ms-animation: 2.5s plusavatar;\n\tanimation: 2.5s plusavatar;\n}\n.infiniteavataranimation {\n\t-webkit-animation: 1.5s infinite plusavatar;\n\t-moz-animation: 1.5s infinite plusavatar;\n\t-o-animation: 1.5s infinite plusavatar;\n\t-ms-animation: 1.5s infinite plusavatar;\n\tanimation: 1.5s infinite plusavatar;\n}\n\n#createaccount {\n\tbackground-position: -190px -80px;\n\twidth: 134px;\n\theight: 44px;\n\tposition: relative;\n\tmargin: auto;\n\tmargin-top: 24px;\n}\n.inputWrapper {\n\ttop:-335px; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\toverflow: hidden;\n\tposition: absolute;\n\tcursor: pointer;\n\twidth: 180px;\n\theight: 180px;\n\tz-index: 3;\n}\n\n.hidden {\n\topacity: 0;\n\t-moz-opacity: 0;\n\tfilter:progid:DXImageTransform.Microsoft.Alpha(opacity=0)\n}\n.upload {\n\t-webkit-animation: plus 2s infinite;\n\t-moz-animation: plus 2s infinite;\n\t-o-animation: plus 2s infinite;\n\t-ms-animation: plus 2s infinite;\n\tanimation: plus 2s infinite;\n}\n#mailform {\n\tposition: absolute;\n\ttop: 190px;\n\tmargin: auto;\n\tright:-120px;\n\ttext-transform: uppercase;\n\twidth: 540px;\n\theight: 330px;\n\tfont-size: 13px;\n\tdisplay: block;\n\topacity: 0.9;\n\tdisplay: none;\n}\n.mailforminfo {\n\tposition: relative;\n\ttext-transform: none;\n\ttop:-60px;\n\tfont-family: \"Museo-500\";\n\tfont-size: 12px;\n\tcolor: #525252;\n}\n.mailforminfosmall {\n\tfont-size: 11px;\n\tposition: relative;\n\ttop: 30px;\n\tline-height: 1.7;\n}\n#skipmail a {\n\tposition: relative;\n\ttext-transform: uppercase;\n\tleft: 0px; right: 0px; bottom: -193px;\n\tfont-size: 11px;\n\tfont-family: Arial;\n\tcolor: #96bcce;\n\ttext-decoration: none;\n\tborder-bottom: 1px dashed #96bcce;\n}\n\n#skipmail a:hover{\n\tcolor: #7ba1b4;\n\ttext-decoration: none;\n\tborder-bottom: 1px dashed #7ba1b4;\n}\n\n/* SECOND CARD FORMS */\n\n#registrationforms {\n\tposition: relative;\n\ttop: 36px;\n\tline-height: 3.3em;\n}\ninput[class=\"backforms\"]{\n\ttext-align: center;\n\tborder: 1px solid #a6ccd9;\n\tcolor: #4c4c4c;\n\tfont-family: Arial;\n\tfont-size: 15px;\n\theight: 30px;\n\twidth: 296px;\n}\ninput[id=\"backmailform\"] {\n\tposition: relative;\n\ttext-align: center;\n\tborder: 1px solid #a6ccd9;\n\tcolor: #4c4c4c;\n\tfont-family: Arial;\n\tfont-size: 15px;\n\theight: 30px;\n\twidth: 296px;\n\ttop: 140px;\n}\n.regbutton, .demobutton, .mailbutton {\n\tcursor: pointer;\n\tposition: relative;\n\tbackground-color: white;\n\tborder: 0px solid;\n\toutline: 3px double #a6ccd9;\n\ttext-transform: uppercase;\n\ttext-shadow: 1px 1px 1px white;\n\tcolor: #6e6e6e;\n}\n.regbutton:hover, .demobutton:hover, .mailbutton:hover  {\n\tbackground-color: #f4f9fb;\n}\n.regbutton:active, .demobutton:active, .mailbutton:active  {\n\tbackground-color: #ecf3f6;\n}\n\n.regbutton {\n\theight: 34px;\n\twidth: 195px;\n\ttop:18px;\n\tfont-size: 11px;\n}\n.demobutton {\n\tletter-spacing: 3px;\n\theight: 50px;\n\twidth: 300px;\n\ttop: 436px;\n\tfont-size: 12px;\n}\n.mailbutton {\n\tposition: relative;\n\theight: 34px;\n\twidth: 195px;\n\ttop: 178px;\n\tfont-size: 11px;\n}\n\n/* THIRD CARD */\n.flipinfo, .frominfo {\n\tcursor: pointer;\n}\n.infoflipback {\n\tposition: absolute;\n\ttop: -12%;\n\twidth: 100%;\n\theight: 500px;\n\tcursor: pointer;\n}\n#infopage {\n\tmargin-left: -290px;\n\tmargin-top: -90px;\n\theight: 660px;\n\twidth: 870px;\n\tbackground-color: white;\n\tz-index: 4;\n\tdisplay: none;\n\tposition: absolute;\n\tcolor: #525252;\n}\n#regpage {\n\tdisplay: block;\n}\n#infosubtitle, #infotitle, #infofooter, #iconsfooter {\n\tcursor: pointer;\n\tfont-family: \"Museo-500\";\n\tposition: absolute;\n\tleft: 0; right: 0;\n\tmargin: auto;\n\twidth: 100%;\n}\n#infosubtitle {\n\ttext-transform: uppercase;\n\tfont-size: 11px;\n\ttop: 0;\n\theight: 30px;\n}\n#infotitle {\n\ttext-transform: uppercase;\n\tfont-size: 27px;\n\ttop: 35px;\n\theight: 40px;\n}\na#infofooter {\n\tbackground-image: url(\"../images/github.gif\");\n\tbackground-size: 25px 25px;\n\tbackground-repeat: no-repeat;\n\tbackground-position: center;\n\tcolor: #757575;\n\ttext-decoration: none;\n\tdisplay: block;\n\tline-height: 120px;\n\tfont-size: 12px;\n\tbottom: 30px;\n\tmargin: auto;\n\twidth: 20%;\n\theight: 25px;\n}\na#iconsfooter {\n\tbackground-position: center;\n\tcolor: #757575;\n\ttext-decoration: none;\n\tfont-size: 10px;\n\tbottom: -30px;\n}\n#infoline {\n\tposition: relative;\n\twidth: 610px;\n\tdisplay: block;\n\tmargin: auto;\n\ttop: 425px;\n\tborder-top: 1px solid #b8d9e1;\n}\n#infolinetext {\n\tfont-family: \"Museo-500\";\n\ttext-transform: uppercase;\n\tletter-spacing: 2px;\n\tfont-size: 10px;\n\tline-height: 30px;\n\tposition: relative;\n\twidth: 245px;\n\theight: 30px;\n\tdisplay: block;\n\tmargin: auto;\n\ttop: 410px;\n\tbackground-color: white;\n}\n.infocolumn {\n\tposition: absolute;\n\ttop: 150px;\n\tmargin: auto;\n\tdisplay: block;\n\twidth: 286px;\n\theight: 236px;\n\toverflow: hidden;\n}\n#infoleft {\n\tleft:0;\n}\n#inforight {\n\tright:0;\n}\n#infocenter {\n\tleft:0; right:0;\n}\n.columnimage {\n\twidth: 104px;\n\theight: 104px;\n\tposition: absolute;\n\tmargin: auto;\n\tleft:0; right: 0; top: 30px;\n}\n#leftcolumn > .columnimage {\n\tbackground-position: -546px 0;\n}\n#rightcolumn > .columnimage {\n\tbackground-position: -330px 0;\n}\n#centercolumn > .columnimage {\n\tbackground-position: -436px 0;\n}\n#leftcolumn, #rightcolumn, #centercolumn {\n\t-moz-transition: 0.8s;\n\t-o-transition: 0.8s ease-out;\n\t-ms-transition: 0.8s ease-out;\n\t-webkit-transition: 0.8s;\n\ttransition: 0.8s;\n\tfont-family: \"Museo-500\";\n\tfont-size: 12px;\n\twidth: 100%;\n\theight: 200%;\n\tposition: absolute;\n\tpadding-top: 157px;\n\ttext-align: center;\n\ttop: 0%;\n}\n#leftcolumn:hover, #rightcolumn:hover, #centercolumn:hover {\n\ttop:-100%;\n}\n.columnfooter {\n\tposition: relative;\n\ttop: 150px;\n\tfont-size: 11px;\n\tline-height: 17px;\n}\n\n@media only screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 144dpi) {\n\t#piggy {\n\t\tbackground-image: url(\"../images/piggy_large@2x.gif\");\n\t}\n\t#logotext {\n\t\tbackground-image: url(\"../images/logotext_large@2x.gif\");\n\t}\n}\n\n/* ANIMATION */\n\n/* SPIN THAT SHIT */\n\n@-webkit-keyframes auto-shake {\n\t0% {\n\t\t-webkit-transform: rotate(0deg);\n\t}\n\t35% {\n\t\t-webkit-transform: rotate(55deg);\n\t}\n\t50% {\n\t\t-webkit-transform: rotate(-35deg);\n\t}\n\t70% {\n\t\t-webkit-transform: rotate(20deg);\n\t}\n\t90% {\n\t\t-webkit-transform: rotate(-10deg);\n\t}\n\t100% {\n\t\t-webkit-transform: rotate(0deg);\n\t}\n}\n@-moz-keyframes auto-shake {\n\t0% {\n\t\t-moz-transform: rotate(0deg);\n\t}\n\t35% {\n\t\t-moz-transform: rotate(55deg);\n\t}\n\t50% {\n\t\t-moz-transform: rotate(-35deg);\n\t}\n\t70% {\n\t\t-moz-transform: rotate(20deg);\n\t}\n\t90% {\n\t\t-moz-transform: rotate(-10deg);\n\t}\n\t100% {\n\t\t-moz-transform: rotate(0deg);\n\t}\n}\n@-o-keyframes auto-shake {\n\t0% {\n\t\t-o-transform: rotate(0deg);\n\t}\n\t35% {\n\t\t-o-transform: rotate(55deg);\n\t}\n\t50% {\n\t\t-o-transform: rotate(-35deg);\n\t}\n\t70% {\n\t\t-o-transform: rotate(20deg);\n\t}\n\t90% {\n\t\t-o-transform: rotate(-10deg);\n\t}\n\t100% {\n\t\t-o-transform: rotate(0deg);\n\t}\n}\n@-ms-keyframes auto-shake {\n\t0% {\n\t\t-ms-transform: rotate(0deg);\n\t}\n\t35% {\n\t\t-ms-transform: rotate(55deg);\n\t}\n\t50% {\n\t\t-ms-transform: rotate(-35deg);\n\t}\n\t70% {\n\t\t-ms-transform: rotate(20deg);\n\t}\n\t90% {\n\t\t-ms-transform: rotate(-10deg);\n\t}\n\t100% {\n\t\t-ms-transform: rotate(0deg);\n\t}\n}\n@keyframes auto-shake {\n\t0% {\n\t\ttransform: rotate(0deg);\n\t}\n\t35% {\n\t\ttransform: rotate(55deg);\n\t}\n\t50% {\n\t\ttransform: rotate(-35deg);\n\t}\n\t70% {\n\t\ttransform: rotate(20deg);\n\t}\n\t90% {\n\t\ttransform: rotate(-10deg);\n\t}\n\t100% {\n\t\ttransform: rotate(0deg);\n\t}\n}\n\n/* SPIN AGAIN */\n\n@-webkit-keyframes hover-shake {\n\t0% {\n\t\t-webkit-transform: rotate(0deg);\n\t}\n\t30% {\n\t\t-webkit-transform: rotate(-30deg);\n\t}\n\t50% {\n\t\t-webkit-transform: rotate(20deg);\n\t}\n\t70% {\n\t\t-webkit-transform: rotate(-10deg);\n\t}\n\t90% {\n\t\t-webkit-transform: rotate(5deg);\n\t}\n\t100% {\n\t\t-webkit-transform: rotate(0deg);\n\t}\n}\n@-moz-keyframes hover-shake {\n\t0% {\n\t\t-moz-transform: rotate(0deg);\n\t}\n\t30% {\n\t\t-moz-transform: rotate(-30deg);\n\t}\n\t50% {\n\t\t-moz-transform: rotate(20deg);\n\t}\n\t70% {\n\t\t-moz-transform: rotate(-10deg);\n\t}\n\t90% {\n\t\t-moz-transform: rotate(5deg);\n\t}\n\t100% {\n\t\t-moz-transform: rotate(0deg);\n\t}\n}\n@-o-keyframes hover-shake {\n\t0% {\n\t\t-o-transform: rotate(0deg);\n\t}\n\t30% {\n\t\t-o-transform: rotate(-30deg);\n\t}\n\t50% {\n\t\t-o-transform: rotate(20deg);\n\t}\n\t70% {\n\t\t-o-transform: rotate(-10deg);\n\t}\n\t90% {\n\t\t-o-transform: rotate(5deg);\n\t}\n\t100% {\n\t\t-o-transform: rotate(0deg);\n\t}\n}\n@-ms-keyframes hover-shake {\n\t0% {\n\t\t-ms-transform: rotate(0deg);\n\t}\n\t30% {\n\t\t-ms-transform: rotate(-30deg);\n\t}\n\t50% {\n\t\t-ms-transform: rotate(20deg);\n\t}\n\t70% {\n\t\t-ms-transform: rotate(-10deg);\n\t}\n\t90% {\n\t\t-ms-transform: rotate(5deg);\n\t}\n\t100% {\n\t\t-ms-transform: rotate(0deg);\n\t}\n}\n@keyframes hover-shake {\n\t0% {\n\t\ttransform: rotate(0deg);\n\t}\n\t30% {\n\t\transform: rotate(-30deg);\n\t}\n\t50% {\n\t\ttransform: rotate(20deg);\n\t}\n\t70% {\n\t\ttransform: rotate(-10deg);\n\t}\n\t90% {\n\t\ttransform: rotate(5deg);\n\t}\n\t100% {\n\t\ttransform: rotate(0deg);\n\t}\n}\n\n/*LOADING SPIN*/\n\n@-webkit-keyframes loadingspin {\n\t0% {\n\t\t-webkit-transform: rotate(0deg);\n\t}\n\t20% {\n\t\t-webkit-transform: rotate(70deg);\n\t}\n\t70% {\n\t\t-webkit-transform: rotate(-400deg);\n\t}\n\t100% {\n\t\t-webkit-transform: rotate(-360deg);\n\t}\n}\n@-moz-keyframes loadingspin {\n\t0% {\n\t\t-moz-transform: rotate(0deg);\n\t}\n\t20% {\n\t\t-moz-transform: rotate(70deg);\n\t}\n\t70% {\n\t\t-moz-transform: rotate(-400deg);\n\t}\n\t100% {\n\t\t-moz-transform: rotate(-360deg);\n\t}\n}\n@-o-keyframes loadingspin {\n\t0% {\n\t\t-o-transform: rotate(0deg);\n\t}\n\t20% {\n\t\t-o-transform: rotate(70deg);\n\t}\n\t70% {\n\t\t-o-transform: rotate(-400deg);\n\t}\n\t100% {\n\t\t-o-transform: rotate(-360deg);\n\t}\n}\n@-ms-keyframes loadingspin {\n\t0% {\n\t\t-ms-transform: rotate(0deg);\n\t}\n\t20% {\n\t\t-ms-transform: rotate(70deg);\n\t}\n\t70% {\n\t\t-ms-transform: rotate(-400deg);\n\t}\n\t100% {\n\t\t-ms-transform: rotate(-360deg);\n\t}\n}\n@keyframes loadingspin {\n\t0% {\n\t\ttransform: rotate(0deg);\n\t}\n\t20% {\n\t\ttransform: rotate(70deg);\n\t}\n\t70% {\n\t\ttransform: rotate(-400deg);\n\t}\n\t100% {\n\t\ttransform: rotate(-360deg);\n\t}\n}\n\n/* ENTER ARROW */\n\n@-webkit-keyframes enterarrow {\n\t0% {\n\t\tleft: 0px;\n\t}\n\t5% {\n\t\tleft: -5px;\n\t}\n\t15% {\n\t\tleft: 0px;\n\t}\n\t20% {\n\t\tleft: -5px;\n\t}\n\t35% {\n\t\tleft: 0px;\n\t}\n\t100% {\n\t\tleft: 0px;\n\t}\n}\n@-moz-keyframes enterarrow {\n\t0% {\n\t\tleft: 0px;\n\t}\n\t5% {\n\t\tleft: -5px;\n\t}\n\t15% {\n\t\tleft: 0px;\n\t}\n\t20% {\n\t\tleft: -5px;\n\t}\n\t35% {\n\t\tleft: 0px;\n\t}\n\t100% {\n\t\tleft: 0px;\n\t}\n}\n@-o-keyframes enterarrow {\n\t0% {\n\t\tleft: 0px;\n\t}\n\t5% {\n\t\tleft: -5px;\n\t}\n\t15% {\n\t\tleft: 0px;\n\t}\n\t20% {\n\t\tleft: -5px;\n\t}\n\t35% {\n\t\tleft: 0px;\n\t}\n\t100% {\n\t\tleft: 0px;\n\t}\n}\n@-ms-keyframes enterarrow {\n\t0% {\n\t\tleft: 0px;\n\t}\n\t5% {\n\t\tleft: -5px;\n\t}\n\t15% {\n\t\tleft: 0px;\n\t}\n\t20% {\n\t\tleft: -5px;\n\t}\n\t35% {\n\t\tleft: 0px;\n\t}\n\t100% {\n\t\tleft: 0px;\n\t}\n}\n@keyframes enterarrow {\n\t0% {\n\t\tleft: 0px;\n\t}\n\t5% {\n\t\tleft: -5px;\n\t}\n\t15% {\n\t\tleft: 0px;\n\t}\n\t20% {\n\t\tleft: -5px;\n\t}\n\t35% {\n\t\tleft: 0px;\n\t}\n\t100% {\n\t\tleft: 0px;\n\t}\n}\n\n/*PLUS AVATAR*/\n\n@-webkit-keyframes plusavatar {\n\t0% {\n\t\topacity: 0;\n\t}\n\t30% {\n\t\topacity: 1;\n\t}\n\t80% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n@-moz-keyframes plusavatar {\n\t0% {\n\t\topacity: 0;\n\t}\n\t60% {\n\t\topacity: 1;\n\t}\n\t80% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n@-o-keyframes plusavatar {\n\t0% {\n\t\topacity: 0;\n\t}\n\t60% {\n\t\topacity: 1;\n\t}\n\t80% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n@-ms-keyframes plusavatar {\n\t0% {\n\t\topacity: 0;\n\t}\n\t60% {\n\t\topacity: 1;\n\t}\n\t80% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n@keyframes plusavatar {\n\t0% {\n\t\topacity: 0;\n\t}\n\t60% {\n\t\topacity: 1;\n\t}\n\t80% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}"
  },
  {
    "path": "gateway/src/main/resources/static/css/style.css",
    "content": "@font-face {\n\tfont-family: 'Museo-100';\n\tsrc: url('../fonts/museo-100/museo-100.eot');\n\tsrc: local('@%@'),\n\turl('../fonts/museo-100/museo-100.eot?#iefix') format('embedded-opentype'),\n\turl('../fonts/museo-100/museo-100.woff') format('woff'),\n\turl('../fonts/museo-100/museo-100.svg') format('svg'),\n\turl('../fonts/museo-100/museo-100.ttf') format('truetype');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n@font-face {\n\tfont-family: 'Museo-300';\n\tsrc: url('../fonts/museo-300/museo-300.eot');\n\tsrc: local('@%@'),\n\turl('../fonts/museo-300/museo-300.eot?#iefix') format('embedded-opentype'),\n\turl('../fonts/museo-300/museo-300.woff') format('woff'),\n\turl('../fonts/museo-300/museo-300.svg') format('svg'),\n\turl('../fonts/museo-300/museo-300.ttf') format('truetype');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n@font-face {\n\tfont-family: 'Museo-500';\n\tsrc: url('../fonts/museo-500/museo-500.eot');\n\tsrc: local('@%@'),\n\turl('../fonts/museo-500/museo-500.eot?#iefix') format('embedded-opentype'),\n\turl('../fonts/museo-500/museo-500.woff') format('woff'),\n\turl('../fonts/museo-500/museo-500.svg') format('svg'),\n\turl('../fonts/museo-500/museo-500.ttf') format('truetype');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\nbody {\n\tfont-family: \"Museo-300\", Arial;\n\t-webkit-font-smoothing: antialiased;\n\tmargin: auto;\n\twidth: 100%;\n\theight: 100%;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-o-user-select: none;\n\t-ms-user-select: none;\n\toverflow: hidden;\n}\n.toppage, .bottompage {\n\t-webkit-transition: all 0.6s cubic-bezier(0.94, 0.06, 0.05, 0.95);\n\t-moz-transition: all0.6s cubic-bezier(0.77, 0, 0.175, 1);\n\t-ms-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1);\n\t-o-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1);\n\ttransition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1);\n\tposition: absolute;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n}\n.toppage {\n\ttop:0;\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: block;\n}\n.bottompage {\n\ttop:100%;\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: none;\n\tz-index: 600;\n}\n.sectionup {\n\t-webkit-transform: translateY(100%);\n\t-moz-transform: translateY(100%);\n\t-o-transform: translateY(100%);\n\t-ms-transform: translateY(100%);\n\ttransform: translateY(100%);\n}\n.sectionDown {\n\t-webkit-transform: translateY(-100%);\n\t-moz-transform: translateY(-100%);\n\t-o-transform: translateY(-100%);\n\t-ms-transform: translateY(-100%);\n\ttransform: translateY(-100%);\n}\ninput::-ms-clear {\n\tdisplay: none;\n}\ninput[type=\"text\"], input[type=\"password\"], textarea {\n\t-webkit-appearance: none;\n\tbox-shadow: none;\n\t-webkit-box-shadow: none;\n\t-webkit-border-radius: 0px;\n\t-moz-border-radius: 0px;\n\tborder-radius: 0px;\n}\n::-moz-selection {\n\tbackground: rgb(255, 221, 45);\n}\n::selection {\n\tbackground: rgb(255, 221, 45);\n}\n*:focus {outline:0px none transparent;}\n\n#bubble, #indicator, #save, #piggyicon, #rublesign, .arrowup, .arrowdown, .incomes-sprite-title, .close-sign, .modal-save-icon, #modaldeletecross, .initicons-arrow, .expenses-sprite-title, .savings-sprite-title, .triangle, .noUi-handle, .noUi-handle:after  {\n\tbackground-size: 538px 84px;\n}\n#chooseicon, .itembackground, .iconbox, .itemlinebackground, #circle-select-1-back, #circle-select-2-back, #circle-select-3-back {\n\tbackground-image: url(\"../images/icons.png\");\n\tbackground-size: 1031px 42px;\n\twidth: 42px;\n\theight: 42px;\n}\n@media only screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 144dpi) {\n\t#chooseicon, .itembackground, .iconbox, .itemlinebackground, #circle-select-1-back, #circle-select-2-back, #circle-select-3-back {\n\t\tbackground-image: url(\"../images/icons@2x.png\");\n\t}\n}\n.selectbox .select {\n\twidth: 55px;\n\theight: 24px;\n\tpadding: 0 45px 0 10px;\n\tfont: 12px/24px Arial;\n\tborder: 1px solid #ccc;\n}\n.selectbox .dropdown {\n\ttop: 25px;\n\twidth: 110px;\n\tmargin: 0;\n\tpadding: 0 0;\n\tbackground: #FFF;\n\tborder: 1px solid #ccc;\n\tfont: 12px Arial;\n}\n.selectbox .select .text {\n\tdisplay: block;\n\twidth: 100%;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n\toverflow: hidden;\n}\n.selectbox {\n\tcolor: #333;\n\tvertical-align: middle;\n\tcursor: pointer;\n\tmargin-bottom: 26px;\n}\n.selectbox .trigger .arrow {\n\tposition: absolute;\n\ttop: 11px;\n\tright: 10px;\n\tborder-left: 3px solid transparent;\n\tborder-right: 3px solid transparent;\n\tborder-top: 3px solid #a1a1a1;\n\twidth: 0;\n\theight: 0;\n\toverflow: hidden;\n}\n.selectbox .select:active {\n\tbackground: #f8f8f8;\n}\n.selectbox li {\n\tpadding: 5px 10px 6px;\n}\n.selectbox li.selected {\n\tbackground: #f6f6f6;\n}\n.selectbox li:hover {\n\tbackground: #f6f6f6;\n}\n\n#lastlogo {\n\t-webkit-perspective: 1000;\n\t-moz-perspective: 1000px;\n\t-ms-perspective: 1000px;\n\t-o-perspective: 1000px;\n\tperspective: 1000;\n\tposition: absolute;\n\ttop: 8%; left: 0; right: 0;\n\tmargin: auto;\n\tz-index: 1000;\n\twidth: 172px;\n\theight: 204px;\n\tdisplay: none;\n}\n#lastlogoflipper {\n\t-webkit-transform-style: preserve-3d;\n\t-moz-transform-style: preserve-3d;\n\t-ms-transform-style: preserve-3d;\n\t-o-transform-style: preserve-3d;\n\ttransform-style: preserve-3d;\n\t-moz-backface-visibility: hidden;\n\t-webkit-transition: 0.4s;\n\t-moz-transition: 0.4s;\n\t-ms-transition: 0.4s;\n\t-o-transition: 0.4s;\n\ttransition: 0.4s;\n\tposition: relative;\n\toverflow: visible;\n}\n#logo_greeting, #logo_settings {\n\t-moz-transition: 0.3s ease-out;\n\t-o-transition: 0.3s ease-out;\n\t-ms-transition: 0.3s ease-out;\n\t-webkit-transition: 0.3s ease-out;\n\ttransition: 0.3s ease-out;\n\tbackground: url(\"../images/logo_large.gif\") no-repeat;\n\tbackground-size: 137px 204px;\n\tbackground-position: center;\n\twidth: 172px;\n\theight: 204px;\n\tposition: absolute;\n\tmargin: auto;\n\tleft: 0; right: 0;\n\tdisplay: none;\n\tz-index: 600;\n}\n#logo_greeting {\n\tposition: absolute;\n\tdisplay: none;\n\ttop: 8%;\n}\n#logo_settings, #logo_statistic {\n\t-webkit-backface-visibility: hidden;\n\t-moz-backface-visibility: hidden;\n\t-ms-backface-visibility: hidden;\n\t-o-backface-visibility: hidden;\n\tbackface-visibility: hidden;\n\toverflow: visible;\n\tposition: absolute;\n\tcursor: pointer;\n}\n#logo_statistic {\n\t-webkit-transform: rotateY(180deg);\n\t-moz-transform: rotateY(180deg);\n\t-ms-transform: rotateY(180deg);\n\t-o-transform: rotateY(180deg);\n\ttransform: rotateY(180deg);\n\tbackground: url(\"../images/logotext_large.gif\") no-repeat;\n\tbackground-size: 172px 65px;\n\twidth: 172px;\n\theight: 65px;\n\tposition: absolute;\n\tcursor: pointer;\n\tz-index: 3000;\n}\n#settings_hat {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 36%;\n\tbackground-color: white;\n\tdisplay: none;\n\tz-index: 500;\n}\n#logoclickplace {\n\twidth: 100%;\n\theight: 100%;\n}\n#bubble {\n\tposition: absolute;\n\ttop:-20px;\n\tright: -60px;\n\tbackground-position: -180px 0;\n\twidth: 57px;\n\theight: 57px;\n\tcursor: pointer;\n\tdisplay: none;\n}\n#indicator {\n\tposition: absolute;\n\tbackground-position: -280px 0;\n\ttop: 21px; right: 10px;\n\twidth: 31px;\n\theight: 31px;\n}\n.bubble-animation {\n\t-webkit-animation: 1s bubble ease-out;\n\t-moz-animation: 1s bubble ease-out;\n\t-o-animation: 1s bubble ease-out;\n\t-ms-animation: 1s bubble ease-out;\n\tanimation: 1s bubble ease-out;\n}\ninput {\n\t-webkit-font-smoothing: antialiased;\n}\n#centerbox {\n\ttop:0; left: 0; right: 0; bottom: 0;\n\tposition: absolute;\n\twidth: 250px;\n\theight: 250px;\n\tmargin: auto;\n\tdisplay: none;\n}\n#avatarcontainer {\n\tfont-family: \"Museo-100\";\n\tborder: 1px solid #b9d6dc;\n\t-webkit-border-radius: 90px;\n\t-moz-border-radius: 90px;\n\tborder-radius: 90px;\n\ttop:0; left: 0; right: 0; bottom: 0;\n\tposition: absolute;\n\tcursor: pointer;\n\twidth: 162px;\n\theight: 162px;\n\tmargin: auto;\n}\n.forward {\n\t-webkit-animation: 0.4s zoomavatar ease-out;\n\t-moz-animation: 0.4s zoomavatar ease-out;\n\t-o-animation: 0.4s zoomavatar ease-out;\n\t-ms-animation: 0.4s zoomavatar ease-out;\n\tanimation: 0.4s zoomavatar ease-out;\n}\n.reverse {\n\t-webkit-animation: 0.5s unzoomavatar ease-out;\n\t-moz-animation: 0.6s unzoomavatar ease-out;\n\t-o-animation: 0.4s unzoomavatar ease-out;\n\t-ms-animation: 0.4s unzoomavatar ease-out;\n\tanimation: 0.5s unzoomavatar ease-out;\n}\n.avatar {\n\tbackground: url(\"../images/userpic.jpg\") center center no-repeat;\n\tposition: absolute;\n\tbox-shadow:0px 0px 0px 5px white inset;\n\twidth: 100%;\n\theight: 100%;\n\t-moz-border-radius: 90px;\n\t-webkit-border-radius: 90px;\n\tborder-radius: 90px;\n\tbackground-size: 100% 100%;\n}\n#lefttitle, #righttitle {\n\ttext-transform: uppercase;\n\tfont-family: \"Museo-300\", Arial;\n\tfont-size: 22px;\n\tposition: absolute;\n\ttext-align: center;\n\tdisplay: none;\n\twidth: 250px;\n\theight: 20px;\n\tmargin: auto;\n}\n#lefttitle {\n\t-webkit-animation: 0.6s goleft ease-out;\n\t-moz-animation: 0.6s goleft ease-out;\n\t-o-animation: 0.6s goleft ease-out;\n\t-ms-animation: 0.6s goleft ease-out;\n\tanimation: 0.6s goleft ease-out;\n\ttop:0; left: -18%; right: 300px; bottom: 0;\n}\n#righttitle {\n\t-webkit-animation: 0.6s goright ease-out;\n\t-moz-animation: 0.6s goright ease-out;\n\t-o-animation: 0.6s goright ease-out;\n\t-ms-animation: 0.6s goright ease-out;\n\tanimation: 0.6s goright ease-out;\n\ttop:0; left: 18%; right: -300px; bottom: 0;\n}\n#bottombuttons {\n\tfont-family: \"Museo-500\";\n\t-webkit-animation: 0.6s godown ease-out;\n\t-moz-animation: 0.6s godown ease-out;\n\t-o-animation: 0.6s godown ease-out;\n\t-ms-animation: 0.6s godown ease-out;\n\tanimation: 0.6s godown ease-out;\n\tposition: absolute;\n\tleft: 0; right: 0; bottom: 8%;\n\tmargin: auto;\n\tdisplay: none;\n\twidth: 90px;\n\theight: 25%;\n}\n#plus {\n\ttext-align: center;\n\ttext-transform: uppercase;\n\tfont-size: 11px;\n\tcolor: #a6cbd4;\n\tposition: absolute;\n\tcursor: pointer;\n\tmargin: auto;\n\twidth: 90px;\n\theight: 90px;\n}\n#plus:hover {\n\tcolor: #9cbac2;\n}\n#plusborder {\n\t-webkit-animation: plus 2s infinite;\n\t-moz-animation: plus 2s infinite;\n\t-o-animation: plus 2s infinite;\n\t-ms-animation: plus 2s infinite;\n\tanimation: plus 2s infinite;\n\tposition: absolute;\n\tborder: 2px solid #cfe6ec;\n\t-webkit-border-radius: 70px;\n\t-moz-border-radius: 70px;\n\tborder-radius: 70px;\n\ttop:0; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\twidth: 80%;\n\theight: 80%;\n}\n#plusone, #plustwo {\n\tposition: absolute;\n\ttop: 0; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\tbackground-color: #cfe6ec;\n}\n#plusone {\n\twidth: 2px;\n\theight: 50%;\n}\n#plustwo {\n\twidth: 50%;\n\theight: 2px;\n}\n#plustext {\n\tfont-family: Arial;\n\tposition: absolute;\n\tleft: 0; right: 0; bottom: -15px;\n\tmargin: auto;\n}\n#minus {\n\ttop: 70%; left: 0; right: 0;\n\tmargin: auto;\n\ttext-transform: uppercase;\n\tfont-size: 10px;\n\tcolor: #c8c2c4;\n\tposition: absolute;\n\tcursor: pointer;\n\twidth: 35px;\n\theight: 35px;\n}\n#minus:hover {\n\tcolor: #a39b9d;\n}\n#minusborder {\n\tposition: absolute;\n\tborder: 1px solid #c8c2c4;\n\t-webkit-border-radius: 70px;\n\t-moz-border-radius: 70px;\n\tborder-radius: 70px;\n\ttop:0; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\twidth: 80%;\n\theight: 80%;\n}\n#minusone {\n\tposition: absolute;\n\ttop: 0; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\tbackground-color: #c8c2c4;\n\twidth: 50%;\n\theight: 1px;\n}\n#minustext {\n\twidth: 70px;\n\ttext-align: center;\n\tfont-family: Arial;\n\tleft: -18px; right: 0; bottom: -17px;\n\tmargin: auto;\n\tposition: absolute;\n}\n.bluetext {\n\tcolor: #a6d1d6;\n}\n#settingspage {\n\tdisplay: none;\n}\n#swipefield {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: 0;\n}\n\n/* =============== S L I D E R S =============== */\n.incomes-sprite-title, .expenses-sprite-title, .savings-sprite-title {\n\tbackground-size: 538px 84px;\n\tposition: absolute;\n\tcursor: pointer;\n\ttop:-4px;\n}\n.incomes-sprite-title {\n\tbackground-position: 0 -60px;\n\twidth: 108px;\n\theight: 24px;\n\tleft: 35%;\n\tcursor: pointer;\n}\n.expenses-sprite-title {\n\tbackground-position: -114px -60px;\n\twidth: 114px;\n\theight: 24px;\n\tleft: 35%;\n\tcursor: pointer;\n}\n.savings-sprite-title {\n\tbackground-position:  -233px -60px;\n\twidth: 164px;\n\theight: 24px;\n\tleft: 30%;\n}\n.zoomplus:hover ~ .plusitem {\n\t-webkit-transform: scale(1.15);\n\t-moz-transform: scale(1.15);\n\t-o-transform: scale(1.15);\n\t-ms-transform: scale(1.15);\n\ttransform: scale(1.15);\n}\n.zoomplus:active ~ .plusitem {\n\t-webkit-transform: scale(1);\n\t-moz-transform: scale(1);\n\t-o-transform: scale(1);\n\t-ms-transform: scale(1);\n\ttransform: scale(1);\n}\n\n.blueline {\n\tbackground-color: #b8d9e1;\n\tposition: absolute;\n\ttop:47px;\n\twidth: 100%;\n\theight: 1px;\n}\n.brownline {\n\tbackground-color: #a6989c;\n\tposition: absolute;\n\ttop:47px;\n\twidth: 12px;\n\theight: 1px;\n}\n.columns {\n\ttext-transform: uppercase;\n\tposition: absolute;\n\tdisplay: block;\n\tmargin: auto;\n\theight: 390px;\n}\n#savebutton {\n\tposition: absolute;\n\tleft: 0; right: 0;\n\tdisplay: block;\n\tcursor: pointer;\n\twidth: 100%;\n\theight: 60px;\n\tbottom: 2%;\n\toverflow: hidden;\n}\n#savebutton:hover > #leftborder, #savebutton:hover > #rightborder, #savebutton:hover > #centerborder  {\n\tbackground-color: #fafcfc;\n}\n#savebutton:active > #leftborder, #savebutton:active > #rightborder, #savebutton:active > #centerborder  {\n\tbackground-color: #f3f7f8;\n}\n\n#leftborder, #rightborder, #centerborder  {\n\tborder-top: 1px solid #b8d9e1;\n\tposition: absolute;\n\tmargin:auto;\n\theight: 100%;\n\tdisplay: block;\n\t-moz-transition: 0.2s;\n\t-o-transition: 0.2s;\n\t-ms-transition: 0.2s;\n\t-webkit-transition: 0.2s;\n\ttransition: 0.2s;\n}\n.saveaction {\n\tbackground-color: #eff6f8;\n}\n\n#leftborder {\n\tleft: -21%; right: 490px;\n\twidth: 340px;\n}\n#rightborder {\n\tleft: 21%; right: -490px;\n\twidth: 340px;\n}\n#centerborder {\n\tleft: 0; right: 0;\n\twidth: 40%;\n}\n#save {\n\tmargin: auto;\n\tposition: absolute;\n\tbottom:15px; left: 25px; right: 0;\n\twidth: 138px;\n\theight: 24px;\n\tbackground-position: -400px -60px;\n}\n\n/* =============== SAVINGS SLIDER =============== */\n\n#piggyicon {\n\tbackground-position: -100px 0;\n\tbackground-size: 538px 84px;\n\tposition: absolute;\n\tdisplay: block;\n\twidth: 46px;\n\theight: 38px;\n\tleft: 25px;\n\ttop: 70px;\n}\n#topsavingsline {\n\tposition: relative;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 190px;\n\tborder-bottom: 1px solid #e2e2e2;\n}\n#bottomsavingsline {\n\tposition: relative;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 92px;\n\tborder-bottom: 1px solid #e2e2e2;\n}\n#savingsvalue{\n\tdisplay: inline-block;\n\tborder: 0px solid;\n\tbackground-color: transparent;\n\tposition: absolute;\n\ttop: 125px;\n\tleft: 30%;\n\tfont-family: \"Museo-100\";\n\tfont-size: 38px;\n\tpadding: 0;\n}\n#rublesign {\n\tcursor: pointer;\n\tbackground-size: 538px 84px;\n\tdisplay: inline-block;\n\twidth: 22px;\n\theight: 31px;\n\tposition: relative;\n\tz-index: 1000;\n}\n#rublebox {\n\ttop: 135px;\n\twidth: 30%;\n\theight: 40px;\n\tleft: 0;\n\tposition: relative;\n\tdisplay: inline-block;\n}\n#percentvalue{\n\tdisplay: inline-block;\n\tborder: 0px solid;\n\tposition: relative;\n\ttop: 48px;\n\tleft: 125px;\n\tfont-family: \"Museo-300\";\n\tfont-size: 22px;\n\tcolor: #9e9e9e;\n\tbackground-color: white;\n}\n\n/* TOGGLES */\n#deposit + label.button{\n\tposition: absolute;\n\ttop:16px;\n}\n#capitalization + label.button{\n\tposition: absolute;\n\tbottom:-42px;\n}\ninput#deposit, input#capitalization  {\n\tmax-height: 0;\n\tmax-width: 0;\n\tdisplay: none;\n}\ninput#deposit + label.button, input#capitalization + label.button{\n\tdisplay: block;\n\tposition: absolute;\n\tright:32px;\n\tbox-shadow: inset 0 0 0px 1px #e3e3e3;\n\theight: 21px;\n\twidth: 33px;\n\tborder-radius: 15px;\n}\ninput#deposit + label.button:before, input#capitalization + label.button:before {\n\tcontent: \"\";\n\tposition: absolute;\n\tdisplay: block;\n\theight: 21px;\n\twidth: 21px;\n\ttop: 0;\n\tleft: 0;\n\tborder-radius: 15px;\n\t-moz-transition: 0.2s ease-out;\n\t-o-transition: 0.2s ease-out;\n\t-ms-transition: 0.2s ease-out;\n\t-webkit-transition: 0.2s ease-out;\n\ttransition: 0.2s ease-out;\n}\ninput#deposit + label.button:after, input#capitalization + label.button:after {\n\tcontent: \"\";\n\tposition: absolute;\n\tdisplay: block;\n\theight: 21px;\n\twidth: 21px;\n\ttop: 0;\n\tleft: 0px;\n\tborder-radius: 15px;\n\tbackground: white;\n\tbox-shadow: inset 0 0 0 1px #e0e0e0, 1px 1px 2px #ebebeb;\n\t-moz-transition: 0.2s ease-out;\n\t-o-transition: 0.2s ease-out;\n\t-ms-transition: 0.2s ease-out;\n\t-webkit-transition: 0.2s ease-out;\n\ttransition: 0.2s ease-out;\n}\n/* END OF TOGGLES */\n\ninput#deposit:checked + label.button:before, input#capitalization:checked + label.button:before {\n\twidth: 33px;\n\theight: 21px;\n\tbackground: #e7f6f8;\n\tbox-shadow: inset 0 0 0px 1px #b4d6da;\n}\ninput#deposit:checked + label.button:after, input#capitalization:checked + label.button:after {\n\tleft: 13px;\n\tbox-shadow: inset 0 0 0 1px #b4d6da, 1px 1px 1px #ebebeb;\n}\n#deposit:checked ~ .savingscapital, #deposit:checked ~ .savingspercent, #deposit:checked ~ input#percentvalue {\n\tcolor: #4b4b4b;\n}\n.savingstitle14 {\n\tfont-family: \"Museo-300\";\n\tfont-size: 14px;\n\tcolor: #4b4b4b;\n\tposition: absolute;\n\tleft: 30%;\n\ttop: 70px;\n}\n.savingstitle12 {\n\tfont-family: \"Museo-300\";\n\tfont-size: 13px;\n\tcolor: #a2c1c6;\n\tfont-style: italic;\n\tposition: absolute;\n\tleft: 30%;\n\ttop: 94px;\n}\n.savingsdeposit {\n\tfont-family: \"Museo-300\";\n\tfont-size: 12px;\n\tcolor: #4b4b4b;\n\tposition: absolute;\n\tleft: 25px;\n\ttop:20px;\n}\n.savingscapital, .savingspercent {\n\tfont-family: \"Museo-300\";\n\tfont-size: 12px;\n\tcolor: #b5b5b5;\n\tposition: absolute;\n}\n.savingscapital {\n\tleft: 25px;\n\tbottom:-40px;\n}\n.savingspercent {\n\tleft: 25px;\n\tbottom:20px;\n}\n\n/* =============== INCOMES SLIDER =============== */\n#noincomes, #noexpenses {\n\twidth: 160px;\n\theight: 160px;\n\tposition: absolute;\n\tdisplay: block;\n\ttop: 30%; left: 0; right: 0;\n\tmargin: auto;\n\tcursor: pointer;\n\tz-index: 10;\n}\n.hoverplace {\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n}\n.hoverplace:hover ~ .plusitemtitle, .majorplusitem:hover ~ .plusitemtitle, .plusitemtitle:hover {\n\tcolor: #a2c1c6;\n}\n.majorplusitem {\n\ttop:0; bottom:0; left: 0; right: 0;\n\tmargin: auto;\n\tposition: absolute;\n\tdisplay: block;\n\twidth: 75px;\n\theight: 75px;\n\t-webkit-transition: 150ms;\n\t-moz-transition: 150ms;\n\t-o-transition: 150ms;\n\t-ms-transition: 150ms;\n\ttransition: 150ms;\n}\n.majorplusitem:hover {\n\t-webkit-transform: scale(1.1);\n\t-moz-transform: scale(1.1);\n\t-o-transform: scale(1.1);\n\t-ms-transform: scale(1.1);\n\ttransform: scale(1.1);\n}\n.majorplusitem:active {\n\t-webkit-transform: scale(1);\n\t-moz-transform: scale(1);\n\t-o-transform: scale(1);\n\t-ms-transform: scale(1);\n\ttransform: scale(1);\n}\n.plusitemtitle {\n\ttext-align: center;\n\twidth: 100%;\n\tfont-family: \"Museo-300\";\n\tfont-size: 14px;\n\tcolor: #4b4b4b;\n\tposition: absolute;\n\tbottom: 0;\n\t-webkit-transition: 0.25s;\n\t-moz-transition: 0.25s;\n\t-o-transition: 0.25s;\n\t-ms-transition: 0.25s;\n\ttransition: 0.25s;\n}\n.plusitemitalic {\n\ttext-align: center;\n\twidth: 100%;\n\tfont-family: \"Museo-300\";\n\tfont-size: 13px;\n\tcolor: #a2c1c6;\n\tfont-style: italic;\n\tposition: absolute;\n\ttop: 0;\n}\n.arrowup, .arrowdown {\n\tposition: absolute;\n\tbackground-size: 538px 84px;\n\twidth: 21px;\n\theight: 12px;\n}\n.arrowup {\n\tbackground-position: -30px 0;\n}\n.arrowdown {\n\tbackground-position: 0 0;\n}\n#incomeup, #incomedown {\n\tfont-family: \"Museo-300\";\n\tfont-size: 12px;\n\tcolor: #a2c1c6;\n\tfont-style: italic;\n\tposition: absolute;\n\tbottom: 0px;\n\tmargin: auto;\n\twidth: 90px;\n\theight: 12px;\n\tcursor: pointer;\n\ttext-align: center;\n}\n#incomeup {\n\tleft: 63%;\n}\n#incomedown {\n\tleft: 35%;\n}\n#incomeslider {\n\t-webkit-transition: 0.25s;\n\t-moz-transition: 0.25s;\n\t-o-transition: 0.25s;\n\t-ms-transition: 0.25s;\n\ttransition: 0.25s;\n\tposition: absolute; /*RELATIVE!*/\n\tbottom: 0px;\n\tmargin: auto;\n\twidth: 100%;\n}\n\n/* =============== EXPENSE SLIDER =============== */\n\n.plusitem {\n\ttop: -8px; left: 9%;\n\tmargin: auto;\n\tposition: absolute;\n\tcursor: pointer;\n\twidth: 33px;\n\theight: 33px;\n\t-webkit-border-radius: 75px;\n\t-moz-border-radius: 75px;\n\tborder-radius: 75px;\n\t-webkit-transition: 150ms;\n\t-moz-transition: 150ms;\n\t-o-transition: 150ms;\n\t-ms-transition: 150ms;\n\ttransition: 150ms;\n}\n.plusitemborder {\n\tposition: absolute;\n\tborder: 1px solid #aad6df;\n\t-webkit-border-radius: 75px;\n\t-moz-border-radius: 75px;\n\tborder-radius: 75px;\n\ttop:0; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n}\n.plusitem:hover {\n\t-webkit-transform: scale(1.15);\n\t-moz-transform: scale(1.15);\n\t-o-transform: scale(1.15);\n\t-ms-transform: scale(1.15);\n\ttransform: scale(1.15);\n}\n.plusitem:active {\n\t-webkit-transform: scale(1);\n\t-moz-transform: scale(1);\n\t-o-transform: scale(1);\n\t-ms-transform: scale(1);\n\ttransform: scale(1);\n}\n.plusitemone, .plusitemtwo {\n\tposition: absolute;\n\ttop: 0; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\tbackground-color: #aad6df;\n}\n.plusitemone {\n\twidth: 50%;\n\theight: 1px;\n}\n.plusitemtwo {\n\twidth: 1px;\n\theight: 50%;\n}\n\n#expenseup, #expensedown {\n\tfont-family: \"Museo-300\";\n\tfont-size: 12px;\n\tcolor: #a2c1c6;\n\tfont-style: italic;\n\tposition: absolute;\n\tbottom:0px;\n\tmargin: auto;\n\twidth: 90px;\n\theight: 12px;\n\tcursor: pointer;\n\ttext-align: center;\n}\n#expenseup {\n\tleft: 63%;\n}\n#expensedown {\n\tleft: 35%;\n}\n.frame {\n\ttext-align: left;\n\toverflow: hidden;\n\tposition: absolute;\n\ttop:50px; left: 0; right: 0; bottom: 0;\n\twidth: 100%;\n\theight: 284px;\n}\n.wrapper {\n\tposition: absolute;\n\ttop:0; left: 0; right: 0; bottom: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n#expenseslider {\n\t-webkit-transition: 0.25s;\n\t-moz-transition: 0.25s;\n\t-o-transition: 0.25s;\n\t-ms-transition: 0.25s;\n\ttransition: 0.25s;\n\tdisplay: none;\n\tposition: absolute; /*RELATIVE!*/\n\tbottom: 0px;\n\tmargin: auto;\n\twidth: 100%;\n}\n.itembackground {\n\tdisplay: block;\n\tposition: relative;\n\ttop: -26px;\n\tleft: 25px;\n}\n.incomeitem, .expenseitem {\n\t-webkit-transition: 350ms ease-out;\n\t-moz-transition: 350ms ease-out;\n\t-o-transition: 350ms ease-out;\n\t-ms-transition: 350ms ease-out;\n\ttransition: 350ms ease-out;\n\tbackground-position: 25px center;\n\tbackground-repeat: no-repeat;\n\tbackground-size: 42px 42px;\n\tborder-bottom: 1px solid #e2e2e2;\n\twidth: 100%;\n\theight: 70px;\n\tz-index: 500;\n\tcursor: pointer;\n\toverflow: hidden;\n}\n.incomeitem:hover, .expenseitem:hover {\n\tbackground-color: #fbfbfb;\n}\n.incomeitem:active, .expenseitem:active  {\n\tbackground-color: #f7f7f7;\n}\n.newitemadded {\n\t-webkit-animation: 4s newitemadded ease-out;\n\t-moz-animation: 4s newitemadded ease-out;\n\t-o-animation: 4s newitemadded ease-out;\n\t-ms-animation: 4s newitemadded ease-out;\n\tanimation: 4s newitemadded ease-out;\n}\n.title11museo300 {\n\tfont-family: \"Museo-100\";\n\tfont-size: 14px;\n\tcolor: #676767;\n\tposition: relative;\n\tleft: 35%;\n\ttop:12px;\n}\n.title9museo300 {\n\tfont-family: \"Museo-300\";\n\tfont-size: 9px;\n\tcolor: #676767;\n\tposition: relative;\n\tleft: 35%;\n\ttop:20px;\n}\n.bolddigit20 {\n\tfont-family: \"Museo-500\";\n\tfont-size: 20px;\n\tcolor: #111;\n}\n.lightdigit20 {\n\tfont-family: \"Museo-100\";\n\tfont-size: 20px;\n\tcolor: #111;\n}\n.lighttitle20 {\n\theight: 40px;\n\tfont-family: \"Museo-100\";\n\tfont-size: 20px;\n\tcolor: #4b4b4b;\n\tposition: absolute;\n\tleft: 35%;\n\tcursor: pointer;\n}\n\n/* ITEMS AND FRAMES ANIMATION */\n.sliderup {\n\t-webkit-transform: translateY(71px);\n\t-moz-transform: translateY(71px);\n\t-o-transform: translateY(71px);\n\t-ms-transform: translateY(71px);\n\ttransform: translateY(71px);\n}\n.sliderdown {\n\t-webkit-transform: translateY(-71px);\n\t-moz-transform: translateY(-71px);\n\t-o-transform: translateY(-71px);\n\t-ms-transform: translateY(-71px);\n\ttransform: translateY(-71px);\n}\n.endoflist {\n\t-webkit-animation: 0.5s endoflist ease-out;\n\t-moz-animation: 0.5s endoflist ease-out;\n\t-o-animation: 0.5s endoflist ease-out;\n\t-ms-animation: 0.5s endoflist ease-out;\n\tanimation: 0.5s endoflist ease-out;\n}\n.startoflist {\n\t-webkit-animation: 0.5s startoflist ease-out;\n\t-moz-animation: 0.5s startoflist ease-out;\n\t-o-animation: 0.5s startoflist ease-out;\n\t-ms-animation: 0.5s startoflist ease-out;\n\tanimation: 0.5s startoflist ease-out;\n}\n#expenseslider, #incomeslider {\n\t-webkit-animation: 1s frameanimate ease-out;\n\t-moz-animation: 0.8s frameanimate ease-out;\n\t-o-animation: 0.8s frameanimate ease-out;\n\t-ms-animation: 0.8s frameanimate ease-out;\n\tanimation: 0.8s frameanimate ease-out;\n}\n\n/* NOTES WINDOW */\n\n#add-notes {\n\tvisibility: hidden;\n\tposition: absolute;\n\ttop: 0; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\twidth: 465px;\n\theight: 530px;\n\tz-index: 2000;\n\topacity: 1;\n\t-webkit-backface-visibility: hidden;\n\t-moz-backface-visibility: hidden;\n\t-o-backface-visibility: hidden;\n\t-ms-backface-visibility: hidden;\n\tbackface-visibility: hidden;\n\t-webkit-perspective: 1300;\n\t-o-perspective: 1300px;\n\t-ms-perspective: 1300px;\n\t-moz-perspective: 1300px;\n\tperspective: 1300;\n}\n#add-notes >.modal-content {\n\theight: 610px;\n}\n.notes-title {\n\tfont-weight: 900;\n\tfont-family: \"Museo-500\";\n\tfont-size: 18px;\n\tcolor: #242424;\n\tposition: absolute;\n\twidth: 240px;\n\theight: 20px;\n\tmargin: auto;\n\tright: 0; left: 0; top: 20px;\n}\n.notes-save {\n\tdisplay: block;\n\tposition: absolute;\n\twidth: 100%;\n\theight: 42px;\n\tbottom: 20px;\n\tbackground-color: #cce5ec;\n\tcursor: pointer;\n\tline-height: 30px;\n}\n.notes-save:hover, .modal-save:hover {\n\tbackground-color: #d6ebf1;\n}\n.notes-save:active, .modal-save:active {\n\tbackground-color: #e0edf0;\n}\n.notes-input {\n\tline-height: 20px;\n\tdisplay: none;\n\tresize: none;\n\tvertical-align: top;\n\tborder: 1px solid #ccc;\n\tbackground-color: white;\n\tposition: absolute;\n\ttop: 78px; left: 0; right: 0;\n\tmargin: auto;\n\tfont-family: \"Museo-300\";\n\tfont-size: 13px;\n\tpadding: 20px;\n\tpadding-top: 30px;\n\twidth: 360px;\n\theight: 60%\n}\n\n/* MODAL WINDOWS */\n#overlay {\n\tdisplay: none;\n\tvisibility: hidden;\n\tposition: fixed;\n\twidth: 100%;\n\theight: 100%;\n\ttop: 0; left: 0;\n\tz-index: 1000;\n\topacity: 0;\n\tbackground: rgba(0,0,0,0.65);\n\t-webkit-transition: all 0.3s;\n\t-o-transition: all 0.3s;\n\t-ms-transition: all 0.3s;\n\t-moz-transition: all 0.3s;\n\ttransition: all 0.3s;\n}\n#add-modal {\n\tvisibility: hidden;\n\tposition: absolute;\n\ttop:50px; left: 0; right: 0; bottom: 0;\n\tmargin: auto;\n\twidth: 465px;\n\theight: 530px;\n\tz-index: 2000;\n\topacity: 1;\n\t-webkit-backface-visibility: hidden;\n\t-moz-backface-visibility: hidden;\n\t-o-backface-visibility: hidden;\n\t-ms-backface-visibility: hidden;\n\tbackface-visibility: hidden;\n\t-webkit-perspective: 1300px;\n\t-o-perspective: 1300px;\n\t-ms-perspective: 1300px;\n\t-moz-perspective: 1300px;\n\tperspective: 1300px;\n}\n.modal-content {\n\tdisplay: none;\n\tvisibility: hidden;\n\ttext-align: center;\n\ttext-transform: uppercase;\n\tbackground: white;\n\tbox-shadow: 0 0 15px rgba(0,0,0,0.2);\n\tposition: relative;\n\theight: 480px;\n\t-webkit-transform-style: preserve-3d;\n\t-moz-transform-style: preserve-3d;\n\ttransform-style: preserve-3d;\n\t-webkit-transform: rotateY(-60deg);\n\t-moz-transform: rotateY(-60deg);\n\t-ms-transform: rotateY(-60deg);\n\ttransform: rotateY(-60deg);\n\t-webkit-transition: all 0.3s;\n\t-o-transition: all 0.3s;\n\t-moz-transition: all 0.3s;\n\t-ms-transition: all 0.3s;\n\ttransition: all 0.3s;\n\topacity: 0;\n}\n\n.modal-show {\n\topacity: 1;\n\tvisibility: visible;\n}\n.modal-show .modal-content {\n\tvisibility: visible;\n\t-webkit-transform: rotateY(0deg);\n\t-moz-transform: rotateY(0deg);\n\t-ms-transform: rotateY(0deg);\n\ttransform: rotateY(0deg);\n\topacity: 1;\n}\n#overlay.modal-show {\n\topacity: 1;\n\tvisibility: visible;\n}\n.close-sign {\n\tbackground-position:  0 -22px;\n\tbackground-size: 538px 84px;\n\tposition: absolute;\n\twidth: 16px;\n\theight: 16px;\n\ttop: 20px; right: 26px;\n}\n.modal-close {\n\tposition: absolute;\n\twidth: 86px;\n\theight: 62px;\n\ttop: 0; right: 0;\n\tcursor: pointer;\n\tz-index: 3500;\n}\n.modal-title {\n\tfont-weight: 900;\n\tfont-family: \"Museo-500\";\n\tfont-size: 18px;\n\tcolor: #242424;\n\tposition: absolute;\n\twidth: 240px;\n\theight: 20px;\n\tmargin: auto;\n\tright: 0; left: 0; top: 20px;\n}\n.modal-save {\n\tdisplay: block;\n\tposition: absolute;\n\twidth: 100%;\n\theight: 42px;\n\ttop: 415px;\n\tbackground-color: #cce5ec;\n\tcursor: pointer;\n\tline-height: 30px;\n}\n.modal-save-icon {\n\tbackground-position: -60px 0;\n\tbackground-size: 538px 84px;\n\tposition: absolute;\n\twidth: 30px;\n\theight: 30px;\n\tleft: 32%; top: 6px;\n}\n.modal-save-text {\n\tposition: absolute;\n\tdisplay: inline-block;\n\tmargin: auto;\n\ttop:0; bottom:0; left: 32%;\n\twidth: 210px;\n\theight: 30px;\n\tfont-family: \"Museo-500\";\n\tvertical-align: middle;\n\tfont-size: 18px;\n\tcolor: white;\n}\n.modal-delete {\n\t-webkit-transition: 0.2s ease-out;\n\t-moz-transition: 0.2s ease-out;\n\t-ms-transition: 0.2s ease-out;\n\t-o-transition: 0.2s ease-out;\n\tdisplay: none;\n\tposition: absolute;\n\twidth: 56px;\n\theight: 42px;\n\ttop: 415px; right: 0;\n\tbackground-color: #d9ebf0;\n\tborder-left: 2px solid white;\n\tcursor: pointer;\n\tline-height: 30px;\n\toverflow: hidden;\n}\n.modal-delete:hover {\n\twidth: 380px;\n\tbackground-color: #facece;\n}\n.modal-delete:active {\n\tbackground-color: #ffe3e3;\n}\n#modaldeletecross {\n\tposition: absolute;\n\twidth: 30px;\n\theight: 30px;\n\tbackground-position: -320px 0;\n\tmargin-top: 6px;\n\tmargin-left: 13px;\n}\n.modal-delete-text {\n\tbackground-position: -320px 0;\n\tposition: absolute;\n\tdisplay: inline-block;\n\tmargin: auto;\n\ttop:0; bottom:0; left: 13px;\n\twidth: 190px;\n\theight: 30px;\n\tfont-family: \"Museo-500\";\n\tvertical-align: middle;\n\tfont-size: 18px;\n\tcolor: white;\n}\n.modalvalue {\n\ttext-align: center;\n\tdisplay: none;\n\tborder: 0px solid;\n\tbackground-color: transparent;\n\tposition: absolute;\n\ttop: 78px; left: 0; right: 0;\n\tmargin: auto;\n\tfont-family: \"Museo-500\";\n\tfont-size: 86px;\n\tpadding: 0;\n\twidth: 370px;\n\t-webkit-transition: 0.3s;\n\t-moz-transition: 0.3s;\n\t-ms-transition: 0.3s;\n\t-o-transition: 0.3s;\n}\n.modalvalueerror {\n\tcolor: #ffdd33;\n\t-webkit-animation: 0.5s modalvalueerror ease-out;\n\t-moz-animation: 0.5s modalvalueerror ease-out;\n\t-o-animation: 0.5s modalvalueerror ease-out;\n\t-ms-animation: 0.5s modalvalueerror ease-out;\n\tanimation: 0.5s modalvalueerror ease-out;\n}\n.modaltitle {\n\tdisplay: none;\n\ttext-transform: uppercase;\n\tposition: absolute;\n\ttop: 345px; left: 0; right: 0;\n\tmargin: auto;\n\ttext-align: center;\n\tborder: 1px solid #ccc;\n\tcolor: #4c4c4c;\n\tfont-family: \"Museo-300\", Arial;\n\tfont-size: 14px;\n\theight: 30px;\n\twidth: 296px;\n\tletter-spacing: 1px;\n\t-webkit-transition: all 0.4s;\n\t-o-transition: all 0.4s;\n\t-ms-transition: all 0.4s;\n\t-moz-transition: all 0.4s;\n\ttransition: all 0.4s;\n}\n.modaltitleerror {\n\tbackground-color: #ffdd33;\n}\n.modalselects {\n\tz-index: 2000;\n\ttext-transform: lowercase;\n\ttext-align: left;\n\tdisplay: block;\n\tposition: absolute;\n\ttop: 200px; left: 0; right: 0;\n\tmargin: auto;\n\twidth: 260px;\n\theight: 30px;\n}\n* {\n\tmargin: 0;\n\tpadding: 0;\n}\n.modalselects .selectbox {\n\tcolor: #333;\n\tvertical-align: middle;\n\tcursor: pointer;\n\tmargin: 5px;\n}\n.modalselects .selectbox .select {\n\twidth: 60px;\n\theight: 24px;\n\tpadding: 0 45px 0 10px;\n\tfont: 12px/24px Arial;\n\tborder: 1px solid #ccc;\n}\n.modalselects .selectbox .select .text {\n\tdisplay: block;\n\twidth: 100%;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n\toverflow: hidden;\n}\n.modalselects .selectbox .trigger .arrow {\n\tposition: absolute;\n\ttop: 11px;\n\tright: 10px;\n\tborder-left: 3px solid transparent;\n\tborder-right: 3px solid transparent;\n\tborder-top: 3px solid #a1a1a1;\n\twidth: 0;\n\theight: 0;\n\toverflow: hidden;\n}\n.modalselects .selectbox .dropdown {\n\ttop: 25px;\n\twidth: 115px;\n\tmargin: 0;\n\tpadding: 0 0;\n\tbackground: #FFF;\n\tborder: 1px solid #ccc;\n\tfont: 12px Arial;\n}\n\n.initicons-arrow {\n\tbackground-position: -32px -24px;\n\tbackground-size: 538px 84px;\n\tposition: absolute;\n\twidth: 15px;\n\theight: 9px;\n\tright: 0; top: 18px;\n}\n.initicons {\n\tcursor: pointer;\n\twidth: 75px;\n\theight: 42px;\n\tposition: absolute;\n\ttext-align: left;\n\ttop: 270px; left: 0; right: 0;\n\tmargin: auto;\n}\n.initicons img {\n\twidth: 42px;\n\theight: 42px;\n}\n.initicons:hover {\n\topacity: 0.8;\n}\n.initicons:active {\n\t-webkit-transform: scale(0.92);\n\t-moz-transform: scale(0.92);\n\t-o-transform: scale(0.92);\n\t-ms-transform: scale(0.92);\n\ttransform: scale(0.92);\n}\n.modalincomessurface {\n\tposition: absolute;\n\tz-index: 3000;\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: white;\n\tdisplay: none;\n}\n.modalexpensessurface {\n\tz-index: 3000;\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: white;\n\tdisplay: none;\n}\n#modalexpensetable {\n\ttext-align: center;\n\tbackground-color: white;\n\tposition: absolute;\n\ttop: 14%; left: 42px;\n\n}\n#modalincomestable {\n\ttext-align: center;\n\tbackground-color: white;\n\tposition: absolute;\n\ttop: 30%; left: 42px;\n}\n.modaltable tr, .modaltable-low tr {\n\tbackground-color: #ddeef1;\n}\n.modaltable td, .modaltable-low td {\n\tcursor: pointer;\n\twidth: 86px;\n\theight: 86px;\n}\n.imgbox {\n\tbackground-color: white;\n\tborder: 2px solid white;\n\t-webkit-transition: all 0.1s;\n\t-o-transition: all 0.1s;\n\t-ms-transition: all 0.1s;\n\t-moz-transition: all 0.1s;\n\ttransition: all 0.1s;\n\twidth: 86px;\n\theight: 86px;\n}\n.imgbox:hover {\n\tborder: 2px solid #ddeef1;\n}\n.modalborderwhite {\n\tborder: 2px solid white;\n}\n.iconbox {\n\tbackground-color: white;\n\tposition: absolute;\n\tmargin:22px;\n}\n.auto {background-position: -989px 0;}\n.gas {background-position: -602px 0;}\n.home {background-position: -473px 0;}\n.baby {background-position: -946px 0;}\n\n.cart {background-position: -903px 0;}\n.clothes {background-position: -817px 0;}\n.phone {background-position: -258px 0;}\n.utilities {background-position: -43px 0;}\n\n.island {background-position: -430px 0;}\n.earth {background-position: -731px 0;}\n.meal {background-position: -387px 0;}\n.sport {background-position: -129px 0;}\n\n.medical {background-position: -344px 0;}\n.tv {background-position: -86px 0;}\n.smoking {background-position: -172px 0;}\n.other {background-position: -301px 0;}\n\n.edu {background-position: -688px 0;}\n.graphs {background-position: -516px 0;}\n.wallet {background-position: 0 0;}\n.case {background-position: -860px 0;}\n\n.rub {background-position: -215px 0;}\n.euro {background-position: -645px 0;}\n.doll {background-position: -774px 0;}\n.gbp {background-position: -559px 0;}\n\n#chooseicon {\n\tposition: absolute;\n\tleft: 0; top: 0;\n}\n\n.imgbox:active {\n\t-webkit-transform: scale(0.8);\n\t-moz-transform: scale(0.8);\n\t-o-transform: scale(0.8);\n\t-ms-transform: scale(0.8);\n\ttransform: scale(0.8);\n}\n.modalforward {\n\t-webkit-animation: 0.4s modalforward ease-out;\n\t-moz-animation: 0.4s modalforward ease-out;\n\t-o-animation: 0.4s modalforward ease-out;\n\t-ms-animation: 0.4s modalforward ease-out;\n\tanimation: 0.4s modalforward ease-out;\n}\n.modalreverse {\n\t-webkit-animation: 0.3s modalreverse ease-out;\n\t-moz-animation: 0.5s modalreverse ease-out;\n\t-o-animation: 0.5s modalreverse ease-out;\n\t-ms-animation: 0.5s modalreverse ease-out;\n\tanimation: 0.3s modalreverse ease-out;\n}\n\n/* ============= STATISTIC PAGE STYLES =============*/\n\n/* MAIN BLOCK */\n#mainblock {\n\t-moz-transition: 0.3s ease-out;\n\t-o-transition: 0.3s ease-out;\n\t-ms-transition: 0.3s ease-out;\n\t-webkit-transition: 0.3s ease-out;\n\ttransition: 0.3s ease-out;\n\twidth: 1370px;\n\theight: 680px;\n\tposition: absolute;\n\tmargin: auto;\n\tleft:0;\tright:0; top:22%;\n}\n.flag {\n\theight: 19px;\n\tborder: 1px solid #c7dade;\n\tfont-family: \"Museo-100\";\n\tfont-size: 11px;\n\tcolor: #242424;\n\ttext-transform: uppercase;\n\tfont-style: italic;\n\tline-height: 20px;\n\tpadding-left: 20px;\n\tleft: 0;\n}\n#deltatitle {\n\tposition: absolute;\n\ttop: -10px;\n\twidth: 130px;\n}\n#savingstitle {\n\tposition: absolute;\n\tbottom: 240px;\n\twidth: 90px;\n}\n.triangle {\n\tposition: absolute;\n\tbottom: -14px;\n\tleft: -1px;\n\twidth: 15px;\n\theight: 13px;\n\tbackground-position: -523px 0;\n}\n/* SAVINGS CHART */\n#savingschart {\n\tposition: absolute;\n\twidth: 504px;\n\theight: 222px;\n\tbottom: 50px;\n\tright: 0;\n}\n#horizontal, #chartline {\n\tposition: absolute;\n}\n#month0 {\n\tposition: absolute;\n\tleft:0;\n\tbottom: 0;\n\tdisplay: block;\n\tmargin-right: -4px;\n\twidth: 0;\n\theight: 68px;\n\tborder-left: 1px solid #cdc6c6;\n}\n#month0 .small-month-circle, #month0 .month-name {\n\tdisplay: block;\n}\n#month0 .month-name {\n\tbottom: 32%;\n}\n#month12 .month-name {\n\twidth: 20px;\n\theight: 16px;\n}\n.months {\n\tposition: relative;\n\tdisplay: inline-block;\n\tmargin-right: -4px;\n\twidth: 42px;\n\theight: 100%;\n}\n.in-month {\n\tposition: absolute;\n\tbottom: 0;\n\twidth: 100%;\n\theight: 50%;\n}\n.small-month-circle {\n\tposition: absolute;\n\tright: -5px;\n\ttop: -4px;\n\twidth: 7px;\n\theight: 7px;\n\tborder: 1px solid #b9b2b4;\n\tborder-radius: 20px;\n\tbackground-color: white;\n}\n.large-month-circle {\n\tposition: absolute;\n\tright: -10px;\n\ttop: -10px;\n\twidth: 17px;\n\theight: 17px;\n\tborder: 1px solid #b9b2b4;\n\tborder-radius: 20px;\n\tbackground-color: white;\n\tdisplay: block;\n}\n.bluedot {\n\tposition: absolute;\n\tright: 3px;\n\ttop: 3px;\n\twidth: 11px;\n\theight: 11px;\n\tborder-radius: 20px;\n\tbackground-color: #d5e4e7;\n}\n.large-month-val {\n\ttext-transform: uppercase;\n\ttext-align: right;\n\tposition: absolute;\n\ttop: -34px; right: 5px;\n\twidth: 160px;\n\theight: 20px;\n\tfont-family: \"Museo-500\";\n\tfont-size: 22px;\n\tcolor: #333;\n}\n.large-month-val .curr {\n\tfont-family: \"Museo-100\";\n\tfont-size: 12px;\n}\n.month-val {\n\ttext-transform: uppercase;\n\ttext-align: center;\n\tposition: absolute;\n\tbottom: -32px; left: -18px;\n\twidth: 120px;\n\theight: 14px;\n\tfont-family: \"Museo-300\";\n\tfont-size: 15px;\n\tcolor: #333;\n\tdisplay: none;\n\tbackground-color: white;\n}\n.month-val .curr {\n\tfont-family: \"Museo-100\";\n\tfont-size: 10px;\n}\n.month-name {\n\tposition: absolute;\n\tbottom: 40%; right: -11px;\n\tfont-family: \"Museo-300\";\n\ttext-transform: uppercase;\n\ttext-align: center;\n\tfont-size: 10px;\n\tcolor: #948b8e;\n\tbackground-color: white;\n\theight: 21px;\n\twidth: 25px;\n\tline-height: 21px;\n\tdisplay: none;\n}\n#month6, #month12{\n\tborder-right: 1px solid #cdc6c6;\n}\n#month6 .small-month-circle, #month6 .month-name, #month12 .month-name {\n\tdisplay: block;\n}\n.months:hover .in-month {\n\tborder-right: 1px solid #cdc6c6;\n}\n.months:hover .small-month-circle,  .months:hover .month-name, .months:hover .month-val  {\n\tdisplay: block;\n}\n/* SAVINGS SLIDER */\n#savings-slider-container {\n\twidth: 260px;\n\theight: 50px;\n\tposition: absolute;\n\tbottom: 70px; left: 40px;\n}\n.savings-slider {\n\ttext-transform: uppercase;\n\tposition: absolute;\n\ttop: -90px;\n\tfont-family: \"Museo-300\";\n\tfont-size: 11px;\n\tcolor: #5e5e5e;\n}\n#savingsTip0, #savingsTip100 {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 35px;\n\tfont-family: \"Museo-300\";\n\tfont-style: italic;\n\tfont-size: 11px;\n\tcolor: #a59b9e;\n\tdisplay: none;\n}\n#savingsTip0 {\n\tbottom: -5px; left: 38px;\n}\n#savingsTip100 {\n\tbottom: -5px;\n}\n.noUi-target,.noUi-target *\n{\n\t-webkit-touch-callout:none;\n\t-webkit-user-select:none;\n\t-ms-touch-action:none;\n\t-ms-user-select:none;\n\t-moz-user-select:none;\n\t-moz-box-sizing:border-box;\n\tbox-sizing:border-box\n}\n.noUi-base {\n\twidth:100%;\n\theight:100%;\n\tposition:absolute;\n}\n.noUi-origin {\n\tposition:absolute;\n\tright:0;\n\ttop:0;\n}\n.noUi-handle {\n\tcontent: \"\";\n\tposition:relative;\n\tz-index:1;\n\tborder: 1px solid #c4d5d9;\n\tcursor:default;\n\t-webkit-border-radius: 23px;\n\t-ms-border-radius: 23px;\n\t-o-border-radius: 23px;\n\t-moz-border-radius: 23px;\n\tborder-radius: 23px;\n\tbackground-position: -516px -17px;\n}\n.noUi-handle-upper {\n\twidth: 3px;\n}\n.noUi-stacking .noUi-handle {\n\tz-index:10\n}\n\n.noUi-stacking+.noUi-origin {\n\tz-index:-1\n}\n.noUi-state-tap .noUi-origin {\n\t-webkit-transition:left .3s,top .3s;\n\ttransition:left .3s,top .3s\n}\n.noUi-state-drag * {\n\tcursor:inherit!important\n}\n.noUi-horizontal {\n\theight: 4px\n}\n.noUi-horizontal .noUi-handle {\n\twidth:23px;\n\theight:23px;\n\tleft:-12px;\n\ttop:-9px\n}\n.noUi-background {\n\tbackground:#d9e7ea;\n\tborder-left: 2px solid #a39a9d;\n\tborder-right: 2px solid #a39a9d;\n}\n.noUi-dragable\n{\n\tcursor: w-resize\n}\n.noUi-vertical .noUi-dragable {\n\tcursor:n-resize;\n}\n.noUi-active {\n\tborder: 1px solid #95b4b8;\n}\n.noUi-handle:after {\n\tcontent: attr(percent);\n\ttext-align: center;\n\tdisplay:block;\n\tposition:absolute;\n\twidth: 30px; height: 30px;\n\tleft: 4px; top: -38px;\n\tfont-family: \"Museo-300\";\n\tfont-size: 11px;\n\tcolor: #2f2f2f;\n\tbackground-position: -414px 0;\n\tline-height: 18px;\n}\n.noUi-handle:before {\n\tcontent: attr(value);\n\ttext-align: center;\n\tdisplay:block;\n\tposition:absolute;\n\theight:1px;\n\twidth:180px;\n\theight: 15px;\n\tleft: -80px; top: 35px;\n\tfont-family: \"Museo-100\";\n\tfont-size: 16px;\n\tcolor: #2f2f2f;\n}\n[disabled] .noUi-connect,[disabled].noUi-connect {\n\tbackground:#B8B8B8\n}\n[disabled] .noUi-handle {\n\tcursor:not-allowed\n}\n/* SAVINGS CIRCLE */\n#savingscircles {\n\ttext-align: center;\n\twidth: 385px;\n\theight: 385px;\n\tposition: absolute;\n\tleft: 390px; bottom: 30px;\n\tz-index: 800;\n}\n#after-savings, #before-savings {\n\tborder: 1px solid #c9dadd;\n\t-webkit-border-radius: 350px;\n\t-ms-border-radius: 350px;\n\t-o-border-radius: 350px;\n\t-moz-border-radius: 350px;\n\tborder-radius: 350px;\n}\n#after-savings {\n\twidth: 82%;\n\theight: 82%;\n\tposition: absolute;\n\tright:0; top:0;\n}\n#before-savings {\n\twidth: 46%;\n\theight: 46%;\n\tposition: absolute;\n\tleft:0; bottom:0;\n}\n.savings-circle-title {\n\tfont-size: 12px;\n\tposition: absolute;\n\tmargin: auto;\n\ttop: 30%; left:0; right:0;\n}\n.savings-circle-currency {\n\tfont-size: 11px;\n\tposition: absolute;\n\tmargin: auto;\n\tbottom: 22%; left:0; right:0;\n}\n#before-savings-value {\n\tposition: absolute;\n\twidth: 130%;\n\theight: 100%;\n\tmargin: auto;\n\tleft:-15%; right: 0;\n\ttext-align: center;\n\tline-height: 165px;\n\tfont-size: 30px;\n\ttop: 8%;\n}\n#after-savings-value {\n\twidth: 130%;\n\theight: 100%;\n\tposition: absolute;\n\tmargin: auto;\n\tleft:-15%; right: 0;\n\ttop:7%;\n\ttext-align: center;\n\tline-height: 290px;\n}\n/* LINES CONTAINER */\n#incomes-lines-container, #expenses-lines-container {\n\t-moz-transition: 0.3s ease-out;\n\t-o-transition: 0.3s ease-out;\n\t-ms-transition: 0.3s ease-out;\n\t-webkit-transition: 0.3s ease-out;\n\ttransition: 0.3s ease-out;\n\tbackground-color: white;\n\twidth: 270px;\n\theight: 300px;\n\tposition: absolute;\n\tleft: 375px; top: 0;\n\tz-index: 200;\n\tpadding-top: 65px;\n\tpadding-left: 30px;\n}\n#incomes-lines-container {\n\tdisplay: none;\n}\n.lines-title {\n\tcursor: pointer;\n\tposition: absolute;\n\twidth: 400px;\n\ttop: 35px;\n\ttext-transform: uppercase;\n\tfont-family: \"Museo-300\";\n\tfont-size: 13px;\n\tcolor: black;\n}\n.lineitemtitle {\n\tposition: absolute;\n\ttop: 15px;\n\twidth: 280px;\n\theight: 70px;\n}\n.lineitemvalue {\n\tfont-family: \"Museo-300\";\n\tfont-size: 25px;\n\tposition: absolute;\n\ttop: 15px;\n\tleft: 64px; top:50px;\n\twidth: 180px;\n}\n.lineitemcurr {\n\tfont-size: 11px;\n}\n.lineitempercent {\n\tfont-family: \"Museo-500\";\n\tfont-size: 10px;\n\tcolor: #928588;\n\tposition: absolute;\n\ttop: -8px;\n\tleft: -27px;\n}\n.grey {\n\tcolor: #adadad;\n}\n.greysmall {\n\tfont-size: 9px;\n}\n.itemline {\n\t-moz-transition: 0.5s ease-out;\n\t-o-transition: 0.5s ease-out;\n\t-ms-transition: 0.5s ease-out;\n\t-webkit-transition: 0.5s ease-out;\n\ttransition: 0.5s ease-out;\n\tfont-size: 13px;\n\tcursor: pointer;\n\tposition: relative;\n\theight: 9px;\n\tborder-top: 2px solid #d5e4e7;\n\toverflow: hidden;\n\tbackground-color: white;\n\twidth: 1%;\n}\n.itemlinebackground {\n\tposition: absolute;\n\ttop: 36px; left:0;\n\twidth: 42px;\n\theight: 42px;\n}\n.leftpoint {\n\twidth: 2px;\n\theight: 2px;\n\tbackground-color: #a39a9d;\n\tposition: absolute;\n\ttop:-2px; left:0;\n}\n.activeline {\n\tborder-top: 4px solid #a39a9d;\n\theight: 88px;\n\toverflow: visible;\n}\n/* LINES CURSOR */\n#expense-cursor {\n\t-webkit-transform: rotate(68deg);\n\t-moz-transform: rotate(68deg);\n\t-ms-transform: rotate(68deg);\n\t-o-transform: rotate(68deg);\n\ttransform: rotate(68deg);\n\twidth: 310px;\n\theight: 310px;\n\tposition: absolute;\n\tleft: 225px; top: -106px;\n\tz-index: 100;\n\tdisplay: none;\n\tcursor: pointer;\n}\n#incomes-cursor {\n\t-webkit-transform: rotate(52deg);\n\t-moz-transform: rotate(52deg);\n\t-ms-transform: rotate(52deg);\n\t-o-transform: rotate(52deg);\n\ttransform: rotate(52deg);\n\twidth: 370px;\n\theight: 370px;\n\tposition: absolute;\n\tleft: 227px; top: -120px;\n\tz-index: 100;\n\tdisplay: none;\n\tcursor: pointer;\n}\n.cursorline {\n\twidth: 1px;\n\theight: 50%;\n\tposition: absolute;\n\tbottom: 0; left: 149px;\n\tbackground-color: #727272;\n}\n.cursorpoint {\n\tposition: absolute;\n\tbottom: -3px; left: 147px;\n\twidth: 6px;\n\theight: 6px;\n\tborder-radius: 10px;\n\tbackground-color: #727272;\n}\n/* LARGE CIRCLE */\n#outermaindiv  {\n\tcolor: black;\n\tposition: absolute;\n\twidth: 265px;\n\theight: 265px;\n\tposition: absolute;\n\ttop: 50px; left:10px;\n\tz-index: 0;\n\tcursor: pointer;\n}\n#innermaindiv  {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\ttop:0; left:0;\n\tmargin: 21px;\n}\n#outermaincursordiv  {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\ttop:0;\n\tz-index: 500;\n\tmargin: -19px;\n}\n.linesbackground {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-image: url(\"../images/linesbackground.png\");\n\tbackground-repeat: repeat;\n}\n.topmaincircletitle {\n\twidth: 100px;\n\theight: 30px;\n\ttext-align: center;\n\tposition: absolute;\n\tmargin: auto;\n\tleft: 0; right: 0; top:84px;\n\tfont-size: 15px;\n\tfont-style: italic;\n}\n.bottommaincircletitle {\n\twidth: 100px;\n\theight: 30px;\n\ttext-align: center;\n\tposition: absolute;\n\tmargin: auto;\n\tleft: 0; right: 0; bottom:60px;\n\tfont-size: 13px;\n}\n#outer-circle-value {\n\twidth: 100%;\n\theight: 100%;\n\ttext-align: center;\n\tposition: absolute;\n\tmargin: auto;\n\tline-height: 265px;\n\ttop: 5px;\n\tfont-size: 48px;\n}\n.lightcircletitle {\n\tfont-family: \"Museo-100\";\n\ttext-transform: uppercase;\n}\n.boldcircletitle {\n\tfont-family: \"Museo-500\";\n\ttext-transform: uppercase;\n}\n#maincircle100percent {\n\twidth: 30px;\n\theight: 15px;\n\tposition: absolute;\n\ttop: 50px; right: 100px;\n\tfont-size: 12px;\n}\n#maincircleline {\n\twidth: 1px;\n\theight: 27px;\n\tposition: absolute;\n\ttop: 17px; right: 131px;\n\tbackground-color: #898989;\n\tbox-shadow: -1px 0 1px #acacac;\n}\n#outer-circle-percent {\n\tdisplay: none;\n\twidth: 30px;\n\theight: 30px;\n\tposition: absolute;\n\tleft: 0; right: 0; top: 0;\n\tfont-size: 11px;\n\tz-index: 1000;\n}\n/* PENDANTS */\n#firstpendant {\n\toverflow: visible;\n\tz-index: -100;\n\tposition: absolute;\n\tbottom: 0; left:60px;\n\twidth: 60px;\n\theight: 100px;\n}\n#firstpendant > .pendant-circle {\n\tbottom: 77px;\n\tborder: 2px solid #d3d8ce;\n}\n#firstpendant > .pendantline {\n\tbottom: 90px;\n\tbackground-color: #d3d8ce;\n}\n#firstpendant > .pendantfont {\n\tbottom: 50px;\n}\n#secondpendant {\n\toverflow: visible;\n\tz-index: -100;\n\tposition: absolute;\n\tbottom: 0; left:282px;\n\twidth: 45px;\n\theight: 100px;\n}\n#secondpendant > .pendant-circle {\n\tbottom: 57px;\n\tborder: 2px solid #b9b2b4;\n}\n#secondpendant > .pendantline {\n\tbottom: 70px;\n\tbackground-color: #b9b2b4;\n}\n#secondpendant > .pendantfont {\n\tbottom: 30px;\n}\n#thirdpendant {\n\toverflow: visible;\n\tz-index: -100;\n\tposition: absolute;\n\tbottom: 0; right:60px;\n\twidth: 45px;\n\theight: 100px;\n}\n#thirdpendant > .pendant-circle {\n\tbottom: 37px;\n\tborder: 2px solid #abb1bf;\n}\n#thirdpendant > .pendantline {\n\tbottom: 50px;\n\tbackground-color: #abb1bf;\n}\n#thirdpendant > .pendantfont {\n\tbottom: 10px;\n}\n.pendant-circle {\n\tposition: absolute;\n\tleft: 17px;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 20px;\n}\n.pendantfont {\n\tposition: absolute;\n\ttext-align: center;\n\twidth: 70px;\n\tfont-size: 11px;\n\tfont-style: italic;\n\tleft: -12px;\n}\n.pendantline {\n\tposition: absolute;\n\tleft: 22px;\n\twidth: 1px;\n\theight: 120%;\n}\n/* SMALL CIRCLES */\n#movingcircle-1, #movingcircle-2, #movingcircle-3 {\n\t-webkit-animation: 1s spincircle ease-out infinite;\n\t-moz-animation: 1s spincircle ease-out infinite;\n\t-o-animation: 1s spincircle ease-out infinite;\n\t-ms-animation: 1s spincircle ease-out infinite;\n\tanimation: 1s spincircle ease-out infinite;\n}\n.flippedcard {\n\t-webkit-transform: rotateY(-180deg);\n\t-moz-transform: rotateY(-180deg);\n\t-ms-transform: rotateY(-180deg);\n\t-o-transform: rotateY(-180deg);\n\ttransform: rotateY(-180deg);\n}\n.flippedcardinfo {\n\t-webkit-transform: rotateY(180deg);\n\t-moz-transform: rotateY(180deg);\n\t-ms-transform: rotateY(180deg);\n\t-o-transform: rotateY(180deg);\n\ttransform: rotateY(180deg);\n}\n#small-circles-container {\n\tposition: absolute;\n\tright: 0; top: 40px;\n\twidth: 608px;\n\theight: 290px;\n\toverflow: visible;\n}\n#firstcirclediv, #secondcirclediv, #thirdcirclediv {\n\t-webkit-perspective: 1000;\n\t-moz-perspective: 1000px;\n\t-ms-perspective: 1000px;\n\t-o-perspective: 1000px;\n\tperspective: 1000;\n\t-moz-transition: 0.3s ease-out;\n\t-o-transition: 0.3s ease-out;\n\t-ms-transition: 0.3s ease-out;\n\t-webkit-transition: 0.3s ease-out;\n\ttransition: 0.3s ease-out;\n\tcursor: pointer;\n\tposition: relative;\n\ttop: 0;\n\twidth: 200px;\n\theight: 180px;\n\tdisplay: inline-block;\n\tbackground-color: white;\n}\n/* Backfaces */\n\n.circlesselect {\n\tz-index: 2000;\n\ttext-transform: uppercase;\n\ttext-align: center;\n\tdisplay: block;\n\tposition: absolute;\n\ttop: 94px; left: 0; right: 20px;\n\tmargin: auto;\n\twidth: 136px;\n\theight: 80px;\n}\n.circlesselect > .selectbox .select {\n\tposition: absolute;\n\tmargin: auto;\n\twidth: 136px;\n\theight: 22px;\n\tpadding: 0 10px 0 10px;\n\tfont: 10px/23px \"Museo-500\";\n\tborder: 1px solid #ccc;\n}\n.circlesselect > .selectbox .dropdown {\n\ttop: 23px;\n\twidth: 156px;\n\tmax-height: 220px;\n\tmargin: 0;\n\tpadding: 0 0;\n\tbackground: #FFF;\n\tborder: 1px solid #ccc;\n\tfont-family: \"Museo-300\";\n\tfont-size: 10px;\n}\n\n\n\n\n\n#circle-select-1-back, #circle-select-2-back, #circle-select-3-back  {\n\tposition: absolute;\n\tmargin: auto;\n\tleft: 0; right: 0; top: 30px;\n\twidth: 42px;\n\theight: 42px;\n}\n.flippedcircle {\n\t-webkit-transform: rotateY(-180deg);\n\t-moz-transform: rotateY(-180deg);\n\t-ms-transform: rotateY(-180deg);\n\t-o-transform: rotateY(-180deg);\n\ttransform: rotateY(-180deg);\n}\n.frontcircle, .backcircle {\n\t-webkit-backface-visibility: hidden;\n\t-moz-backface-visibility: hidden;\n\t-ms-backface-visibility: hidden;\n\t-o-backface-visibility: hidden;\n\tbackface-visibility: hidden;\n\toverflow: visible;\n\tposition: absolute;\n}\n.frontcircle {\n\t-moz-transition: 0.3s ease-out;\n\t-o-transition: 0.3s ease-out;\n\t-ms-transition: 0.3s ease-out;\n\t-webkit-transition: 0.3s ease-out;\n\ttransition: 0.3s ease-out;\n\tposition: absolute;\n\tmargin: auto;\n\tright: 0; left: 0;\n\tz-index: 2;\n\twidth: 165px;\n\theight: 165px;\n}\n.backcircle {\n\t-webkit-transform: rotateY(180deg);\n\t-moz-transform: rotateY(180deg);\n\t-ms-transform: rotateY(180deg);\n\t-o-transform: rotateY(180deg);\n\ttransform: rotateY(180deg);\n\tz-index: 1;\n\tposition: absolute;\n\ttop: -30px;\n\tleft: 0; right: 0;\n\twidth: 200px;\n\theight: 200px;\n}\n.backcircle .circletoptitle {\n\twidth: 170px;\n\theight: 50px;\n\tline-height: 14px;\n\tbottom: -85px;\n}\n#firstcirclediv {\n\tleft: -16px;\n}\n#secondcirclediv {\n\tleft: 0;\n}\n#thirdcirclediv {\n\tleft: 16px;\n}\n#firstcircledivflipper, #secondcircledivflipper, #thirdcircledivflipper {\n\t-webkit-transform-style: preserve-3d;\n\t-moz-transform-style: preserve-3d;\n\t-ms-transform-style: preserve-3d;\n\t-o-transform-style: preserve-3d;\n\ttransform-style: preserve-3d;\n\t-webkit-transition: 1000ms;\n\t-moz-transition: 1000ms;\n\t-ms-transition: 1000ms;\n\t-o-transition: 1000ms;\n\ttransition: 1000ms;\n\tposition: relative;\n\toverflow: visible;\n}\n.frontcircle:active {\n\t-webkit-transform: scale(0.8);\n\t-moz-transform: scale(0.8);\n\t-o-transform: scale(0.8);\n\t-ms-transform: scale(0.8);\n\ttransform: scale(0.8);\n}\n.cursordiv  {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\ttop: 0;\n\tz-index: 600;\n\tmargin: -12px;\n}\n.circletoptitle  {\n\t-moz-transition: 0.3s ease-out;\n\t-o-transition: 0.3s ease-out;\n\t-ms-transition: 0.3s ease-out;\n\t-webkit-transition: 0.3s ease-out;\n\ttransition: 0.3s ease-out;\n\ttext-align: center;\n\toverflow: hidden;\n\tfont-style: italic;\n\tfont-size: 10px;\n\tposition: absolute;\n\twidth: 120px;\n\theight: 12px;\n\tmargin: auto;\n\tleft: 0; right: 0; top: 52px;\n}\n.circlevalue {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\ttop: 4px;\n\ttext-align: center;\n\tline-height: 165px;\n\tfont-size: 31px;\n}\n.bottomcircletitle {\n\twidth: 100px;\n\theight: 30px;\n\ttext-align: center;\n\tposition: absolute;\n\tmargin: auto;\n\tleft: 0; right: 0; top: 110px;\n\tfont-size: 10px;\n}\n#first-circle-percent, #second-circle-percent, #third-circle-percent {\n\tdisplay: none;\n\twidth: 30px;\n\theight: 30px;\n\tposition: absolute;\n\tleft: 0; right: 0; top: 0;\n\tfont-size: 11px;\n}\n.circle100percent {\n\twidth: 30px;\n\theight: 15px;\n\tposition: absolute;\n\ttop: 30px; right: 52px;\n\tfont-size: 9px;\n}\n.circleline {\n\twidth: 1px;\n\theight: 17px;\n\tposition: absolute;\n\ttop: 11px;\n\tright: 82px;\n\tbackground-color: #898989;\n}\n#setcurrency {\n\tright: -18px;\n\ttop: -15%;\n\tposition: absolute;\n\twidth: 170px;\n\theight: 42px;\n}\n#rubcurr, #eurcurr, #usdcurr {\n\tvertical-align: top;\n\ttext-align: center;\n\tposition: relative;\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tcolor: #444;\n\tdisplay: inline-block;\n\twidth: 42px;\n\theight: 42px;\n\tborder-radius: 42px;\n\tmargin-right: 7px;\n\tline-height: 43px;\n}\n#rubcurr:hover, #eurcurr:hover, #usdcurr:hover {\n\tborder: 2px solid #f6f6f6;\n}\n.currunchecked {\n\tcursor: pointer;\n\tfont-family: \"Museo-100\";\n\tborder: 2px solid white;\n}\n.currchecked {\n\tcursor: default;\n\tfont-family: \"Museo-500\";\n\tborder: 2px solid #e8e8e8;\n}\n\n/* ============== CONDITIONS FOR DIFFERENT SCREEN SIZES ============== */\n@media screen and (max-height: 870px) {\n\t#mainblock {\n\t\theight: 620px; top:19%;\n\t}\n\t#savingschart {\n\t\tbottom: 10px;\n\t}\n\t#savingscircles {\n\t\tbottom: 0px;\n\t}\n\t#savings-slider-container {\n\t\tbottom: 20px;\n\t}\n\t#savingstitle {\n\t\tbottom: 190px;\n\t}\n\t#small-circles-container {\n\t\theight: 274px;\n\t}\n\n}\n@media screen and (max-height: 780px) {\n\t#mainblock {\n\t\theight: 590px; top:17%;\n\t}\n\t#savingschart {\n\t\tbottom: 20px;\n\t}\n\t#savingscircles {\n\t\tbottom: 25px;\n\t}\n\t#savings-slider-container {\n\t\tbottom: 30px;\n\t}\n\t#savingstitle {\n\t\tbottom: 200px;\n\t}\n\t#logo_statistic {\n\t\ttop: -25px;\n\t\t-webkit-transform: rotateY(180deg) scale(0.8);\n\t\t-moz-transform: rotateY(180deg) scale(0.8);\n\t\t-ms-transform: rotateY(180deg) scale(0.8);\n\t\t-o-transform: rotateY(180deg) scale(0.8);\n\t\ttransform: rotateY(180deg) scale(0.8);\n\t}\n\t#small-circles-container {\n\t\theight: 274px;\n\t}\n\t#setcurrency {\n\t\ttop: -12%;\n\t}\n\t#infopage {\n\t\theight: 610px;\n\t\tmargin-top: -70px;\n\t}\n\t#infosubtitle {\n\t\ttop: 40px;\n\t}\n\t#infotitle {\n\t\ttop: 75px;\n\t}\n}\n@media screen and (max-height: 700px) {\n\t.month-val {\n\t\tbottom: 5px;\n\t}\n\t#mainblock {\n\t\theight: 582px; top:14%;\n\t}\n\t#savingschart {\n\t\tbottom: 20px;\n\t}\n\t#savingscircles {\n\t\tbottom: 10px;\n\t}\n\t#savings-slider-container {\n\t\tbottom: 30px;\n\t}\n\t#savingstitle {\n\t\tbottom: 200px;\n\t}\n\t#logo_statistic {\n\t\ttop: -35px;\n\t\t-webkit-transform: rotateY(180deg) scale(0.7);\n\t\t-moz-transform: rotateY(180deg) scale(0.7);\n\t\t-ms-transform: rotateY(180deg) scale(0.7);\n\t\t-o-transform: rotateY(180deg) scale(0.7);\n\t\ttransform: rotateY(180deg) scale(0.7);\n\t}\n\t#small-circles-container {\n\t\theight: 270px;\n\t}\n\t#setcurrency {\n\t\ttop: -10%;\n\t}\n}\n\n@media screen and (max-width: 1100px) {\n\t.columns {\n\t\twidth: 300px; top:24%;\n\t}\n\t#savings {\n\t\tleft: 20%; right: -450px; bottom: 0;\n\t}\n\t#incomes {\n\t\tleft: -20%; right: 450px; bottom: 0;\n\t}\n\t#expenses {\n\t\tleft: 0; right: 0; bottom: 0;\n\t}\n}\n@media screen and (min-width: 1100px) {\n\t.columns {\n\t\twidth: 340px; top:24%;\n\t}\n\t#savings {\n\t\tleft: 21%; right: -490px; bottom: 0;\n\t}\n\t#incomes {\n\t\tleft: -21%; right: 490px; bottom: 0;\n\t}\n\t#expenses {\n\t\tleft: 0; right: 0; bottom: 0;\n\t}\n}\n@media screen and (max-height: 850px) {\n\t#logo_greeting, #logo_settings {\n\t\tbackground: url(\"../images/logo_large.gif\") no-repeat;\n\t\tbackground-position: top center;\n\t\tbackground-size: 137px 204px;\n\t\tz-index: 1000;\n\t\twidth: 172px;\n\t\theight: 204px;\n\t\ttop: 5%;\n\t}\n\t#settings_hat {\n\t\theight: 28%;\n\t}\n\t.columns {\n\t\ttop:16%;\n\t}\n\t#logo_settings {\n\t\theight: 130px;\n\t\ttop: 8%;\n\t}\n\t#savebutton {\n\t\tbottom: 3%;\n\t}\n\t#incomeup, #incomedown, #expenseup, #expensedown {\n\t\tbottom: 20px;\n\t}\n}\n@media screen and (max-height: 700px) {\n\t#settings_hat {\n\t\theight: 22%;\n\t}\n\t#add-notes >.modal-content {\n\t\theight: 530px;\n\t}\n\t.notes-input {\n\t\theight: 56%\n\t}\n\t#logo_greeting, #logo_settings {\n\t\tbackground: url(\"../images/logo.gif\") no-repeat;\n\t\tbackground-size: 114px 145px;\n\t\twidth: 114px;\n\t\theight: 145px;\n\t\ttop: 8%;\n\t}\n\t.columns {\n\t\ttop:16%;\n\t}\n\t#logo_settings {\n\t\theight: 100px;\n\t}\n\t#bubble {\n\t\tright: -70px;\n\t}\n\t#savebutton {\n\t\tbottom: 1%;\n\t}\n\t#incomeup, #incomedown, #expenseup, #expensedown {\n\t\tbottom: 20px;\n\t}\n\t#piggy {\n\t\tbackground: url(\"../images/piggy.gif\") no-repeat;\n\t\tbackground-size: 149px 149px;\n\t\twidth: 149px;\n\t\theight: 149px;\n\t}\n\t#logotext {\n\t\tbackground: url(\"../images/logotext.gif\") no-repeat;\n\t\tsize: 163px 67px;\n\t\twidth: 163px;\n\t\theight: 67px;\n\t\tmargin-top: 24px;\n\t}\n\t#wrapper {\n\t\ttop: 305px;\n\t}\n\t#secondenter{\n\t\tbottom: 115px;\n\t}\n\t#preloader{\n\t\tbottom: 99px;\n\t}\n}\n/* STATISTIC PAGE CONDITIONS */\n@media screen and (max-width: 1500px) {\n\t#mainblock {\n\t\twidth: 1250px;\n\t}\n\t#incomes-lines-container, #expenses-lines-container {\n\t\tleft: 315px; width: 210px;\n\t}\n\t#savingscircles {\n\t\tleft: 330px;\n\t}\n\t#savingscircles {\n\t\tleft: 355px;\n\t\twidth: 345px;\n\t\theight: 345px;\n\t}\n\t#after-savings-value {\n\t\ttop: 2%;\n\t}\n\t#before-savings-value {\n\t\ttop: 0;\n\t}\n}\n@media screen and (max-width: 1200px) {\n\t#mainblock {\n\t\twidth: 1060px;\n\t}\n\t#incomes-lines-container, #expenses-lines-container {\n\t\tleft: 350px; width: 260px;\n\t\tpadding-top: 72px;\n\t}\n\t#savingschart {\n\t\twidth: 396px;\n\t\theight: 222px;\n\t}\n\t.months {\n\t\twidth: 33px;\n\t}\n\t#savingscircles {\n\t\tleft: 345px;\n\t\twidth: 300px;\n\t\theight: 300px;\n\t}\n\t#after-savings-value {\n\t\ttop:-5%;\n\t}\n\t#before-savings-value {\n\t\ttop:-7%;\n\t}\n\t#firstcirclediv, #firstpendant {\n\t\topacity: 0;\n\t}\n\t#small-circles-container {\n\t\theight: 260px;\n\t}\n\t#before-savings-value {\n\t\tfont-size: 24px;\n\t}\n}\n\n/*================ @2x IMAGES FOR RETINA DISPLAYS ================   */\n\n@media only screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 144dpi) {\n\t#logo_greeting, #logo_settings {\n\t\tbackground-image: url(\"../images/logo_large@2x.gif\");\n\t}\n\t#logo_statistic {\n\t\tbackground: url(\"../images/logotext_large@2x.gif\");\n\t\tbackground-size: 172px 65px;\n\t}\n\t.imgbox:hover {\n\t\tborder: 2px solid white;\n\t}\n}\n\n"
  },
  {
    "path": "gateway/src/main/resources/static/index.html",
    "content": "<!DOCTYPE html>\n<htmL>\n<head>\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n    <meta name=\"viewport\" content=\"width=1150, user-scalable=no\">\n    <title>Piggy Metrics</title>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/launch.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/style.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/animation.css\">\n    <style>\n        #bubble, #indicator, #save, #piggyicon, #rublesign, .arrowup, .arrowdown, .incomes-sprite-title, .close-sign, .modal-save-icon,\n        #modaldeletecross, .initicons-arrow, .expenses-sprite-title, .savings-sprite-title, .triangle, .noUi-handle, .noUi-handle:after  {\n            background-image: url(\"images/sprites.png\");\n            background-size: 538px 84px;\n        }\n        @media only screen and (-webkit-min-device-pixel-ratio: 1.5),\n        only screen and (min-resolution: 144dpi) {\n            #bubble, #indicator, #save, #piggyicon, #rublesign, .arrowup, .arrowdown, .incomes-sprite-title, .close-sign, .modal-save-icon,\n            #modaldeletecross, .initicons-arrow, .expenses-sprite-title, .savings-sprite-title, .triangle, .noUi-handle, .noUi-handle:after {\n                background-image: url(\"images/sprites@2x.png\");\n            }\n        }\n    </style>\n</head>\n    <body>\n\n        <div id=\"settings_hat\"></div>\n\n        <div id=\"lastlogo\">\n            <div id=\"lastlogoflipper\">\n                <div id=\"logo_settings\">\n                    <div id=\"logoclickplace\"></div><div id=\"bubble\"><div id=\"indicator\"></div></div>\n                </div>\n                <div id=\"logo_statistic\"></div>\n            </div>\n        </div>\n\n        <section class=\"toppage\">\n\n            <!-- GREETING PAGE -->\n\n            <div id=\"logo_greeting\"></div>\n            <div id=\"centerbox\">\n                <div id=\"avatarcontainer\" class=\"forward plus\">\n                    <div class=\"avatar\"></div>\n                </div>\n            </div>\n            <div id=\"lefttitle\"><span class=\"bluetext\"> metrics</span></div>\n            <div id=\"righttitle\"><span class=\"bluetext\">last seen: </span></div>\n            <div id=\"bottombuttons\">\n                <div id=\"plus\" class=\"plus\">\n                    <div id=\"plusborder\">\n                        <div id=\"plusone\"></div>\n                        <div id=\"plustwo\"></div>\n                    </div>\n                    <div id=\"plustext\">Get in</div>\n                </div>\n                <form id=\"logout\" autocomplete=\"off\">\n                    <div id=\"minus\">\n                        <div id=\"minusborder\">\n                            <div id=\"minusone\"></div>\n                        </div>\n                        <div id=\"minustext\">Log out</div>\n                    </div>\n                </form>\n            </div>\n\n            <!-- SETTINGS PAGE -->\n\n            <div id=\"settingspage\">\n                <div id=\"swipefield\"></div>\n\n                <div id=\"incomes\" class=\"columns\"><div class=\"incomes-sprite-title zoomplus incomebutton\"></div>\n                    <div id=\"noincomes\" class=\"incomebutton\">\n                        <div class=\"hoverplace\"></div>\n                        <div class=\"majorplusitem\">\n                            <div class=\"plusitemborder\"></div>\n                            <div class=\"plusitemone\"></div>\n                            <div class=\"plusitemtwo\"></div>\n                        </div>\n                        <span class=\"plusitemtitle\">Add income</span>\n                        <span class=\"plusitemitalic\">Empty space</span>\n                    </div>\n                    <div class=\"plusitem\" id=\"incomesplusitem\">\n                        <div class=\"plusitemborder incomebutton\"></div>\n                        <div class=\"plusitemone\"></div>\n                        <div class=\"plusitemtwo\"></div>\n                    </div>\n                    <div class=\"blueline\"></div>\n                    <div class=\"brownline\"></div>\n                    <div id=\"incomedown\"><div class=\"arrowup\"></div>forth</div>\n                    <div id=\"incomeup\"><div class=\"arrowdown\"></div>back</div>\n                    <div class=\"frame\">\n                        <div id=\"incomewrapper\" class=\"wrapper\">\n                            <div id=\"incomeslider\"></div>\n                        </div>\n                    </div>\n                </div>\n                <div id=\"expenses\" class=\"columns\"><div class=\"expenses-sprite-title zoomplus expensebutton\"></div>\n                    <div id=\"noexpenses\" class=\"expensebutton\">\n                        <div class=\"hoverplace\"></div>\n                        <div class=\"majorplusitem\">\n                            <div class=\"plusitemborder expensebutton\"></div>\n                            <div class=\"plusitemone\"></div>\n                            <div class=\"plusitemtwo\"></div>\n                        </div>\n                        <span class=\"plusitemtitle\">Add expense</span>\n                        <span class=\"plusitemitalic\">Empty space</span>\n                    </div>\n                    <div class=\"plusitem\" id=\"expensesplusitem\">\n                        <div class=\"plusitemborder\"></div>\n                        <div class=\"plusitemone\"></div>\n                        <div class=\"plusitemtwo\"></div>\n                    </div>\n                    <div class=\"blueline\"></div>\n                    <div class=\"brownline\"></div>\n                    <div id=\"expensedown\"><div class=\"arrowup\"></div>forth</div>\n                    <div id=\"expenseup\"><div class=\"arrowdown\"></div>back</div>\n                    <div class=\"frame\">\n                        <div id=\"expensewrapper\" class=\"wrapper\">\n                            <div id=\"expenseslider\"></div>\n                        </div>\n                    </div>\n                </div>\n                <div id=\"savings\" class=\"columns\"><div class=\"savings-sprite-title\"></div>\n                    <div class=\"blueline\"></div>\n                    <div class=\"brownline\"></div>\n                    <div id=\"topsavingsline\">\n                        <div id=\"piggyicon\"></div>\n                        <span class=\"savingstitle14\">My spare money</span>\n                        <span class=\"savingstitle12\">At the moment</span>\n                        <div id=\"rublebox\"></div><div id=\"rublesign\"></div>\n                        <input type=\"text\" name=\"savingsvalue\" id=\"savingsvalue\" data-a-sep=\" \" data-v-min=\"0\" data-v-max=\"999999999\" data-l-zero=\"deny\" maxlength=\"12\" autocomplete=\"off\">\n                    </div>\n                    <div id=\"bottomsavingsline\">\n                        <input type=\"checkbox\" name=\"deposit\" id=\"deposit\">\n                        <label for=\"deposit\" class=\"button\"></label>\n                        <label for=\"deposit\" class=\"savingsdeposit\">Accumulation account</label>\n                        <span class=\"savingspercent\">Interest</span>\n                        <input type=\"text\" name=\"percentvalue\" id=\"percentvalue\" data-a-sep=\"\"  data-v-min=\"0.00\" data-v-max=\"99.99\" data-l-zero=\"deny\" data-a-sign=\" %\" data-w-empty=\"sign\" data-p-sign=\"s\" maxlength=\"5\" data-a-pad=\"false\" size=\"6\" data-a-dec=\".\" autocomplete=\"off\">\n                        <input type=\"checkbox\" name=\"capitalization\" id=\"capitalization\">\n                        <label for=\"capitalization\" class=\"button\"></label>\n                        <label for=\"capitalization\" class=\"savingscapital\">Monthly capitalization</label>\n                    </div>\n                </div>\n                <div id=\"savebutton\"><div id=\"leftborder\"></div><div id=\"centerborder\"></div><div id=\"rightborder\"></div><div id=\"save\"></div></div>\n            </div>\n\n            <form action=\"user/save\" id=\"saveoptions\" method=\"post\" autocomplete=\"off\"></form>\n\n            <!-- NOTES WINDOW -->\n            <div id=\"overlay\"></div>\n            <div id=\"add-notes\">\n                <div class=\"modal-content\">\n                    <div class=\"notes-title\">Financial notes</div>\n                    <div class=\"modal-close\"><div class=\"close-sign\"></div></div>\n                    <div class=\"notes-save\"><div class=\"modal-save-icon\"></div><div class=\"modal-save-text\">Save changes</div></div>\n                    <textarea id=\"notes\" class=\"notes-input\" autocomplete=\"off\" maxlength=\"5000\"></textarea>\n                </div>\n            </div>\n            <!-- MODAL WINDOWS -->\n\n            <div id=\"add-modal\">\n                <div class=\"modal-content\">\n                    <div class=\"modal-title mainmodaltitle\"></div>\n                    <div class=\"modal-close\"><div class=\"close-sign\"></div></div>\n                    <input type=\"text\" name=\"modalvalue\" class=\"modalvalue\" data-a-sep=\" \" data-v-min=\"0\" data-v-max=\"999999999\" data-l-zero=\"deny\" maxlength=\"12\" autocomplete=\"off\" value=\"0\">\n                    <div class=\"modalselects\">\n                        <select class=\"modalcurrency\">\n                            <option value=\"RUB\">rubles</option>\n                            <option selected=\"selected\" value=\"USD\">dollars</option>\n                            <option value=\"EUR\">euro</option>\n                        </select>\n\n                        <select class=\"modalperiod\">\n                            <option value=\"YEAR\">Per year</option>\n                            <option value=\"QUARTER\">Per quarter</option>\n                            <option selected=\"selected\" value=\"MONTH\">Per month</option>\n                            <option value=\"DAY\">Per day</option>\n                            <option value=\"HOUR\">Per hour</option>\n                        </select>\n                    </div>\n                    <div class=\"initicons\"><div id=\"chooseicon\" class=\"cart-icon\"></div><div class=\"initicons-arrow\"></div></div>\n                    <input class=\"modaltitle\" placeholder=\"Title\" type=\"text\" autocomplete=\"off\" maxlength=\"18\"/>\n                    <div class=\"modal-save\"><div class=\"modal-save-icon\"></div><div class=\"modal-save-text\">Save changes</div></div>\n                    <div class=\"modal-delete\"><div id=\"modaldeletecross\"></div><div class=\"modal-delete-text\">Remove</div></div>\n\n                    <!-- Expenses table -->\n                    <div class=\"modalexpensessurface\">\n                        <div class=\"modal-title\">Choose an icon</div>\n                        <table class=\"modaltable modalforward\" id=\"modalexpensetable\">\n                            <tr>\n                                <td><div class=\"imgbox\"><div class=\"iconbox auto\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox gas\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox home\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox baby\"></div></div></td>\n                            </tr>\n                            <tr>\n                                <td><div class=\"imgbox\"><div class=\"iconbox cart\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox clothes\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox phone\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox utilities\"></div></div></td>\n                            </tr>\n                            <tr>\n                                <td><div class=\"imgbox\"><div class=\"iconbox island\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox earth\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox meal\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox sport\"></div></div></td>\n                            </tr>\n                            <tr>\n                                <td><div class=\"imgbox\"><div class=\"iconbox medical\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox tv\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox smoking\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox other\"></div></div></td>\n                            </tr>\n                        </table>\n                    </div>\n\n                    <!-- Incomes table -->\n                    <div class=\"modalincomessurface\">\n                        <div class=\"modal-title\">Choose an icon</div>\n                        <table class=\"modaltable modalforward\" id=\"modalincomestable\">\n                            <tr>\n                                <td><div class=\"imgbox\"><div class=\"iconbox edu\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox graphs\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox wallet\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox case\"></div></div></td>\n                            </tr>\n                            <tr>\n                                <td><div class=\"imgbox\"><div class=\"iconbox rub\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox euro\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox doll\"></div></div></td>\n                                <td><div class=\"imgbox\"><div class=\"iconbox gbp\"></div></div></td>\n                            </tr>\n                        </table>\n                    </div>\n\n                </div>\n            </div>\n\n        </section>\n\n        <!-- LAST PAGE -->\n        <section class=\"bottompage\">\n            <div id=\"mainblock\">\n                <div id=\"setcurrency\">\n                    <div id=\"usdcurr\" class=\"currunchecked\">Usd</div>\n                    <div id=\"eurcurr\" class=\"currunchecked\">Eur</div>\n                    <div id=\"rubcurr\" class=\"currunchecked\">Rub</div>\n                </div>\n                <div id=\"expense-cursor\">\n                    <div class=\"cursorline\"></div>\n                    <div class=\"cursorpoint\"></div>\n                </div>\n                <div id=\"incomes-cursor\">\n                    <div class=\"cursorline\"></div>\n                    <div class=\"cursorpoint\"></div>\n                </div>\n                <div id=\"expenses-lines-container\"></div>\n                <div id=\"incomes-lines-container\"></div>\n\n                <div id=\"deltatitle\" class=\"flag\">Incomes &mdash; expenses<div class=\"triangle\"></div></div>\n                <div id=\"savingstitle\" class=\"flag\">Savings<div class=\"triangle\"></div></div>\n\n                <div id=\"outermaindiv\"> <div class=\"linesbackground\"></div>\n                    <div id=\"outermaincursordiv\"> <input class=\"knob\" id=\"outer-circle-cursor\"/> </div>\n                    <input class=\"knob\" id=\"outer-circle\"/>\n                    <div class=\"topmaincircletitle lightcircletitle\"></div>\n                    <div id=\"outer-circle-value\" class=\"boldcircletitle\"></div></span>\n                    <div class=\"bottommaincircletitle\"><span class=\"boldcircletitle curr\"></span><span class=\"lightcircletitle\">/ Month</span></div>\n                    <div id=\"innermaindiv\"> <input class=\"knob\" id=\"inner-circle\"/> </div>\n                    <div id=\"maincircleline\"></div>\n                    <div id=\"maincircle100percent\"><span class=\"lightcircletitle\">100%</span></div>\n                    <div id=\"outer-circle-percent\"><span class=\"lightcircletitle\"></span></div>\n                </div>\n\n                <div id=\"small-circles-container\">\n                    <div id=\"firstcirclediv\">\n                        <div id=\"firstcircledivflipper\">\n                            <div class=\"frontcircle\"> <div class=\"linesbackground\"></div>\n                                <input class=\"knob\" id=\"first-circle\" />\n                                <div class=\"cursordiv\"> <input class=\"knob\" id=\"first-circle-cursor\"/> </div>\n                                <div id=\"first-circle-title\" class=\"circletoptitle lightcircletitle\"></div>\n                                <div id=\"first-circle-value\" class=\"circlevalue boldcircletitle\"></div></span>\n                                <div class=\"bottomcircletitle\"><span class=\"boldcircletitle curr\"></span><span class=\"lightcircletitle\">/ Hour</span></div>\n                                <div id=\"first-circle-percent\"><span class=\"lightcircletitle\"></span></div>\n                                <div class=\"circleline\"></div>\n                                <div class=\"circle100percent\"><span class=\"lightcircletitle\">100%</span></div>\n                            </div>\n                            <div class=\"backcircle\">\n                                <canvas id=\"movingcircle-1\" width=\"200\" height=\"200\"></canvas>\n                                <div class=\"circletoptitle lightcircletitle\">Choose any<br>item</div>\n                                <div class=\"circlesselect\">\n                                    <select id=\"circle-select-1\" class=\"selectcircles\"></select>\n                                </div>\n                                <div id=\"circle-select-1-back\"></div>\n                            </div>\n                        </div>\n                    </div>\n\n                    <div id=\"secondcirclediv\">\n                        <div id=\"secondcircledivflipper\">\n                            <div class=\"frontcircle\"> <div class=\"linesbackground\"></div>\n                                <input class=\"knob\" id=\"second-circle\" />\n                                <div class=\"cursordiv\"> <input class=\"knob\" id=\"second-circle-cursor\"/> </div>\n                                <div id=\"second-circle-title\" class=\"circletoptitle lightcircletitle\"></div>\n                                <div id=\"second-circle-value\" class=\"circlevalue boldcircletitle\"></div></span>\n                                <div class=\"bottomcircletitle\"><span class=\"boldcircletitle curr\"></span><span class=\"lightcircletitle\">/ Day</span></div>\n                                <div id=\"second-circle-percent\"><span class=\"lightcircletitle\"></span></div>\n                                <div class=\"circleline\"></div>\n                                <div class=\"circle100percent\"><span class=\"lightcircletitle\">100%</span></div>\n                            </div>\n                            <div class=\"backcircle\">\n                                <canvas id=\"movingcircle-2\" width=\"200\" height=\"200\"></canvas>\n                                <div class=\"circletoptitle lightcircletitle\">Choose any<br>item</div>\n                                <div class=\"circlesselect\">\n                                    <select id=\"circle-select-2\" class=\"selectcircles\"></select>\n                                </div>\n                                <div id=\"circle-select-2-back\"></div>\n                            </div>\n                        </div>\n                    </div>\n\n                    <div id=\"thirdcirclediv\">\n                        <div id=\"thirdcircledivflipper\">\n                            <div class=\"frontcircle\"> <div class=\"linesbackground\"></div>\n                                <div class=\"cursordiv\"> <input class=\"knob\" id=\"third-circle-cursor\"/> </div>\n                                <div id=\"third-circle-title\" class=\"circletoptitle lightcircletitle\"></div>\n                                <div id=\"third-circle-value\" class=\"circlevalue boldcircletitle\"></div></span>\n                                <div class=\"bottomcircletitle\"><span class=\"boldcircletitle curr\"></span><span class=\"lightcircletitle\">/ Year</span></div>\n                                <div id=\"third-circle-percent\"><span class=\"lightcircletitle\"></span></div>\n                                <div class=\"circleline\"></div>\n                                <div class=\"circle100percent\"><span class=\"lightcircletitle\">100%</span></div>\n                                <input class=\"knob\" id=\"third-circle\" />\n                            </div>\n                            <div class=\"backcircle\">\n                                <canvas id=\"movingcircle-3\" width=\"200\" height=\"200\"></canvas>\n                                <div class=\"circletoptitle lightcircletitle\">Choose any<br>item</div>\n                                <div class=\"circlesselect\">\n                                    <select id=\"circle-select-3\" class=\"selectcircles\"></select>\n                                </div>\n                                <div id=\"circle-select-3-back\"></div>\n                            </div>\n                        </div>\n                    </div>\n                    <div id=\"firstpendant\">\n                        <div class=\"pendant-circle\"></div>\n                        <span class=\"lightcircletitle pendantfont\">Per hour</span>\n                        <div class=\"pendantline\"></div>\n                    </div>\n                    <div id=\"secondpendant\">\n                        <div class=\"pendant-circle\"></div>\n                        <span class=\"lightcircletitle pendantfont\">Per day</span>\n                        <div class=\"pendantline\"></div>\n                    </div>\n                    <div id=\"thirdpendant\">\n                        <div class=\"pendant-circle\"></div>\n                        <span class=\"lightcircletitle pendantfont\">Per year</span>\n                        <div class=\"pendantline\"></div>\n                    </div>\n                </div>\n\n                <div id=\"savings-slider-container\">\n                    <span class=\"savings-slider\">part of the spare money which you'll send to accumulation account every month:</span>\n                    <div id=\"savings-slider\"></div>\n                    <div id=\"savingsTip0\">Without monthly deposit refill</div>\n                    <div id=\"savingsTip100\">All savings go to deposit every month</div>\n                </div>\n\n                <div id=\"savingscircles\">\n                    <div id=\"after-savings\">\n                        <div class=\"savings-circle-title lightcircletitle\">In a year</div>\n                        <div id=\"after-savings-value\" class=\"boldcircletitle\"></div></span>\n                        <div class=\"savings-circle-currency boldcircletitle\"></div>\n                    </div>\n                    <div id=\"before-savings\">\n                        <div class=\"savings-circle-title lightcircletitle\">At the moment</div>\n                        <div id=\"before-savings-value\" class=\"boldcircletitle\"></div></span>\n                        <div class=\"savings-circle-currency boldcircletitle\"></div>\n                    </div>\n                </div>\n\n                <div id=\"savingschart\">\n                    <canvas id=\"horizontal\"></canvas>\n                    <canvas id=\"chartline\"></canvas>\n                    <div id=\"month0\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"></div></div>\n                    <div class=\"months\"><div id=\"month1\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month2\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month3\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month4\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month5\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></span></div></span></div></div>\n                    <div class=\"months\"><div id=\"month6\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"large-month-circle\"><div class=\"bluedot\"></div></div><div class=\"month-name\"></div><div class=\"large-month-val\"><span class=\"val\"></span> <span class=\"curr\"></div><div class=\"month-val\"></div></div></div>\n                    <div class=\"months\"><div id=\"month7\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month8\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month9\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month10\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month11\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"month-name\"></div><div class=\"month-val\"><span class=\"val\"></span> <span class=\"curr\"></div></span></div></div>\n                    <div class=\"months\"><div id=\"month12\" class=\"in-month\"><div class=\"small-month-circle\"></div><div class=\"large-month-circle\"><div class=\"bluedot\"></div></div><div class=\"month-name\"></div><div class=\"large-month-val\"><span class=\"val\"></span> <span class=\"curr\"></div><div class=\"month-val\"></div></div></div>\n                </div>\n            </div>\n        </section>\n\n        <section id=\"loginpage\">\n            <div id=\"flipper\">\n                <div class=\"FRONTCARD\">\n                    <div id=\"piggy\"></div>\n                    <div id=\"logotext\"></div>\n                    <div class=\"flipinfo\" id=\"info\"></div>\n                    <div id=\"secondenter\"></div>\n                    <div id=\"preloader\"></div>\n                    <div id=\"wrapper\">\n                        <!-- LOG IN FORMS -->\n                        <div id=\"cube\">\n                            <form action=\"user/login\" id=\"auth\" method=\"post\" autocomplete=\"off\">\n                                <div class=\"side\" id=\"side1\">\n                                    <div id=\"backlogin\"></div>\n                                    <div id=\"enter\"></div>\n                                    <input class=\"frontforms\" id=\"frontloginform\" name=\"username\" placeholder=\"enter your login\" type=\"text\" autocomplete=\"off\"/>\n                                </div>\n                                <div class=\"side\" id=\"side2\">\n                                    <div id=\"backpassword\"></div>\n                                    <input class=\"frontforms\" id=\"frontpasswordform\" name=\"password\" placeholder=\"password\" type=\"password\" autocomplete=\"off\"/>\n                                </div>\n                            </form>\n                        </div>\n                    </div>\n                    <div class=\"fliptext\">Or <a href=\"#\">create new account</a></div>\n                </div>\n\n                <div class=\"BACKCARD\">\n                    <div id=\"infopage\">\n                        <span class=\"frominfo\" id=\"infosubtitle\">Piggy Metrics is the new simple way to deal with personal finances</span>\n                        <span class=\"frominfo\" id=\"infotitle\">This is how it works</span>\n                        <div class=\"infoflipback frominfo\"></div>\n                        <div id=\"infoleft\" class=\"infocolumn frominfo\">\n                            <div id=\"leftcolumn\">enter planned periodic<br>incomes and expenses<br>\n                                <div class=\"columnimage\"></div>\n                                <span class=\"columnfooter\">Plan or estimate your regular expenses. Divide them into categories. Сomponents of your budget<br>will always be near to hand</span>\n                            </div>\n                        </div>\n                        <div id=\"infocenter\"class=\" infocolumn frominfo\">\n                            <div id=\"centercolumn\">check all your savings<br>at the moment<br>\n                                <div class=\"columnimage\"></div>\n                                <span class=\"columnfooter\">Regularly update the value of your savings.<br>Follow the progress.</span>\n                            </div>\n                        </div>\n                        <div id=\"inforight\"class=\"infocolumn frominfo\">\n                            <div id=\"rightcolumn\">analyze financial statistics<br>and forecasts<br>\n                                <div class=\"columnimage\"></div>\n                                <span class=\"columnfooter\">Based on this information, you’ll get statistics for your budget and forecast of savings<br>for the year ahead</span>\n                            </div>\n                        </div>\n                        <div id=\"infoline\"></div>\n                        <div id=\"infolinetext\" class=\"frominfo\">Try without registration</div>\n                        <button class=\"demobutton\">Demo account</button>\n                        <a id=\"infofooter\" href=\"https://github.com/sqshq/PiggyMetrics\">&copy; 2016 sqshq.com</a>\n                        <a id=\"iconsfooter\" href=\"attribution.html\">icons attribution</a>\n                    </div>\n                    <div id=\"regpage\">\n                        <div id=\"franklin\">\n                            <div class=\"avatar\"></div>\n                        </div>\n                        <div id=\"createaccount\"></div>\n                        <div class=\"fliptext\">Or <a href=\"#\">use existing account</a></div>\n                        <div id=\"registrationforms\">\n                            <form id=\"signup\" autocomplete=\"off\">\n                                <input class=\"backforms\" name=\"reg_user_name\" id=\"backloginform\" placeholder=\" enter a login\" type=\"text\"/><br>\n                                <input class=\"backforms\" name=\"reg_user_password\" id=\"backpasswordform\" placeholder=\" password\" type=\"password\"/>\n                                <button class=\"regbutton\" type=\"submit\" name=\"registration\">r e g i s t e r</button>\n                            </form>\n                        </div>\n                        <div id=\"mailform\">\n                            <form id=\"mail\" autocomplete=\"off\">\n                                <input name=\"usermail\" id=\"backmailform\" placeholder=\"e-mail address\" type=\"text\"/><br>\n                                <button class=\"mailbutton\" type=\"submit\" name=\"mailbutton\">Subscribe to notifications</button>\n                            </form>\n                            <span class=\"mailforminfo\">Thank you for registration.<br>\n                                <span class=\"mailforminfosmall\">\n                                    We suggest you to enter an e-mail address so we can occasionally remind you about the service. Continuous tracking your budget statistics might be especially effective.\n                                </span>\n                            </span>\n                            <div id=\"skipmail\"><a href=\"#\">Skip this step</a></div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </section>\n\n        <script src=\"js/lib/jquery.min.js\"></script>\n        <script src=\"js/lib/touchscreens.js\"></script>\n        <script src=\"js/lib/extrascripts.js\"></script>\n\n        <script src=\"js/dashboard.js\" charset=\"utf-8\"></script>\n        <script src=\"js/main.js\" charset=\"utf-8\"></script>\n        <script src=\"js/login.js\" charset=\"utf-8\"></script>\n        <script src=\"js/launch.js\" charset=\"utf-8\"></script>\n\n    </body>\n</html>"
  },
  {
    "path": "gateway/src/main/resources/static/js/dashboard.js",
    "content": "/**\n * KNOB circles initialization\n */\n\n$(\"#inner-circle\").knob({\n\t\"readOnly\": true,\n\t\"width\": 223,\n\t\"height\": 223,\n\t\"thickness\": 0.1,\n\t\"displayInput\": false,\n\t\"fgColor\": \"#e6eff1\"\n});\n\n$(\"#outer-circle\").data({ \"width\": 265 }).knob({\n\t\"readOnly\": true,\n\t\"width\": 265,\n\t\"height\": 265,\n\t\"thickness\": 0.13,\n\t\"displayInput\": false,\n\t\"fgColor\": \"#a59b9e\"\n});\n\n$(\"#outer-circle-cursor\").knob({\n\t\"cursor\": 0.5,\n\t\"readOnly\": true,\n\t\"width\": 303,\n\t\"height\": 303,\n\t\"thickness\": 0.24,\n\t\"displayInput\": false,\n\t\"fgColor\":\"#898989\"\n});\n\n$(\"#first-circle\").data({ \"width\": 165 }).knob({\n\t\"readOnly\": true,\n\t\"width\": 165,\n\t\"height\": 165,\n\t\"thickness\": 0.14,\n\t\"displayInput\": false,\n\t\"fgColor\": \"#efefef\"\n});\n\n$(\"#second-circle\").data({ \"width\": 165 }).knob({\n\t\"readOnly\": true,\n\t\"width\": 165,\n\t\"height\": 165,\n\t\"thickness\": 0.14,\n\t\"displayInput\": false,\n\t\"fgColor\": \"#e0ded5\"\n});\n\n$(\"#third-circle\").data({ \"width\": 165 }).knob({\n\t\"readOnly\": true,\n\t\"width\": 165,\n\t\"height\": 165,\n\t\"thickness\": 0.14,\n\t\"displayInput\": false,\n\t\"fgColor\": \"#b6aeb0\"\n});\n\n$(\"#first-circle-cursor, #second-circle-cursor, #third-circle-cursor\").knob({\n\t\"cursor\": 0.5,\n\t\"readOnly\": true,\n\t\"width\": 189,\n\t\"height\": 189,\n\t\"thickness\": 0.25,\n\t\"displayInput\": false,\n\t\"fgColor\":\"#898989\"\n});\n\nfunction getConverted(column) {\n\tvar firstitem, seconditem;\n\tfor (var key in column) {\n\t\tswitch (column[key].currency) {\n\t\t\tcase \"RUB\": column[key].converted = column[key].amount;\n\t\t\t\tbreak;\n\t\t\tcase \"EUR\": column[key].converted = (column[key].amount * global.eur).toFixed(3);\n\t\t\t\tbreak;\n\t\t\tcase \"USD\": column[key].converted = (column[key].amount * global.usd).toFixed(3);\n\t\t\t\tbreak;\n\t\t}\n\t\tswitch (column[key].period) {\n\t\t\tcase \"MONTH\": break;\n\t\t\tcase \"HOUR\": column[key].converted = (column[key].converted * 730).toFixed(3);\n\t\t\t\tbreak;\n\t\t\tcase \"DAY\": column[key].converted = (column[key].converted * 30.41666667).toFixed(3);\n\t\t\t\tbreak;\n\t\t\tcase \"QUARTER\": column[key].converted = (column[key].converted / 3).toFixed(3);\n\t\t\t\tbreak;\n\t\t\tcase \"YEAR\": column[key].converted = (column[key].converted / 12).toFixed(3);\n\t\t\t\tbreak;\n\t\t}\n\t\tswitch (user.checkedCurr) {\n\t\t\tcase \"RUB\": break;\n\t\t\tcase \"EUR\": column[key].converted = (column[key].converted / global.eur).toFixed(3);\n\t\t\t\tbreak;\n\t\t\tcase \"USD\": column[key].converted = (column[key].converted / global.usd).toFixed(3);\n\t\t\t\tbreak;\n\t\t}\n\t\tif (column == incomes) {\n\t\t\tif (firstitem == undefined) {firstitem = key; $(\"#incomeslider\").data(\"firstitem\", key); }\n\t\t\tincomesSumMonth += Math.round(column[key].converted);\n\t\t\t$(\"#circle-select-1, #circle-select-2, #circle-select-3\").append('<option value=\"' + parseInt(column[key].income_id, 10) + '\">' + escape(column[key].title) + '</option>');\n\t\t}\n\t\telse {\n\t\t\tif (firstitem == undefined) {firstitem = key; $(\"#expenseslider\").data(\"firstitem\", key); }\n\t\t\telse if (seconditem == undefined) {seconditem = key; $(\"#expenseslider\").data(\"seconditem\", key); }\n\t\t\texpensesSumMonth += Math.round(column[key].converted);\n\t\t\t$(\"#circle-select-1, #circle-select-2, #circle-select-3\").append('<option value=\"' + (parseInt(column[key].expense_id, 10) + 100) + '\">' + escape(column[key].title) + '</option>');\n\t\t}\n\t}\n\n\tif ( Math.abs(incomesSumMonth-expensesSumMonth) < 10 ) {\n\t\texpensesSumMonth = incomesSumMonth;\n\t}\n\n\t$('select').trigger('refresh');\n}\n\nfunction initStatisticPage() {\n\n\tchangeCurrency = function () {\n\t\tswitch (user.checkedCurr) {\n\t\t\tcase \"RUB\":\n\t\t\t\tif (user.lastCurr == \"RUB\") { break; }\n\t\t\t\telse if (user.lastCurr == \"USD\") { savings.freeMoney = (savings.freeMoney * global.usd).toFixed(3); }\n\t\t\t\telse if (user.lastCurr == \"EUR\") { savings.freeMoney = (savings.freeMoney * global.eur).toFixed(3); }\n\t\t\t\tbreak;\n\t\t\tcase \"EUR\":\n\t\t\t\tif (user.lastCurr == \"EUR\") { break; }\n\t\t\t\telse if (user.lastCurr == \"USD\") { savings.freeMoney = (savings.freeMoney * global.usd / global.eur).toFixed(3); }\n\t\t\t\telse if (user.lastCurr == \"RUB\") { savings.freeMoney = (savings.freeMoney / global.eur).toFixed(3); }\n\t\t\t\tbreak;\n\t\t\tcase \"USD\":\n\t\t\t\tif (user.lastCurr == \"USD\") { break; }\n\t\t\t\telse if (user.lastCurr == \"EUR\") { savings.freeMoney = (savings.freeMoney * global.eur / global.usd).toFixed(3); }\n\t\t\t\telse if (user.lastCurr == \"RUB\") { savings.freeMoney = (savings.freeMoney / global.usd).toFixed(3); }\n\t\t\t\tbreak;\n\t\t}\n\t\tuser.lastCurr = user.checkedCurr;\n\t};\n\n\tchangeCurrency();\n\n\tinitCircle = function (whichcircle, item, beforevalue) {\n\t\tvar sum, value, circle;\n\t\t$(\"#\" + whichcircle + \"circlediv\").show();\n\t\tif ( item.hasOwnProperty(\"income_id\") ) {\n\t\t\tsum = incomesSumMonth;\n\t\t\tvalue = item.income_id;\n\t\t}\n\t\telse {\n\t\t\tsum = expensesSumMonth;\n\t\t\tvalue = parseInt(item.expense_id, 10) + 100;\n\t\t}\n\t\tswitch (whichcircle) {\n\t\t\tcase \"first\": animatecircle.call($(\"#first-circle\"), beforevalue, (item.converted / 730).toFixed(2), (sum / 730), item.title );\n\t\t\t\tcircle = 1;\n\t\t\t\t$(\"#first-circle\").data({\"item\": item, \"sum\": sum});\n\t\t\t\tbreak;\n\t\t\tcase \"second\": animatecircle.call($(\"#second-circle\"), beforevalue, (item.converted / 30.41666667).toFixed(1), (sum / 30.41666667), item.title );\n\t\t\t\tcircle = 2;\n\t\t\t\t$(\"#second-circle\").data({\"item\": item, \"sum\": sum});\n\t\t\t\tbreak;\n\t\t\tcase \"third\": animatecircle.call($(\"#third-circle\"), beforevalue, Math.round(item.converted * 1.2) * 10, (sum * 1.2) * 10, item.title );\n\t\t\t\tcircle = 3;\n\t\t\t\t$(\"#third-circle\").data({\"item\": item, \"sum\": sum});\n\t\t\t\tbreak;\n\t\t}\n\t\t$(\"#circle-select-\" + circle).find('option').removeAttr(\"selected\");\n\t\tsetTimeout( function() { $(\"#circle-select-\" + circle +\" option[value=\" + value + \"]\").attr('selected','selected'); $('select').trigger('refresh'); } , 100)\n\t\t$(\"#circle-select-\" + circle + \"-back\").removeClass().addClass(item.icon);\n\t}\n\n\tif (incomes[ $(\"#incomeslider\").data(\"firstitem\") ] != undefined && expenses[ $(\"#expenseslider\").data(\"firstitem\") ] != undefined) {\n\t\tinitCircle(\"first\", incomes[ $(\"#incomeslider\").data(\"firstitem\") ], 0);\n\t\tinitCircle(\"second\", expenses[ $(\"#expenseslider\").data(\"firstitem\") ], 0);\n\t\tif ( $(\"#expenseslider\").data(\"seconditem\") !== undefined ) { initCircle(\"third\", expenses[ $(\"#expenseslider\").data(\"seconditem\") ], 0); }\n\t\telse initCircle(\"third\", expenses[ $(\"#expenseslider\").data(\"firstitem\") ], 0);\n\t}\n\n\t$(\"#expenses-lines-container\").html('<div class=\"lines-title\"> Expenses structure <span class=\"grey\">(' + separateNumber(expensesSumMonth) + '<span class=\"greysmall\"><span class=\"greysmallval curr\"></span>/Month</span>)<span></span></div>')\n\t$(\"#incomes-lines-container\").html('<div class=\"lines-title\"> Incomes structure <span class=\"grey\">(' + separateNumber(incomesSumMonth) + '<span class=\"greysmall\"><span class=\"greysmallval curr\"></span>/Month</span>)<span></span></div>')\n\n\tvar incomesIdSorted = Object.keys(incomes).sort(function(a,b){return incomes[b].converted - incomes[a].converted});\n\tvar expensesIdSorted = Object.keys(expenses).sort(function(a,b){return expenses[b].converted - expenses[a].converted});\n\n\tinitlines = function(column) {\n\t\tvar container, idSorted, maxConvertedId, i;\n\t\tif (column == incomes) {container = \"#incomes-lines-container\"; idSorted = incomesIdSorted; maxConvertedId = incomesIdSorted[0]; sum = incomesSumMonth; i=0}\n\t\telse {container = \"#expenses-lines-container\"; idSorted = expensesIdSorted; maxConvertedId = expensesIdSorted[0]; sum = expensesSumMonth; i=100}\n\n\t\tidSorted.forEach(function(id) {\n\t\t\t$(container).append('<div id=\"line-' + i + '\" class=\"itemline\"><span class=\"lineitemtitle lightcircletitle\">' + column[id].title + '</span><div class=\"lineitempercent\">'+ Math.round(100 * column[id].converted / sum) +'%</div><div class=\"lineitemvalue\">' + separateNumber(Math.round(column[id].converted)) + ' <span class=\"boldcircletitle curr lineitemcurr\"></span><span class=\"lightcircletitle lineitemcurr\">/Month</span></div><div class=\"leftpoint\"></div><div id=\"linebackground-'+ i +'\" class=\"itemlinebackground\"></div></div>');\n\t\t\t$(\"#line-\" + i).data({\"item\": column[id]}).css({\"width\": Math.round(100 * column[ id ].converted / column[maxConvertedId].converted ) + \"%\"});\n\t\t\t$(\"#linebackground-\" + i).addClass(column[id].icon);\n\t\t\ti++;\n\t\t});\n\t\t$(\"#line-0, #line-100\").addClass(\"activeline\");\n\t}\n\n\tinitlines(incomes);\n\tinitlines(expenses);\n\n\t// FUTURE SAVINGS CALCULATION\n\tinitSavingsCircles = function (slidervalue, lastslidervalue, beforevalue, beforemiddlevalue, animatetime) {\n\n\t\tvar monthSavings = [],\n\t\t\t\tdepositMonthSavings = [],\n\t\t\t\tdelta = incomesSumMonth - expensesSumMonth,\n\t\t\t\tdeltaDeposit = delta * slidervalue,\n\t\t\t\tdeltaNoDeposit =  delta * (1 - slidervalue),\n\t\t\t\tpercent = savings.percent,\n\t\t\t\tdeltapercents = 0;\n\t\tmonthSavings[0] = parseInt(savings.freeMoney, 10);\n\n\t\t// Set deposit tips next to slider\n\t\tif (slidervalue === 0) {\n\t\t\t$(\"#savingsTip100\").hide();\n\t\t\t$(\"#savingsTip0\").fadeIn(200);\n\t\t}\n\t\telse if (slidervalue === 1) {\n\t\t\t$(\"#savingsTip0\").hide();\n\t\t\t$(\"#savingsTip100\").fadeIn(200);\n\t\t}\n\t\telse {\n\t\t\t$(\"#savingsTip0, #savingsTip100\").fadeOut(200);\n\t\t}\n\n\t\tif (delta >= 0) {\t// if incomes > expenses\n\t\t\tif (savings.deposit) {\t// deposit turned on\n\t\t\t\tif (savings.capitalization) { // capitalization turned on (percent adds every month)\n\t\t\t\t\tif (delta === 0) { // delta = 0\n\t\t\t\t\t\t$('#savings-slider').css({\"opacity\": \"0.5\"}).attr('disabled', 'disabled');\n\t\t\t\t\t\t$(\"#savingsTip100, #savingsTip0\").hide();\n\t\t\t\t\t\tfor (var k=1; k < 13; k++) {\n\t\t\t\t\t\t\tmonthSavings[k] = ( parseInt(monthSavings[ (k-1) ], 10) + delta );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (monthSavings[0] === 0) { // Savings = 0\n\t\t\t\t\t\t\t// Draw horizontal chart\n\t\t\t\t\t\t\t$({ value: 91 }).animate({ value: 155 }, {\n\t\t\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\t\t\tdrawChartLine(this.value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse { // Savings > 0\n\t\t\t\t\t\t\t// Draw increasing chart\n\t\t\t\t\t\t\t$({ value: 91 }).animate({ value: 70 }, {\n\t\t\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\t\t\tdrawChartLine(this.value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse { // delta > 0\n\t\t\t\t\t\t$('#savings-slider').css({\"opacity\": \"1\"}).removeAttr('disabled');\n\t\t\t\t\t\t// Animate chart\n\t\t\t\t\t\t$({ value: lastslidervalue}).animate({ value: slidervalue }, {\n\t\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\t\tdrawChartLine(110 - (this.value) * 95);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tdepositMonthSavings[0] = monthSavings[0];\n\t\t\t\t\tfor (var i=1; i < 13; i++) {\n\t\t\t\t\t\tdepositMonthSavings[i] = ( deltaDeposit + parseInt(depositMonthSavings[ (i-1) ], 10) + ( parseInt(depositMonthSavings[ (i-1) ], 10) * percent * 30.41666667 / 36500 ) ) // including delta in the last MONTH!\n\t\t\t\t\t\tmonthSavings[i] = (depositMonthSavings[i] + (deltaNoDeposit * i));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\t // capitalization turned off (percent adds in the last MONTH)\n\t\t\t\t\tif (delta === 0) {\n\t\t\t\t\t\t$('#savings-slider').css({\"opacity\": \"0.5\"}).attr('disabled', 'disabled');\n\t\t\t\t\t\t$(\"#savingsTip100, #savingsTip0\").hide();\n\t\t\t\t\t\tif (monthSavings[0] === 0) {\n\t\t\t\t\t\t\t// Draw horizontal chart\n\t\t\t\t\t\t\t$({ value: 91 }).animate({ value: 155 }, {\n\t\t\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\t\t\tdrawChartLine(this.value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t// Draw increasing chart\n\t\t\t\t\t\t\t$({ value: 91 }).animate({ value: 80 }, {\n\t\t\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\t\t\tdrawChartLine(this.value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t$('#savings-slider').css({\"opacity\": \"1\"}).removeAttr('disabled');\n\t\t\t\t\t\t// Animate chart\n\t\t\t\t\t\t$({ value: lastslidervalue}).animate({ value: slidervalue }, {\n\t\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\t\tdrawChartLine(110 - (this.value) * 95);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tfor (var j=1; j < 12; j++) {\n\t\t\t\t\t\tmonthSavings[j] = ( parseInt(monthSavings[ (j-1) ], 10) + deltaDeposit );\n\t\t\t\t\t\tdeltapercents = deltapercents + ( deltaDeposit * percent * j / 1200 );\n\t\t\t\t\t}\n\t\t\t\t\tmonthSavings[12] = (monthSavings[0] * (1 + (savings.percent / 100)) ) + (deltaDeposit * 12) + (deltaNoDeposit * 12) + deltapercents; // including delta in the last month!\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\t\t// deposit turned off\n\t\t\t\t$('#savings-slider').css({\"opacity\": \"0.5\"}).attr('disabled', 'disabled');\n\t\t\t\t$(\"#savingsTip100, #savingsTip0\").hide();\n\t\t\t\t$('.noUi-handle:after').css({\"opacity\": \"0\"})\n\t\t\t\tfor (var k=1; k < 13; k++) {\n\t\t\t\t\tmonthSavings[k] = ( parseInt(monthSavings[ (k-1) ], 10) + delta );\n\t\t\t\t}\n\t\t\t\tif (delta === 0) {\n\t\t\t\t\t// Draw horizontal chart\n\t\t\t\t\t$({ value: 91 }).animate({ value: 155 }, {\n\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\tdrawChartLine(this.value);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Draw increasing chart\n\t\t\t\t\t$({ value: 91 }).animate({ value: 120 }, {\n\t\t\t\t\t\tduration: animatetime,\n\t\t\t\t\t\teasing: 'swing',\n\t\t\t\t\t\tstep: function () {\n\t\t\t\t\t\t\tdrawChartLine(this.value);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\t// if incomes < expenses\n\t\t\t$('#savings-slider').css({\"opacity\": \"0.5\"}).attr('disabled', 'disabled');\n\t\t\t$(\"#savingsTip100, #savingsTip0\").hide();\n\t\t\t$('.noUi-handle:after').css({\"opacity\": \"0\"});\n\t\t\tfor (var n=1; n < 13; n++) {\n\t\t\t\tmonthSavings[n] = ( parseInt(monthSavings[ (n-1) ], 10) + delta );\n\t\t\t}\n\t\t\t// Draw decreasing chart\n\t\t\t$({ value: 91 }).animate({ value: 175 }, {\n\t\t\t\tduration: animatetime,\n\t\t\t\teasing: 'swing',\n\t\t\t\tstep: function () {\n\t\t\t\t\tdrawChartLine(this.value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tvar aftervalue = monthSavings[12];\n\n\t\t$(\"#before-savings-value\").html( separateNumber( Math.round(savings.freeMoney) ) );\n\t\t$(\"#after-savings-value\").data({\"lastvalue\": aftervalue, \"lastmiddlevalue\": monthSavings[6]}).html( separateNumber(Math.round((savings.freeMoney * 10.222) / 100) * 100) );\n\t\t$(\"#savings-slider\").data(\"lastpercent\", $(\"#savings-slider\").data(\"checkedPercent\"));\n\n\t\t// Set font-size in big circle\n\t\t(function() {\n\t\t\tif (Math.abs (aftervalue) < 9999999 ) {\n\t\t\t\t$(\"#after-savings-value\").css({\"font-size\": \"48px\"})\n\t\t\t}\n\t\t\telse if (Math.abs (aftervalue) < 99999999) {\n\t\t\t\t$(\"#after-savings-value\").css({\"font-size\": \"42px\"})\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$(\"#after-savings-value\").css({\"font-size\": \"37px\"})\n\t\t\t}\n\t\t\tif (Math.abs (beforevalue) > 9999999 ) {\n\t\t\t\t$(\"#before-savings-value\").css({\"font-size\": \"26px\"})\n\t\t\t}\n\t\t})();\n\n\t\t// Animate last value on chart\n\t\t$({ value: beforevalue }).animate({ value: aftervalue }, {\n\t\t\tduration: animatetime,\n\t\t\teasing: 'linear',\n\t\t\tstep: function () {\n\t\t\t\t$(\"#after-savings-value\").html(separateNumber( Math.round(this.value / 10) * 10));\n\t\t\t\t$(\"#month12\").children(\".large-month-val\").children(\".val\").html(separateNumber(Math.round(this.value / 10) * 10));\n\t\t\t}\n\t\t});\n\t\t// Animate middle value on chart\n\t\t$({ value: beforemiddlevalue }).animate({ value: monthSavings[6] }, {\n\t\t\tduration: animatetime,\n\t\t\teasing: 'linear',\n\t\t\tstep: function () {\n\t\t\t\t$(\"#month6\").children(\".large-month-val\").children(\".val\").html(separateNumber( Math.round(this.value)));\n\t\t\t}\n\t\t});\n\t\t// Set precision value\n\t\tsetTimeout(function() {\n\t\t\t$(\"#after-savings-value\").html(separateNumber( Math.round(aftervalue / 10) * 10));\n\t\t\t$(\"#month12\").children(\".large-month-val\").children(\".val\").html(separateNumber(Math.round(aftervalue / 10) * 10));\n\t\t}, animatetime+20);\n\n\t\t// Set every month values on chart\n\t\tfor (i = 1; i < 12; i++) {\n\t\t\t$(\"#month\" + i).children(\".month-val\").children(\".val\").html(separateNumber(Math.round(monthSavings[i])));\n\t\t}\n\t\t$(\"#month6, #month12\").children(\".month-val\").html(\"\");\n\t}\n\n\t// Launch Savings circles\n\tif (typeof $(\"#savings-slider\").data('checkedPercent') == 'undefined')\n\t{\n\t\t$(\"#savings-slider\").data({\"checkedPercent\": Number(user.checkedPercent) });\n\t}\n\n\t// Change currency stuff\n\t$(\"#rubcurr, #eurcurr, #usdcurr\").removeClass(\"currchecked\");\n\tswitch (user.checkedCurr) {\n\t\tcase \"RUB\": $(\".curr\").html(\" Rub \").data(\"curr\", \" rub.\"); $(\".savings-circle-currency\").html(\" Rubles \"); $(\"#rubcurr\").addClass(\"currchecked\");\n\t\t\tbreak;\n\t\tcase \"EUR\": $(\".curr, .savings-circle-currency\").html(\" Eur \").data(\"curr\", \" \\u20ac\"); $(\"#eurcurr\").addClass(\"currchecked\");\n\t\t\tbreak;\n\t\tcase \"USD\": $(\".curr, .savings-circle-currency\").html(\" USD \").data(\"curr\", \" $\"); $(\"#usdcurr\").addClass(\"currchecked\");\n\t\t\tbreak;\n\t}\n\n\tif (incomesSumMonth > expensesSumMonth) {\n\t\tsetTimeout(function() {\n\t\t\t$(\".topmaincircletitle\").html(\"Spare\");\n\t\t\tsimpleanimatecircle.call($(\"#inner-circle\"), 0, 100, 1200);\n\t\t\t$(\"#outer-circle-value\").css({\"color\": \"black\"});\n\t\t\t$(\"#incomes-cursor\").hide();\n\t\t\t$(\"#expense-cursor\").show();\n\t\t\tanimatecircle.call($(\"#outer-circle\"), 0, Math.round(expensesSumMonth), Math.round(incomesSumMonth));}, 350);\n\t}\n\telse {\n\t\tsetTimeout(function() {\n\t\t\t$(\".topmaincircletitle\").html(\"Loss\");\n\t\t\tsimpleanimatecircle.call($(\"#inner-circle\"), 0, 100, 800);\n\t\t\t$(\"#outer-circle-value\").css({\"color\": \"#eaa7a7\"});\n\t\t\t$(\"#expense-cursor\").hide();\n\t\t\t$(\"#incomes-cursor\").show();\n\t\t\tanimatecircle.call($(\"#outer-circle\"), 0, Math.round(incomesSumMonth), Math.round(expensesSumMonth)); }, 350);\n\t}\n}\n\nfunction separateNumber(val) {\n\tif (Math.abs (val) > 999) {\n\t\tval = val.toString().replace(/(\\d)(?=(\\d\\d\\d)+(?!\\d))/g, '$1 <span class=\"lightcircletitle\">');\n\t}\n\telse {\n\t\tval = val.toString().replace(/(\\.)/g, '<span class=\"lightcircletitle\">$1');\n\t}\n\treturn val;\n};\n\n// ANIMATE CIRCLE PROCESS\nfunction animatecircle(beforevalue, aftervalue, sum, title) {\n\tvar before = (100 * beforevalue/sum),\n\t\t\tafter = (100 * aftervalue/sum),\n\t\t\tR = $(this).data(\"width\") / 2,\n\t\t\talpha = (after * 0.02 * Math.PI),\t// percent to radians\n\t\t\tn = 10,\t\t\t\t\t\t\t\t// margin from circle\n\t\t\tid = $(this).attr(\"id\"),\n\t\t\t$this = $(this),\n\t\t\tycord, xcord;\n\n\t// Set font size\n\t(function() {\n\t\tvar fontpx;\n\t\tif (id == \"outer-circle\") {\n\t\t\tif (sum-aftervalue < 999999) { fontpx = 48 }\n\t\t\telse if (sum-aftervalue < 9999999) {fontpx = 37 }\n\t\t\telse { fontpx = 22 }\n\t\t}\n\t\telse {\n\t\t\tif (aftervalue < 99999) {fontpx = 31}\n\t\t\telse if (aftervalue < 999999) {\tfontpx = 27 }\n\t\t\telse { fontpx = 18 }\n\t\t}\n\t\t$(\"#\" + id + \"-value\").css({\"font-size\": fontpx + \"px\"})\n\t})();\n\n\t// Percent value location\n\tif (after <= 25 ) {\n\t\tycord = Math.round( R + (n / 2) - (R * Math.sin(1.57 - alpha)) );\n\t\txcord = Math.round( R + (n) + (R * Math.cos(1.57 - alpha)) );\n\t\tanimatetime = 500;\n\t}\n\telse if (after <= 50 ) {\n\t\tycord = Math.round( R + (n / 2) + (R * Math.sin(alpha - 1.57)) );\n\t\txcord = Math.round( R + (n * 2) + (R * Math.cos(alpha - 1.57)) );\n\t\tanimatetime = 1400;\n\t}\n\telse if (after <= 75 ) {\n\t\tycord = Math.round( R - (n * 2) + (R * Math.sin(alpha - 1.57)) );\n\t\txcord = Math.round( R - (n * 4) + (R * Math.cos(alpha - 1.57)) );\n\t\tanimatetime = 1500;\n\t}\n\telse {\n\t\tycord = Math.round( R + (- n * 3) - (R * Math.sin(1.57 - alpha)) );\n\t\txcord = Math.round( R + (- n * 3) + (R * Math.cos(1.57 - alpha)) );\n\t\tanimatetime = 1600;\n\t}\n\n\t// Set and show percent value next to cursor\n\t$(\"#\" + id + \"-percent\").hide();\n\t$(\"#\" + id + \"-percent\").empty().append('<span class=\"lightcircletitle\">' + Math.round(after) + '%</span>');\n\tsetTimeout(function () { $(\"#\" + id + \"-percent\").css({\"left\": xcord, \"top\": ycord}).fadeIn(400)}, animatetime-200);\n\n\t// Animate circle with its cursor\n\t$({ value: before }).animate({ value: after }, {\n\t\tduration: animatetime,\n\t\teasing: 'swing',\n\t\tstep: function () {\n\t\t\t$this.val(this.value).trigger('change');\n\t\t\t$(\"#\" + id + \"-cursor\").val(this.value).trigger('change');\n\t\t}\n\t});\n\n\t// Set precision value\n\t(function() {\n\t\tif (id == \"outer-circle\") {\n\t\t\t$(\"#\" + id + \"-value\").html(separateNumber( Math.abs(Math.round((sum-aftervalue) / 10) * 10) ) );\n\t\t}\n\t\telse {\n\t\t\t$(\"#\" + id + \"-value\").html( separateNumber(aftervalue) )\n\t\t\t$(\"#\" + id + \"-title\").html(title);\n\t\t}\n\t})();\n}\n\n// Animate without percent moving and value growing (for inner circle)\nfunction simpleanimatecircle(before, after, duration) {\n\tvar $this = $(this);\n\t$({ value: before }).animate({ value: after }, {\n\t\tduration: duration,\n\t\teasing: 'swing',\n\t\tstep: function () {\n\t\t\t$this.val(this.value).trigger('change');\n\t\t}\n\t});\n}\n\n/**\n * EVENT HANDLERS\n */\n\n// Currency buttons\n$(\".currunchecked\").click(function() {\n\tif (this.id == \"eurcurr\") {\n\t\tuser.checkedCurr = \"EUR\";\n\t}\n\telse if (this.id == \"usdcurr\") {\n\t\tuser.checkedCurr = \"USD\";\n\t}\n\telse {\n\t\tuser.checkedCurr = \"RUB\";\n\t}\n\trunConvert();\n\n\tinitStatisticPage();\n\tsetTimeout(function() { initSavingsCircles($(\"#savings-slider\").data(\"checkedPercent\"), 0.2, savings.freeMoney, savings.freeMoney, 700) }, 100);\n\tinitCircle(\"first\", $(\"#first-circle\").data(\"item\"), 0);\n\tinitCircle(\"second\", $(\"#second-circle\").data(\"item\"), 0);\n\tinitCircle(\"third\", $(\"#third-circle\").data(\"item\"), 0);\n\n\t$('#savings-slider').noUiSlider({\n\t\tstart: (incomesSumMonth-expensesSumMonth) * $(\"#savings-slider\").data(\"checkedPercent\"),\n\t\tstep: (incomesSumMonth-expensesSumMonth) / 20,\n\t\trange: {\n\t\t\t'min': [ 0 ],\n\t\t\t'max': [ Math.abs(incomesSumMonth-expensesSumMonth) ]\n\t\t}\n\t}, true);\n});\n\n$(\"#outermaindiv, #incomes-cursor, #expense-cursor, .lines-title\").click(function() {\n\tsetTimeout(function() { $(\"#expenses-lines-container, #incomes-lines-container\").toggle(); }, 250);\n\t$(\"#expense-cursor, #incomes-cursor\").toggle(300);\n\t$(\".itemline\").removeClass(\"activeline\");\n\t$(\"#line-0, #line-100\").addClass(\"activeline\");\n});\n\n$(\"#expenses-lines-container, #incomes-lines-container\").on(\"hover\", \".itemline\", function() {\n\t$(\".itemline\").removeClass(\"activeline\");\n\t$(this).addClass(\"activeline\");\n});\n\n$(\"#expenses-lines-container, #incomes-lines-container\").on(\"click\", \".itemline\", function() {\n\t$(\".itemline\").removeClass(\"activeline\");\n\t$(this).addClass(\"activeline\");\n\tvar item = $(this).data(\"item\");\n\tinitCircle(\"first\", item, 0);\n\tinitCircle(\"second\", item, 0);\n\tinitCircle(\"third\", item, 0);\n});\n\n$(\"#firstcirclediv, #secondcirclediv, #thirdcirclediv\").click(function() {\n\t$(\"#\" + this.id + \"flipper\").toggleClass(\"flippedcard\");\n});\n\n$(\"#circle-select-1, #circle-select-2, #circle-select-3\").on(\"change\", function() {\n\tvar column, item,\n\t\t\tcircle = this.id,\n\t\t\twhichCircle;\n\tswitch (circle) {\n\t\tcase \"circle-select-1\": whichCircle = \"first\";\n\t\t\tbreak;\n\t\tcase \"circle-select-2\": whichCircle = \"second\";\n\t\t\tbreak;\n\t\tdefault: whichCircle = \"third\";\n\t\t\tbreak;\n\t}\n\tif ($(this).val() < 100 ) {\n\t\tcolumn = incomes;\n\t\titem = $(this).val();\n\t\tsetTimeout(function() { initCircle( whichCircle, column[item], 0) }, 300);\n\t}\n\telse {\n\t\tcolumn = expenses;\n\t\titem = $(this).val() - 100;\n\t\tsetTimeout(function() { initCircle( whichCircle, column[item], 0) }, 300);\n\t}\n\t$(\"#\" + circle + \"-back\").removeClass().addClass( column[item].icon );\n});\n\nfunction initSavingsSlider() {\n\tvar Link = $.noUiSlider.Link,\n\t\t\tsub;\n\tsimpleSeparateNumber = function(val) {\n\t\tif (Math.abs (val) > 999) {\n\t\t\tval = val.toString().replace(/(\\d)(?=(\\d\\d\\d)+(?!\\d))/g, '$1 ');\n\t\t}\n\t\telse {\n\t\t\tval = val.toString().replace(/(\\.)/g, '$1');\n\t\t}\n\t\treturn val;\n\t};\n\t$('#savings-slider').noUiSlider({\n\t\tstart: (incomesSumMonth-expensesSumMonth) * user.checkedPercent,\n\t\tstep: (incomesSumMonth-expensesSumMonth) / 20,\n\t\trange: {\n\t\t\t'min': [ 0 ],\n\t\t\t'max': [ incomesSumMonth-expensesSumMonth ]\n\t\t},\n\t\tserialization: {\n\t\t\tlower: [\n\t\t\t\tnew Link({\ttarget: function(value){ $(\"#savings-slider\").data({\"checkedPercent\": Math.round(value / (incomesSumMonth-expensesSumMonth + 0.001) * 100) / 100}); $(\".noUi-handle\").attr({ percent: Math.round(value / (incomesSumMonth-expensesSumMonth + 0.001) * 100) + \"%\"  }) }, format: { decimals: 0 } }),\n\t\t\t\tnew Link({\ttarget: function(value){ $(\".noUi-handle\").attr({ value: simpleSeparateNumber(Math.round(value / 10 ) * 10) + $(\".curr\").data(\"curr\")}) }, format: { decimals: 0} })\n\t\t\t]\n\t\t}\n\t});\n}\n\n$('#savings-slider').on('slide', function() {\n\tvar lastVal = $(\"#after-savings-value\").data(\"lastvalue\"),\n\t\t\tanimatetime;\n\tif (lastVal < 99999) animatetime = 200;\n\telse animatetime = 700;\n\tinitSavingsCircles($(\"#savings-slider\").data(\"checkedPercent\"), $(\"#savings-slider\").data(\"lastpercent\"), lastVal, $(\"#after-savings-value\").data(\"lastmiddlevalue\"), animatetime);\n});\n\n(function(){\n\tvar canvas1 = document.getElementById(\"movingcircle-1\"),\n\t\t\tcanvas2 = document.getElementById(\"movingcircle-2\"),\n\t\t\tcanvas3 = document.getElementById(\"movingcircle-3\"),\n\t\t\tctx1 = canvas1.getContext('2d'),\n\t\t\tctx2 = canvas2.getContext('2d'),\n\t\t\tctx3 = canvas3.getContext('2d'),\n\t\t\tx = canvas1.width / 2,\n\t\t\ty = canvas1.height / 2,\n\t\t\tradius = 99,\n\t\t\tstartAngle = 1 * Math.PI,\n\t\t\tendAngle = 2.2 * Math.PI,\n\t\t\tcounterClockwise = false;\n\n\tctx1.beginPath();\n\tctx1.arc(x, y, radius, startAngle, endAngle, counterClockwise);\n\tctx1.lineWidth = 3;\n\tctx1.strokeStyle = \"#f2f2f2\";\n\tctx1.stroke();\n\n\tctx2.beginPath();\n\tctx2.arc(x, y, radius, startAngle, endAngle, counterClockwise);\n\tctx2.lineWidth = 3;\n\tctx2.strokeStyle = \"#f2f2f2\";\n\tctx2.stroke();\n\n\tctx3.beginPath();\n\tctx3.arc(x, y, radius, startAngle, endAngle, counterClockwise);\n\tctx3.lineWidth = 3;\n\tctx3.strokeStyle = \"#f2f2f2\";\n\tctx3.stroke();\n})();\n\n(function(){\n\tvar canvas = document.getElementById(\"horizontal\"),\n\t\t\tcurrentDate = new Date(),\n\t\t\tcurrentMonth = currentDate.getMonth(),\n\t\t\tallMonths = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n\tif (canvas.getContext) {\n\t\tvar ctx = canvas.getContext(\"2d\");\n\t\tctx.canvas.width  = $(\"#savingschart\").width();\n\t\tctx.canvas.height = $(\"#savingschart\").height();\n\t\tfor (i=0; i < 11; i++){\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(0, 1 + i * 22);\n\t\t\tctx.lineTo($(\"#savingschart\").width(), 1 + i * 22);\n\t\t\tctx.strokeStyle = '#e5e5e5';\n\t\t\tctx.stroke();\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(0, 0.5 + i * 22);\n\t\t\tctx.lineTo($(\"#savingschart\").width(), 0.5 + i * 22);\n\t\t\tctx.strokeStyle = '#FFF';\n\t\t\tctx.stroke();\n\t\t}\n\t}\n\tfor (i = 0, j = currentMonth; i <=12; i++, j++) {\n\t\tif (j == 12) {j = 0}\n\t\t$(\"#month\" + i).children(\".month-name\").html(allMonths[j]);\n\t}\n})();\n\nfunction drawChartLine(position) {\n\tvar canvas = document.getElementById(\"chartline\");\n\tvar chartWidth = $(\"#savingschart\").width(),\n\t\t\tchartHeight = $(\"#savingschart\").height(),\n\t\t\tsegmentWidth = $(\".months\").width();\n\tif (canvas.getContext){\n\t\tvar ctx = canvas.getContext(\"2d\");\n\t\tctx.canvas.width  = $(\"#savingschart\").width();\n\t\tctx.canvas.height = $(\"#savingschart\").height();\n\t\tctx.beginPath();\n\t\tctx.moveTo(0, 155);\n\t\tctx.lineTo($(\"#savingschart\").width(), position);\n\t\tctx.strokeStyle = '#bed8db';\n\t\tctx.stroke();\n\t}\n\tfor (i = 0, j = 12; i < 12; i++, j--) {\n\t\t$(\"#month\" + j).css({\"height\": chartHeight - position - i * 7 -((i * segmentWidth * (chartHeight - 153 - position) ) / chartWidth) });\n\t}\n}\n"
  },
  {
    "path": "gateway/src/main/resources/static/js/launch.js",
    "content": "var global = {\n    mobileClient: false,\n    savePermit: true,\n    usd: 0,\n    eur: 0\n};\n\n/**\n * Oauth2\n */\n\nfunction requestOauthToken(username, password) {\n\n\tvar success = false;\n\n\t$.ajax({\n\t\turl: 'uaa/oauth/token',\n\t\tdatatype: 'json',\n\t\ttype: 'post',\n\t\theaders: {'Authorization': 'Basic YnJvd3Nlcjo='},\n\t\tasync: false,\n\t\tdata: {\n\t\t\tscope: 'ui',\n\t\t\tusername: username,\n\t\t\tpassword: password,\n\t\t\tgrant_type: 'password'\n\t\t},\n\t\tsuccess: function (data) {\n\t\t\tlocalStorage.setItem('token', data.access_token);\n\t\t\tsuccess = true;\n\t\t},\n\t\terror: function () {\n\t\t\tremoveOauthTokenFromStorage();\n\t\t}\n\t});\n\n\treturn success;\n}\n\nfunction getOauthTokenFromStorage() {\n\treturn localStorage.getItem('token');\n}\n\nfunction removeOauthTokenFromStorage() {\n    return localStorage.removeItem('token');\n}\n\n/**\n * Current account\n */\n\nfunction getCurrentAccount() {\n\n\tvar token = getOauthTokenFromStorage();\n\tvar account = null;\n\n\tif (token) {\n\t\t$.ajax({\n\t\t\turl: 'accounts/current',\n\t\t\tdatatype: 'json',\n\t\t\ttype: 'get',\n\t\t\theaders: {'Authorization': 'Bearer ' + token},\n\t\t\tasync: false,\n\t\t\tsuccess: function (data) {\n\t\t\t\taccount = data;\n\t\t\t},\n\t\t\terror: function () {\n\t\t\t\tremoveOauthTokenFromStorage();\n\t\t\t}\n\t\t});\n\t}\n\n\treturn account;\n}\n\n$(window).load(function(){\n\n\tif(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {\n\t\tFastClick.attach(document.body);\n        global.mobileClient = true;\n\t}\n\n    $.getJSON(\"https://api.exchangeratesapi.io/latest?base=RUB&symbols=EUR,USD\", function( data ) {\n        global.eur = 1 / data.rates.EUR;\n        global.usd = 1 / data.rates.USD;\n    });\n\n\tvar account = getCurrentAccount();\n\n\tif (account) {\n\t\tshowGreetingPage(account);\n\t} else {\n\t\tshowLoginForm();\n\t}\n});\n\nfunction showGreetingPage(account) {\n    initAccount(account);\n\tvar userAvatar = $(\"<img />\").attr(\"src\",\"images/userpic.jpg\");\n\t$(userAvatar).load(function() {\n\t\tsetTimeout(initGreetingPage, 500);\n\t});\n}\n\nfunction showLoginForm() {\n\t$(\"#loginpage\").show();\n\t$(\"#frontloginform\").focus();\n\tsetTimeout(initialShaking, 700);\n}"
  },
  {
    "path": "gateway/src/main/resources/static/js/lib/extrascripts.js",
    "content": "/*\n* autoNumeric.js\n* @author: Bob Knothe\n* @author: Sokolov Yura\n*\n* Created by Robert J. Knothe on 2010-10-25. Please report any bugs to https://github.com/BobKnothe/autoNumeric\n* Created by Sokolov Yura on 2010-11-07\n*\n* Copyright (c) 2011 Robert J. Knothe http://www.decorplanit.com/plugin/\n*\n* The MIT License (http://www.opensource.org/licenses/mit-license.php)\n* Permission is hereby granted, free of charge, to any person\n* obtaining a copy of this software and associated documentation\n* files (the \"Software\"), to deal in the Software without\n* restriction, including without limitation the rights to use,\n* copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following\n* conditions:\n* The above copyright notice and this permission notice shall be\n* included in all copies or substantial portions of the Software.\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n* OTHER DEALINGS IN THE SOFTWARE.\n*/\n(function(f){function m(b,a,c){void 0===b.selectionStart?(b.focus(),b=b.createTextRange(),b.collapse(!0),b.moveEnd(\"character\",c),b.moveStart(\"character\",a),b.select()):(b.selectionStart=a,b.selectionEnd=c)}function B(b,a){f.each(a,function(c,d){\"function\"===typeof d?a[c]=d(b,a,c):\"function\"===typeof b.autoNumeric[d]&&(a[c]=b.autoNumeric[d](b,a,c))})}function p(b,a){\"string\"===typeof b[a]&&(b[a]*=1)}function w(b,a){B(b,a);a.oEvent=null;a.tagList=\"B CAPTION CITE CODE DD DEL DIV DFN DT EM H1 H2 H3 H4 H5 H6 INS KDB LABEL LI OUTPUT P Q S SAMPLE SPAN STRONG TD TH U VAR\".split(\" \");\nvar c=a.vMax.toString().split(\".\"),d=a.vMin||0===a.vMin?a.vMin.toString().split(\".\"):[];p(a,\"vMax\");p(a,\"vMin\");p(a,\"mDec\");a.allowLeading=!0;a.aNeg=0>a.vMin?\"-\":\"\";c[0]=c[0].replace(\"-\",\"\");d[0]=d[0].replace(\"-\",\"\");a.mInt=Math.max(c[0].length,d[0].length,1);if(null===a.mDec){var e=0,g=0;c[1]&&(e=c[1].length);d[1]&&(g=d[1].length);a.mDec=Math.max(e,g)}null===a.altDec&&0<a.mDec&&(\".\"===a.aDec&&\",\"!==a.aSep?a.altDec=\",\":\",\"===a.aDec&&\".\"!==a.aSep&&(a.altDec=\".\"));c=a.aNeg?\"([-\\\\\"+a.aNeg+\"]?)\":\"(-?)\";\na.aNegRegAutoStrip=c;a.skipFirstAutoStrip=RegExp(c+\"[^-\"+(a.aNeg?\"\\\\\"+a.aNeg:\"\")+\"\\\\\"+a.aDec+\"\\\\d].*?(\\\\d|\\\\\"+a.aDec+\"\\\\d)\");a.skipLastAutoStrip=RegExp(\"(\\\\d\\\\\"+a.aDec+\"?)[^\\\\\"+a.aDec+\"\\\\d]\\\\D*$\");a.allowedAutoStrip=RegExp(\"[^\"+(\"-\"+a.aNum+\"\\\\\"+a.aDec)+\"]\",\"gi\");a.numRegAutoStrip=RegExp(c+\"(?:\\\\\"+a.aDec+\"?(\\\\d+\\\\\"+a.aDec+\"\\\\d+)|(\\\\d*(?:\\\\\"+a.aDec+\"\\\\d*)?))\");return a}function h(b,a,c){if(a.aSign)for(;-1<b.indexOf(a.aSign);)b=b.replace(a.aSign,\"\");b=b.replace(a.skipFirstAutoStrip,\"$1$2\");b=b.replace(a.skipLastAutoStrip,\n\"$1\");b=b.replace(a.allowedAutoStrip,\"\");a.altDec&&(b=b.replace(a.altDec,a.aDec));b=(b=b.match(a.numRegAutoStrip))?[b[1],b[2],b[3]].join(\"\"):\"\";if((\"allow\"===a.lZero||\"keep\"===a.lZero)&&\"strip\"!==c){var d=[],e=\"\",d=b.split(a.aDec);-1!==d[0].indexOf(\"-\")&&(e=\"-\",d[0]=d[0].replace(\"-\",\"\"));d[0].length>a.mInt&&\"0\"===d[0].charAt(0)&&(d[0]=d[0].slice(1));b=e+d.join(a.aDec)}if(c&&\"deny\"===a.lZero||c&&\"allow\"===a.lZero&&!1===a.allowLeading)a=\"^\"+a.aNegRegAutoStrip+\"0*(\\\\d\"+(\"leading\"===c?\")\":\"|$)\"),a=RegExp(a),\nb=b.replace(a,\"$1$2\");return b}function x(b,a,c){if(a&&c){var d=b.split(a);d[1]&&d[1].length>c&&(0<c?(d[1]=d[1].substring(0,c),b=d.join(a)):b=d[0])}return b}function r(b,a,c){a&&\".\"!==a&&(b=b.replace(a,\".\"));c&&\"-\"!==c&&(b=b.replace(c,\"-\"));b.match(/\\d/)||(b+=\"0\");return b}function y(b,a){var c=b.indexOf(\".\"),d=+b;-1!==c&&(1E-6>d&&-1<d?(b=+b,1E-6>b&&0<b&&(b=(b+10).toString(),b=b.substring(1)),0>b&&-1<b&&(b=(b-10).toString(),b=\"-\"+b.substring(2)),b=b.toString()):(c=b.split(\".\"),void 0!==c[1]&&(0===\n+c[1]?b=c[0]:(c[1]=c[1].replace(/0*$/,\"\"),b=c.join(\".\")))));return\"keep\"===a.lZero?b:b.replace(/^0*(\\d)/,\"$1\")}function z(b,a,c){c&&\"-\"!==c&&(b=b.replace(\"-\",c));a&&\".\"!==a&&(b=b.replace(\".\",a));return b}function s(b,a){b=h(b,a);b=x(b,a.aDec,a.mDec);b=r(b,a.aDec,a.aNeg);var c=+b;\"set\"===a.oEvent&&(c<a.vMin||c>a.vMax)&&f.error(\"The value (\"+c+\") from the 'set' method falls outside of the vMin / vMax range\");return c>=a.vMin&&c<=a.vMax}function q(b,a,c){return\"\"===b||b===a.aNeg?\"zero\"===a.wEmpty?b+\n\"0\":\"sign\"===a.wEmpty||c?b+a.aSign:b:null}function t(b,a){b=h(b,a);var c=b.replace(\",\",\".\"),d=q(b,a,!0);if(null!==d)return d;var d=\"\",d=2===a.dGroup?/(\\d)((\\d)(\\d{2}?)+)$/:4===a.dGroup?/(\\d)((\\d{4}?)+)$/:/(\\d)((\\d{3}?)+)$/,e=b.split(a.aDec);a.altDec&&1===e.length&&(e=b.split(a.altDec));var g=e[0];if(a.aSep)for(;d.test(g);)g=g.replace(d,\"$1\"+a.aSep+\"$2\");0!==a.mDec&&1<e.length?(e[1].length>a.mDec&&(e[1]=e[1].substring(0,a.mDec)),b=g+a.aDec+e[1]):b=g;a.aSign&&(d=-1!==b.indexOf(a.aNeg),b=b.replace(a.aNeg,\n\"\"),b=\"p\"===a.pSign?a.aSign+b:b+a.aSign,d&&(b=a.aNeg+b));\"set\"===a.oEvent&&0>c&&null!==a.nBracket&&(b=negativeBracket(b,a.nBracket,a.oEvent));return b}function u(b,a){b=\"\"===b?\"0\":b.toString();p(a,\"mDec\");var c=\"\",d=0,e=\"\",g=\"boolean\"===typeof a.aPad||null===a.aPad?a.aPad?a.mDec:0:+a.aPad,n=function(a){a=a.replace(0===g?/(\\.[1-9]*)0*$/:1===g?/(\\.\\d[1-9]*)0*$/:RegExp(\"(\\\\.\\\\d{\"+g+\"}[1-9]*)0*$\"),\"$1\");0===g&&(a=a.replace(/\\.$/,\"\"));return a};\"-\"===b.charAt(0)&&(e=\"-\",b=b.replace(\"-\",\"\"));b.match(/^\\d/)||\n(b=\"0\"+b);\"-\"===e&&0===+b&&(e=\"\");if(0<+b&&\"keep\"!==a.lZero||0<b.length&&\"allow\"===a.lZero)b=b.replace(/^0*(\\d)/,\"$1\");var d=b.lastIndexOf(\".\"),f=b.length-1-(-1===d?b.length-1:d);if(f<=a.mDec){c=b;if(f<g)for(-1===d&&(c+=\".\");f<g;)n=\"000000\".substring(0,g-f),c+=n,f+=n.length;else f>g?c=n(c):0===f&&0===g&&(c=c.replace(/\\.$/,\"\"));return e+c}var c=d+a.mDec,d=+b.charAt(c+1),f=b.substring(0,c+1).split(\"\"),h=\".\"===b.charAt(c)?b.charAt(c-1)%2:b.charAt(c)%2;if(4<d&&\"S\"===a.mRound||4<d&&\"A\"===a.mRound&&\"\"===\ne||5<d&&\"A\"===a.mRound&&\"-\"===e||5<d&&\"s\"===a.mRound||5<d&&\"a\"===a.mRound&&\"\"===e||4<d&&\"a\"===a.mRound&&\"-\"===e||5<d&&\"B\"===a.mRound||5===d&&\"B\"===a.mRound&&1===h||0<d&&\"C\"===a.mRound&&\"\"===e||0<d&&\"F\"===a.mRound&&\"-\"===e||0<d&&\"U\"===a.mRound)for(d=f.length-1;0<=d;d-=1)if(\".\"!==f[d]){f[d]=+f[d]+1;if(10>f[d])break;0<d&&(f[d]=\"0\")}f=f.slice(0,c+1);c=n(f.join(\"\"));return 0===+c?c:e+c}function A(b,a){this.settings=a;this.that=b;this.$that=f(b);this.formatted=!1;this.settingsClone=w(this.$that,this.settings);\nthis.value=b.value}function l(b){\"string\"===typeof b&&(b=b.replace(/\\[/g,\"\\\\[\").replace(/\\]/g,\"\\\\]\"),b=\"#\"+b.replace(/(:|\\.)/g,\"\\\\$1\"));return f(b)}function k(b,a,c){var d=b.data(\"autoNumeric\");d||(d={},b.data(\"autoNumeric\",d));var e=d.holder;if(void 0===e&&a||c)e=new A(b.get(0),a),d.holder=e;return e}A.prototype={init:function(b){this.value=this.that.value;this.settingsClone=w(this.$that,this.settings);this.ctrlKey=b.ctrlKey;this.cmdKey=b.metaKey;this.shiftKey=b.shiftKey;var a=this.that,c={};if(void 0===\na.selectionStart){a.focus();var d=document.selection.createRange();c.length=d.text.length;d.moveStart(\"character\",-a.value.length);c.end=d.text.length;c.start=c.end-c.length}else c.start=a.selectionStart,c.end=a.selectionEnd,c.length=c.end-c.start;this.selection=c;if(\"keydown\"===b.type||\"keyup\"===b.type)this.kdCode=b.keyCode;this.which=b.which;this.formatted=this.processed=!1},setSelection:function(b,a,c){b=Math.max(b,0);a=Math.min(a,this.that.value.length);this.selection={start:b,end:a,length:a-\nb};(void 0===c||c)&&m(this.that,b,a)},setPosition:function(b,a){this.setSelection(b,b,a)},getBeforeAfter:function(){var b=this.value,a=b.substring(0,this.selection.start),b=b.substring(this.selection.end,b.length);return[a,b]},getBeforeAfterStriped:function(){var b=this.getBeforeAfter();b[0]=h(b[0],this.settingsClone);b[1]=h(b[1],this.settingsClone);return b},normalizeParts:function(b,a){var c=this.settingsClone;a=h(a,c);var d=a.match(/^\\d/)?!0:\"leading\";b=h(b,c,d);\"\"!==b&&b!==c.aNeg||\"deny\"!==c.lZero||\n\"\"<a&&(a=a.replace(/^0*(\\d)/,\"$1\"));d=b+a;if(c.aDec){var e=d.match(RegExp(\"^\"+c.aNegRegAutoStrip+\"\\\\\"+c.aDec));e&&(b=b.replace(e[1],e[1]+\"0\"),d=b+a)}\"zero\"!==c.wEmpty||d!==c.aNeg&&\"\"!==d||(b+=\"0\");return[b,a]},setValueParts:function(b,a){var c=this.settingsClone,d=this.normalizeParts(b,a),e=d.join(\"\"),d=d[0].length;return s(e,c)?(e=x(e,c.aDec,c.mDec),d>e.length&&(d=e.length),this.value=e,this.setPosition(d,!1),!0):!1},signPosition:function(){var b=this.settingsClone,a=b.aSign,c=this.that;if(a){a=\na.length;if(\"p\"===b.pSign)return b.aNeg&&c.value&&c.value.charAt(0)===b.aNeg?[1,a+1]:[0,a];b=c.value.length;return[b-a,b]}return[1E3,-1]},expandSelectionOnSign:function(b){var a=this.signPosition(),c=this.selection;c.start<a[1]&&c.end>a[0]&&((c.start<a[0]||c.end>a[1])&&this.value.substring(Math.max(c.start,a[0]),Math.min(c.end,a[1])).match(/^\\s*$/)?c.start<a[0]?this.setSelection(c.start,a[0],b):this.setSelection(a[1],c.end,b):this.setSelection(Math.min(c.start,a[0]),Math.max(c.end,a[1]),b))},checkPaste:function(){if(void 0!==\nthis.valuePartsBeforePaste){var b=this.getBeforeAfter(),a=this.valuePartsBeforePaste;delete this.valuePartsBeforePaste;b[0]=b[0].substr(0,a[0].length)+h(b[0].substr(a[0].length),this.settingsClone);this.setValueParts(b[0],b[1])||(this.value=a.join(\"\"),this.setPosition(a[0].length,!1))}},skipAllways:function(b){var a=this.kdCode,c=this.which,d=this.ctrlKey,e=this.cmdKey,f=this.shiftKey;if((d||e)&&\"keyup\"===b.type&&void 0!==this.valuePartsBeforePaste||f&&45===a)return this.checkPaste(),!1;if(112<=a&&\n123>=a||91<=a&&93>=a||9<=a&&31>=a||8>a&&(0===c||c===a)||144===a||145===a||45===a||(d||e)&&65===a)return!0;if((d||e)&&(67===a||86===a||88===a)){\"keydown\"===b.type&&this.expandSelectionOnSign();if(86===a||45===a)\"keydown\"===b.type||\"keypress\"===b.type?void 0===this.valuePartsBeforePaste&&(this.valuePartsBeforePaste=this.getBeforeAfter()):this.checkPaste();return\"keydown\"===b.type||\"keypress\"===b.type||67===a}return d||e?!0:37===a||39===a?(c=this.settingsClone.aSep,d=this.selection.start,e=this.that.value,\n\"keydown\"===b.type&&c&&!this.shiftKey&&(37===a&&e.charAt(d-2)===c?this.setPosition(d-1):39===a&&e.charAt(d+1)===c&&this.setPosition(d+1)),!0):34<=a&&40>=a?!0:!1},processAllways:function(){var b;return 8===this.kdCode||46===this.kdCode?(this.selection.length?(this.expandSelectionOnSign(!1),b=this.getBeforeAfterStriped()):(b=this.getBeforeAfterStriped(),8===this.kdCode?b[0]=b[0].substring(0,b[0].length-1):b[1]=b[1].substring(1,b[1].length)),this.setValueParts(b[0],b[1]),!0):!1},processKeypress:function(){var b=\nthis.settingsClone,a=String.fromCharCode(this.which),c=this.getBeforeAfterStriped(),d=c[0],c=c[1];if(a===b.aDec||b.altDec&&a===b.altDec||(\".\"===a||\",\"===a)&&110===this.kdCode){if(!b.mDec||!b.aDec||b.aNeg&&-1<c.indexOf(b.aNeg)||-1<d.indexOf(b.aDec)||0<c.indexOf(b.aDec))return!0;0===c.indexOf(b.aDec)&&(c=c.substr(1));this.setValueParts(d+b.aDec,c);return!0}if(\"-\"===a||\"+\"===a){if(!b.aNeg)return!0;\"\"===d&&-1<c.indexOf(b.aNeg)&&(d=b.aNeg,c=c.substring(1,c.length));d=d.charAt(0)===b.aNeg?d.substring(1,\nd.length):\"-\"===a?b.aNeg+d:d;this.setValueParts(d,c);return!0}\"0\"<=a&&\"9\">=a&&(b.aNeg&&\"\"===d&&-1<c.indexOf(b.aNeg)&&(d=b.aNeg,c=c.substring(1,c.length)),0>=b.vMax&&b.vMin<b.vMax&&-1===this.value.indexOf(b.aNeg)&&\"0\"!==a&&(d=b.aNeg+d),this.setValueParts(d+a,c));return!0},formatQuick:function(){var b=this.settingsClone,a=this.getBeforeAfterStriped(),c=this.value;if((\"\"===b.aSep||\"\"!==b.aSep&&-1===c.indexOf(b.aSep))&&(\"\"===b.aSign||\"\"!==b.aSign&&-1===c.indexOf(b.aSign))){var d=[],e=\"\",d=c.split(b.aDec);\n-1<d[0].indexOf(\"-\")&&(e=\"-\",d[0]=d[0].replace(\"-\",\"\"),a[0]=a[0].replace(\"-\",\"\"));d[0].length>b.mInt&&\"0\"===a[0].charAt(0)&&(a[0]=a[0].slice(1));a[0]=e+a[0]}c=t(this.value,this.settingsClone);d=c.length;if(c){a=a[0].split(\"\");e=0;for(e;e<a.length;e+=1)a[e].match(\"\\\\d\")||(a[e]=\"\\\\\"+a[e]);a=RegExp(\"^.*?\"+a.join(\".*?\"));(a=c.match(a))?(d=a[0].length,(0===d&&c.charAt(0)!==b.aNeg||1===d&&c.charAt(0)===b.aNeg)&&b.aSign&&\"p\"===b.pSign&&(d=this.settingsClone.aSign.length+(\"-\"===c.charAt(0)?1:0))):b.aSign&&\n\"s\"===b.pSign&&(d-=b.aSign.length)}this.that.value=c;this.setPosition(d);this.formatted=!0}};var v={init:function(b){return this.each(function(){var a=f(this),c=a.data(\"autoNumeric\"),d=a.data();if(\"object\"!==typeof c){c=f.extend({},{aNum:\"0123456789\",aSep:\",\",dGroup:\"3\",aDec:\".\",altDec:null,aSign:\"\",pSign:\"p\",vMax:\"999999999.99\",vMin:\"0.00\",mDec:null,mRound:\"S\",aPad:!0,nBracket:null,wEmpty:\"empty\",lZero:\"allow\",aForm:!0,onSomeEvent:function(){}},d,b);if(c.aDec===c.aSep)return f.error(\"autoNumeric will not function properly when the decimal character aDec: '\"+\nc.aDec+\"' and thousand separator aSep: '\"+c.aSep+\"' are the same character\"),this;a.data(\"autoNumeric\",c)}else return this;c.lastSetValue=\"\";c.runOnce=!1;var e=k(a,c);if(-1===f.inArray(a.prop(\"tagName\"),c.tagList)&&\"INPUT\"!==a.prop(\"tagName\"))return f.error(\"The <\"+a.prop(\"tagName\")+\"> is not supported by autoNumeric()\"),this;!1===c.runOnce&&c.aForm&&(a.is(\"input[type=text], input[type=hidden], input:not([type])\")&&(d=!0,\"\"===a[0].value&&\"empty\"===c.wEmpty&&(a[0].value=\"\",d=!1),\"\"===a[0].value&&\"sign\"===\nc.wEmpty&&(a[0].value=c.aSign,d=!1),d&&a.autoNumeric(\"set\",a.val())),-1!==f.inArray(a.prop(\"tagName\"),c.tagList)&&\"\"!==a.text()&&a.autoNumeric(\"set\",a.text()));c.runOnce=!0;a.is(\"input[type=text], input[type=hidden], input:not([type])\")&&(a.on(\"keydown.autoNumeric\",function(b){e=k(a);if(e.settings.aDec===e.settings.aSep)return f.error(\"autoNumeric will not function properly when the decimal character aDec: '\"+e.settings.aDec+\"' and thousand separator aSep: '\"+e.settings.aSep+\"' are the same character\"),\nthis;if(e.that.readOnly)return e.processed=!0;e.init(b);e.settings.oEvent=\"keydown\";if(e.skipAllways(b))return e.processed=!0;if(e.processAllways())return e.processed=!0,e.formatQuick(),b.preventDefault(),!1;e.formatted=!1;return!0}),a.on(\"keypress.autoNumeric\",function(b){var c=k(a),d=c.processed;c.init(b);c.settings.oEvent=\"keypress\";if(c.skipAllways(b))return!0;if(d)return b.preventDefault(),!1;if(c.processAllways()||c.processKeypress())return c.formatQuick(),b.preventDefault(),!1;c.formatted=\n!1}),a.on(\"keyup.autoNumeric\",function(b){var c=k(a);c.init(b);c.settings.oEvent=\"keyup\";b=c.skipAllways(b);c.kdCode=0;delete c.valuePartsBeforePaste;a[0].value===c.settings.aSign&&(\"s\"===c.settings.pSign?m(this,0,0):m(this,c.settings.aSign.length,c.settings.aSign.length));if(b||\"\"===this.value)return!0;c.formatted||c.formatQuick()}),a.on(\"focusin.autoNumeric\",function(){var b=k(a);b.settingsClone.oEvent=\"focusin\";if(null!==b.settingsClone.nBracket){var c=a.val();a.val(negativeBracket(c,b.settingsClone.nBracket,\nb.settingsClone.oEvent))}b.inVal=a.val();c=q(b.inVal,b.settingsClone,!0);null!==c&&(a.val(c),\"s\"===b.settings.pSign?m(this,0,0):m(this,b.settings.aSign.length,b.settings.aSign.length))}),a.on(\"focusout.autoNumeric\",function(){var b=k(a),c=b.settingsClone,d=a.val(),e=d;b.settingsClone.oEvent=\"focusout\";var f=\"\";\"allow\"===c.lZero&&(c.allowLeading=!1,f=\"leading\");\"\"!==d&&(d=h(d,c,f),null===q(d,c)&&s(d,c,a[0])?(d=r(d,c.aDec,c.aNeg),d=u(d,c),d=z(d,c.aDec,c.aNeg)):d=\"\");f=q(d,c,!1);null===f&&(f=t(d,c));\nf!==e&&a.val(f);f!==b.inVal&&(a.change(),delete b.inVal);null!==c.nBracket&&0>a.autoNumeric(\"get\")&&(b.settingsClone.oEvent=\"focusout\",a.val(negativeBracket(a.val(),c.nBracket,c.oEvent)))}))})},destroy:function(){return f(this).each(function(){var b=f(this);b.off(\".autoNumeric\");b.removeData(\"autoNumeric\")})},update:function(b){return f(this).each(function(){var a=l(f(this)),c=a.data(\"autoNumeric\");if(\"object\"!==typeof c)return f.error(\"You must initialize autoNumeric('init', {options}) prior to calling the 'update' method\"),\nthis;var d=a.autoNumeric(\"get\"),c=f.extend(c,b);k(a,c,!0);if(c.aDec===c.aSep)return f.error(\"autoNumeric will not function properly when the decimal character aDec: '\"+c.aDec+\"' and thousand separator aSep: '\"+c.aSep+\"' are the same character\"),this;a.data(\"autoNumeric\",c);if(\"\"!==a.val()||\"\"!==a.text())return a.autoNumeric(\"set\",d)})},set:function(b){return f(this).each(function(){var a=l(f(this)),c=a.data(\"autoNumeric\"),d=b.toString(),e=b.toString();if(\"object\"!==typeof c)return f.error(\"You must initialize autoNumeric('init', {options}) prior to calling the 'set' method\"),\nthis;e!==a.attr(\"value\")&&e!==a.text()||!1!==c.runOnce||(d=d.replace(\",\",\".\"));e!==a.attr(\"value\")&&\"INPUT\"===a.prop(\"tagName\")&&!1===c.runOnce&&(d=h(d,c));if(!f.isNumeric(+d))return\"\";d=y(d,c);c.oEvent=\"set\";c.lastSetValue=d;d.toString();\"\"!==d&&(d=u(d,c));d=z(d,c.aDec,c.aNeg);s(d,c)||(d=u(\"\",c));d=t(d,c);if(a.is(\"input[type=text], input[type=hidden], input:not([type])\"))return a.val(d);if(-1!==f.inArray(a.prop(\"tagName\"),c.tagList))return a.text(d);f.error(\"The <\"+a.prop(\"tagName\")+\"> is not supported by autoNumeric()\");\nreturn!1})},get:function(){var b=l(f(this)),a=b.data(\"autoNumeric\");if(\"object\"!==typeof a)return f.error(\"You must initialize autoNumeric('init', {options}) prior to calling the 'get' method\"),this;a.oEvent=\"get\";var c=\"\";if(b.is(\"input[type=text], input[type=hidden], input:not([type])\"))c=b.eq(0).val();else if(-1!==f.inArray(b.prop(\"tagName\"),a.tagList))c=b.eq(0).text();else return f.error(\"The <\"+b.prop(\"tagName\")+\"> is not supported by autoNumeric()\"),!1;if(\"\"===c&&\"empty\"===a.wEmpty||c===a.aSign&&\n(\"sign\"===a.wEmpty||\"empty\"===a.wEmpty))return\"\";null!==a.nBracket&&\"\"!==c&&(c=negativeBracket(c,a.nBracket,a.oEvent));if(a.runOnce||!1===a.aForm)c=h(c,a);c=r(c,a.aDec,a.aNeg);0===+c&&\"keep\"!==a.lZero&&(c=\"0\");return\"keep\"===a.lZero?c:c=y(c,a)},getString:function(){var b=!1,a=l(f(this)).serialize().split(\"&\"),c=0;for(c;c<a.length;c+=1){var d=a[c].split(\"=\");\"object\"===typeof f('*[name=\"'+decodeURIComponent(d[0])+'\"]').data(\"autoNumeric\")&&null!==d[1]&&void 0!==f('*[name=\"'+decodeURIComponent(d[0])+\n'\"]').data(\"autoNumeric\")&&(d[1]=f('input[name=\"'+decodeURIComponent(d[0])+'\"]').autoNumeric(\"get\"),a[c]=d.join(\"=\"),b=!0)}if(!0===b)return a.join(\"&\");f.error(\"You must initialize autoNumeric('init', {options}) prior to calling the 'getString' method\");return this},getArray:function(){var b=!1,a=l(f(this)).serializeArray();f.each(a,function(a,d){\"object\"===typeof f('*[name=\"'+decodeURIComponent(d.name)+'\"]').data(\"autoNumeric\")&&(\"\"!==d.value&&void 0!==f('*[name=\"'+decodeURIComponent(d.name)+'\"]').data(\"autoNumeric\")&&\n(d.value=f('input[name=\"'+decodeURIComponent(d.name)+'\"]').autoNumeric(\"get\").toString()),b=!0)});if(!0===b)return a;f.error(\"You must initialize autoNumeric('init', {options}) prior to calling the 'getArray' method\");return this},getSettings:function(){return l(f(this)).eq(0).data(\"autoNumeric\")}};f.fn.autoNumeric=function(b){if(v[b])return v[b].apply(this,Array.prototype.slice.call(arguments,1));if(\"object\"===typeof b||!b)return v.init.apply(this,arguments);f.error('Method \"'+b+'\" is not supported by autoNumeric()')}})(jQuery);\n\n/*\n * jQuery SelectBox Styler v1.0.1\n * http://dimox.name/styling-select-boxes-using-jquery-css/\n * Copyright 2012 Dimox (http://dimox.name/)\n * Released under the MIT license.\n * Date: 2012.10.07\n */\n(function($){$.fn.selectbox=function(){$(this).each(function(){var select=$(this);if(select.prev('span.selectbox').length<1){function doSelect(){var option=select.find('option');var optionSelected=option.filter(':selected');var optionText=option.filter(':first').text();if(optionSelected.length)optionText=optionSelected.text();var ddlist='';for(i=0;i<option.length;i++){var selected='';var disabled=' class=\"disabled\"';if(option.eq(i).is(':selected'))selected=' class=\"selected sel\"';if(option.eq(i).is(':disabled'))selected=disabled;ddlist+='<li'+selected+'>'+option.eq(i).text()+'</li>';}var selectbox=$('<span class=\"selectbox\" style=\"display:inline-block;position:relative\">'+'<div class=\"select\" style=\"float:left;position:relative;z-index:10000\"><div class=\"text\">'+optionText+'</div>'+'<b class=\"trigger\"><i class=\"arrow\"></i></b>'+'</div>'+'<div class=\"dropdown\" style=\"position:absolute;z-index:9999;overflow:auto;overflow-x:hidden;list-style:none\">'+'<ul>'+ddlist+'</ul>'+'</div>'+'</span>');select.before(selectbox).css({position:'absolute',top:-9999});var divSelect=selectbox.find('div.select');var divText=selectbox.find('div.text');var dropdown=selectbox.find('div.dropdown');var li=dropdown.find('li');var selectHeight=selectbox.outerHeight();if(dropdown.css('left')=='auto')dropdown.css({left:0});if(dropdown.css('top')=='auto')dropdown.css({top:selectHeight});var liHeight=li.outerHeight();var position=dropdown.css('top');dropdown.hide();divSelect.click(function(){var topOffset=selectbox.offset().top;var bottomOffset=$(window).height()-selectHeight-(topOffset-$(window).scrollTop());if(bottomOffset<0||bottomOffset<liHeight*6){dropdown.height('auto').css({top:'auto',bottom:position});if(dropdown.outerHeight()>topOffset-$(window).scrollTop()-20){dropdown.height(Math.floor((topOffset-$(window).scrollTop()-20)/liHeight)*liHeight);}}else if(bottomOffset>liHeight*6){dropdown.height('auto').css({bottom:'auto',top:position});if(dropdown.outerHeight()>bottomOffset-20){dropdown.height(Math.floor((bottomOffset-20)/liHeight)*liHeight);}}$('span.selectbox').css({zIndex:1}).removeClass('focused');selectbox.css({zIndex:2});if(dropdown.is(':hidden')){$('div.dropdown:visible').hide();dropdown.show();}else{dropdown.hide();}return false;});li.hover(function(){$(this).siblings().removeClass('selected');});var selectedText=li.filter('.selected').text();li.filter(':not(.disabled)').click(function(){var liText=$(this).text();if(selectedText!=liText){$(this).addClass('selected sel').siblings().removeClass('selected sel');option.removeAttr('selected').eq($(this).index()).attr('selected',true);selectedText=liText;divText.text(liText);select.change();}dropdown.hide();});dropdown.mouseout(function(){dropdown.find('li.sel').addClass('selected');});select.focus(function(){$('span.selectbox').removeClass('focused');selectbox.addClass('focused');}).keyup(function(){divText.text(option.filter(':selected').text());li.removeClass('selected sel').eq(option.filter(':selected').index()).addClass('selected sel');});$(document).on('click',function(e){if(!$(e.target).parents().hasClass('selectbox')){dropdown.hide().find('li.sel').addClass('selected');selectbox.removeClass('focused');}});}doSelect();select.on('refresh',function(){select.prev().remove();doSelect();})}});}})(jQuery);\n\n/*! $.noUiSlider - WTFPL - refreshless.com/nouislider/ */\n(function(e){function t(e){throw new RangeError(\"noUiSlider: \"+e)}function n(e,n,r){(e[n]||e[r])&&e[n]===e[r]&&t(\"(Link) '\"+n+\"' can't match '\"+r+\"'.'\")}function r(e){return\"number\"===typeof e&&!isNaN(e)&&isFinite(e)}function i(t){return e.isArray(t)?t:[t]}function s(e,t){e.addClass(t);setTimeout(function(){e.removeClass(t)},300)}function o(e,t){return 100*t/(e[1]-e[0])}function u(e,t){if(t>=e.d.slice(-1)[0])return 100;for(var n=1,r,i,s;t>=e.d[n];)n++;r=e.d[n-1];i=e.d[n];s=e.c[n-1];r=[r,i];return s+o(r,0>r[0]?t+Math.abs(r[0]):t-r[0])/(100/(e.c[n]-s))}function a(e,t){for(var n=1,r;t>=e.c[n];)n++;if(e.m)return r=e.c[n-1],n=e.c[n],t-r>(n-r)/2?n:r;e.h[n-1]?(r=e.h[n-1],n=e.c[n-1]+Math.round((t-e.c[n-1])/r)*r):n=t;return n}function f(r){void 0===r&&(r={});\"object\"!==typeof r&&t(\"(Format) 'format' option must be an object.\");var i={};e(H).each(function(e,n){void 0===r[n]?i[n]=B[e]:typeof r[n]===typeof B[e]?(\"decimals\"===n&&(0>r[n]||7<r[n])&&t(\"(Format) 'format.decimals' option must be between 0 and 7.\"),i[n]=r[n]):t(\"(Format) 'format.\"+n+\"' must be a \"+typeof B[e]+\".\")});n(i,\"mark\",\"thousand\");n(i,\"prefix\",\"negative\");n(i,\"prefix\",\"negativeBefore\");this.B=i}function l(t,n){if(!(this instanceof l))throw Error(\"Link: Don't use Link as a function. Use the 'new' keyword.\");if(!t)throw new RangeError(\"Link: missing parameters.\");this.g=t.format||{};this.update=!n;var r=this,i=t.target||function(){},s=t.method,o=\"string\"===typeof i&&0===i.indexOf(\"-tooltip-\"),u=\"string\"===typeof i&&0!==i.indexOf(\"-\"),a=\"function\"===typeof i,f=i instanceof e||e.zepto&&e.zepto.isZ(i),c=f&&i.is(\"input, select, textarea\"),h=f&&\"function\"===typeof s,p=f&&\"string\"===typeof s&&i[s];if(o)this.method=s||\"html\",this.j=e(i.replace(\"-tooltip-\",\"\")||\"<div/>\")[0];else if(u)this.method=\"val\",this.j=document.createElement(\"input\"),this.j.name=i,this.j.type=\"hidden\";else if(a)this.target=!1,this.method=i;else{if(f){if(s&&(h||p)){this.target=i;this.method=s;return}if(!s&&c){this.method=\"val\";this.target=i;this.target.on(\"change\",function(t){t=e(t.target).val();var n=r.q;r.u.val([n?null:t,n?t:null],{link:r})});return}if(!s&&!c){this.method=\"html\";this.target=i;return}}throw new RangeError(\"Link: Invalid Link.\")}}function c(e,n){r(n)||t(\"'step' is not numeric.\");e.h[0]=n}function h(n,i){(\"object\"!==typeof i||e.isArray(i))&&t(\"'range' is not an object.\");e.each(i,function(i,s){var o;\"number\"===typeof s&&(s=[s]);e.isArray(s)||t(\"'range' contains invalid value.\");o=\"min\"===i?0:\"max\"===i?100:parseFloat(i);r(o)&&r(s[0])||t(\"'range' value isn't numeric.\");n.c.push(o);n.d.push(s[0]);o?n.h.push(isNaN(s[1])?!1:s[1]):isNaN(s[1])||(n.h[0]=s[1])});e.each(n.h,function(e,t){if(!t)return!0;n.h[e]=o([n.d[e],n.d[e+1]],t)/(100/(n.c[e+1]-n.c[e]))})}function p(n,r){\"number\"===typeof r&&(r=[r]);(!e.isArray(r)||!r.length||2<r.length)&&t(\"'start' option is incorrect.\");n.a=r.length;n.start=r}function d(e,n){e.m=n;\"boolean\"!==typeof n&&t(\"'snap' option must be a boolean.\")}function v(e,n){\"lower\"===n&&1===e.a?e.i=1:\"upper\"===n&&1===e.a?e.i=2:!0===n&&2===e.a?e.i=3:!1===n?e.i=0:t(\"'connect' option was doesn't match handle count.\")}function m(e,n){switch(n){case\"horizontal\":e.k=0;break;case\"vertical\":e.k=1;break;default:t(\"'orientation' option is invalid.\")}}function g(e,n){2<e.c.length&&t(\"'margin' option is only supported on linear sliders.\");e.margin=o(e.d,n);r(n)||t(\"'margin' option must be numeric.\")}function y(e,n){switch(n){case\"ltr\":e.dir=0;break;case\"rtl\":e.dir=1;e.i=[0,2,1,3][e.i];break;default:t(\"'direction' option was not recognized.\")}}function b(e,n){\"string\"!==typeof n&&t(\"'behaviour' must be a string containing options.\");var r=0<=n.indexOf(\"snap\");e.n={p:0<=n.indexOf(\"tap\")||r,extend:0<=n.indexOf(\"extend\"),s:0<=n.indexOf(\"drag\"),fixed:0<=n.indexOf(\"fixed\"),m:r}}function w(n,r,i){n.o=[r.lower,r.upper];n.g=new f(r.format);e.each(n.o,function(n,s){e.isArray(s)||t(\"'serialization.\"+(n?\"upper\":\"lower\")+\"' must be an array.\");e.each(s,function(){this instanceof l||t(\"'serialization.\"+(n?\"upper\":\"lower\")+\"' can only contain Link instances.\");this.q=n;this.u=i;this.scope=this.scope||i;this.g=new f(e.extend({},r.format,this.g))})});n.dir&&1<n.a&&n.o.reverse()}function E(n,r){var i={c:[],d:[],h:[!1],margin:0},s;s={step:{e:!1,f:c},range:{e:!0,f:h},start:{e:!0,f:p},snap:{e:!1,f:d},connect:{e:!0,f:v},orientation:{e:!1,f:m},margin:{e:!1,f:g},direction:{e:!0,f:y},behaviour:{e:!0,f:b},serialization:{e:!0,f:w}};n=e.extend({connect:!1,direction:\"ltr\",behaviour:\"tap\",orientation:\"horizontal\"},n);n.serialization=e.extend({lower:[],upper:[],format:{}},n.serialization);e.each(s,function(e,s){if(void 0===n[e])if(s.e)t(\"'\"+e+\"' is required.\");else return!0;s.f(i,n[e],r)});i.style=i.k?\"top\":\"left\";return i}function S(t,n){var r=e(\"<div><div/></div>\").addClass(P[2]),i=[\"-lower\",\"-upper\"];t.dir&&i.reverse();r.children().addClass(P[3]+\" \"+P[3]+i[n]);return r}function x(t,n){n.j&&(n=new l({target:e(n.j).clone().appendTo(t),method:n.method,format:n.g},!0));return n}function T(e,t){var n,r=[];for(n=0;n<e.a;n++){var i=r,s=n,o=e.o[n],u=t[n].children(),a=void 0,f=[];f.push(new l({format:e.g},!0));for(a=0;a<o.length;a++)f.push(x(u,o[a]));i[s]=f}return r}function N(e,t,n){switch(e){case 1:t.addClass(P[7]);n[0].addClass(P[6]);break;case 3:n[1].addClass(P[6]);case 2:n[0].addClass(P[7]);case 0:t.addClass(P[6])}}function C(e,t){var n,r=[];for(n=0;n<e.a;n++)r.push(S(e,n).appendTo(t));return r}function k(t,n){n.addClass([P[0],P[8+t.dir],P[4+t.k]].join(\" \"));return e(\"<div/>\").appendTo(n).addClass(P[1])}function L(t,n,r){function i(){return b[[\"width\",\"height\"][n.k]]()}function o(e){var t,n=[g.val()];for(t=0;t<e.length;t++)g.trigger(e[t],n)}function f(t,r,i){var s=t[0]!==E[0][0]?1:0,o=y[0]+n.margin,u=y[1]-n.margin;i&&1<E.length&&(r=s?Math.max(r,o):Math.min(r,u));100>r&&(r=a(n,r));r=Math.max(Math.min(parseFloat(r.toFixed(7)),100),0);if(r===y[s])return 1===E.length?!1:r===o||r===u?0:!1;t.css(n.style,r+\"%\");t.is(\":first-child\")&&t.toggleClass(P[17],50<r);y[s]=r;n.dir&&(r=100-r);e(w[s]).each(function(){this.write(n,r,t.children(),g)});return!0}function l(e,t,n){n||s(g,P[14]);f(e,t,!1);o([\"slide\",\"set\",\"change\"])}function c(e,t,r,i){e=e.replace(/\\s/g,\".nui \")+\".nui\";t.on(e,function(e){var t=g.attr(\"disabled\");if(g.hasClass(P[14])||void 0!==t&&null!==t)return!1;e.preventDefault();var t=0===e.type.indexOf(\"touch\"),s=0===e.type.indexOf(\"mouse\"),o=0===e.type.indexOf(\"pointer\"),u,a,f=e;0===e.type.indexOf(\"MSPointer\")&&(o=!0);e.originalEvent&&(e=e.originalEvent);t&&(u=e.changedTouches[0].pageX,a=e.changedTouches[0].pageY);if(s||o)o||void 0!==window.pageXOffset||(window.pageXOffset=document.documentElement.scrollLeft,window.pageYOffset=document.documentElement.scrollTop),u=e.clientX+window.pageXOffset,a=e.clientY+window.pageYOffset;f.v=[u,a];f.cursor=s;e=f;e.l=e.v[n.k];r(e,i)})}function h(e,t){var n=t.a||E,r,s=!1,s=100*(e.l-t.start)/i(),u=n[0][0]!==E[0][0]?1:0;var a=t.w;r=s+a[0];s+=a[1];1<n.length?(0>r&&(s+=Math.abs(r)),100<s&&(r-=s-100),r=[Math.max(Math.min(r,100),0),Math.max(Math.min(s,100),0)]):r=[r,s];s=f(n[0],r[u],1===n.length);1<n.length&&(s=f(n[1],r[u?0:1],!1)||s);s&&o([\"slide\"])}function p(t){e(\".\"+P[15]).removeClass(P[15]);t.cursor&&e(\"body\").css(\"cursor\",\"\").off(\".nui\");M.off(\".nui\");g.removeClass(P[12]);o([\"set\",\"change\"])}function d(t,n){1===n.a.length&&n.a[0].children().addClass(P[15]);t.stopPropagation();c(D.move,M,h,{start:t.l,a:n.a,w:[y[0],y[E.length-1]]});c(D.end,M,p,null);t.cursor&&(e(\"body\").css(\"cursor\",e(t.target).css(\"cursor\")),1<E.length&&g.addClass(P[12]),e(\"body\").on(\"selectstart.nui\",!1))}function v(t){var r=t.l,s=0;t.stopPropagation();e.each(E,function(){s+=this.offset()[n.style]});s=r<s/2||1===E.length?0:1;r-=b.offset()[n.style];r=100*r/i();l(E[s],r,n.n.m);n.n.m&&d(t,{a:[E[s]]})}function m(e){var t=(e=e.l<b.offset()[n.style])?0:100;e=e?0:E.length-1;l(E[e],t,!1)}var g=e(t),y=[-1,-1],b,w,E;b=k(n,g);E=C(n,b);w=T(n,E);N(n.i,g,E);(function(e){var t;if(!e.fixed)for(t=0;t<E.length;t++)c(D.start,E[t].children(),d,{a:[E[t]]});e.p&&c(D.start,b,v,{a:E});e.extend&&(g.addClass(P[16]),e.p&&c(D.start,g,m,{a:E}));e.s&&(t=b.find(\".\"+P[7]).addClass(P[10]),e.fixed&&(t=t.add(b.children().not(t).children())),c(D.start,t,d,{a:E}))})(n.n);t.F=function(t,r,i,a,l){var c;n.dir&&1<n.a&&t.reverse();l&&s(g,P[14]);for(c=0;c<(1<E.length?3:1);c++)l=i||w[c%2][0],l=l.valueOf(t[c%2]),!1!==l&&(l=u(n,l),n.dir&&(l=100-l),!0!==f(E[c%2],l,!0)&&e(w[c%2]).each(function(){this.write(n,y[c%2],E[c%2].children(),g,a)}));!0===r&&o([\"set\"])};t.D=function(){var e,t=[];for(e=0;e<n.a;e++)t[e]=w[e][0].A;return 1===t.length?t[0]:n.dir&&1<n.a?t.reverse():t};t.r=function(){e.each(w,function(){e.each(this,function(){this.target&&this.target.off(\".nui\")})});e(this).off(\".nui\").removeClass(P.join(\" \")).empty();return r};g.val(n.start)}function A(e){this.length||t(\"Can't initialize slider on empty selection.\");var n=E(e,this);return this.each(function(){L(this,n,e)})}function O(t){return this.each(function(){var n=e(this).val(),r=this.r(),i=e.extend({},r,t);e(this).noUiSlider(i);r.start===i.start&&e(this).val(n)})}var M=e(document),_=e.fn.val,D=window.navigator.G?{start:\"pointerdown\",move:\"pointermove\",end:\"pointerup\"}:window.navigator.msPointerEnabled?{start:\"MSPointerDown\",move:\"MSPointerMove\",end:\"MSPointerUp\"}:{start:\"mousedown touchstart\",move:\"mousemove touchmove\",end:\"mouseup touchend\"},P=\"noUi-target noUi-base noUi-origin noUi-handle noUi-horizontal noUi-vertical noUi-background noUi-connect noUi-ltr noUi-rtl noUi-dragable  noUi-state-drag  noUi-state-tap noUi-active noUi-extended noUi-stacking\".split(\" \"),H=\"decimals mark thousand prefix postfix encoder decoder negative negativeBefore\".split(\" \"),B=[2,\".\",\"\",\"\",\"\",function(e){return e},function(e){return e},\"-\",\"\"];f.prototype.b=function(e){return this.B[e]};f.prototype.C=function(e){function t(e){return e.split(\"\").reverse().join(\"\")}e=this.b(\"encoder\")(e);var n=\"\",r=\"\",i=\"\",s=\"\";0>e&&(n=this.b(\"negative\"),r=this.b(\"negativeBefore\"));e=Math.abs(e).toFixed(this.b(\"decimals\")).toString();e=e.split(\".\");0===parseFloat(e)&&(e[0]=\"0\");this.b(\"thousand\")?(i=t(e[0]).match(/.{1,3}/g),i=t(i.join(t(this.b(\"thousand\"))))):i=e[0];this.b(\"mark\")&&1<e.length&&(s=this.b(\"mark\")+e[1]);return r+this.b(\"prefix\")+n+i+s+this.b(\"postfix\")};f.prototype.t=function(e){function t(e){return e.replace(/[\\-\\/\\\\\\^$*+?.()|\\[\\]{}]/g,\"\\\\$&\")}var n;if(null===e||void 0===e)return!1;e=e.toString();n=e.replace(RegExp(\"^\"+t(this.b(\"negativeBefore\"))),\"\");e!==n?(e=n,n=\"-\"):n=\"\";e=e.replace(RegExp(\"^\"+t(this.b(\"prefix\"))),\"\");this.b.negative&&(n=\"\",e=e.replace(RegExp(\"^\"+t(this.b(\"negative\"))),\"-\"));e=e.replace(RegExp(t(this.b(\"postfix\"))+\"$\"),\"\").replace(RegExp(t(this.b(\"thousand\")),\"g\"),\"\").replace(this.b(\"mark\"),\".\");e=this.b(\"decoder\")(parseFloat(n+e));return isNaN(e)?!1:e};l.prototype.write=function(e,t,n,r,i){if(!this.update||!1!==i){if(100<=t)t=e.d.slice(-1)[0];else{i=1;for(var s,o,u;t>=e.c[i];)i++;s=e.d[i-1];o=e.d[i];u=e.c[i-1];s=[s,o];t=100/(e.c[i]-u)*(t-u)*(s[1]-s[0])/100+s[0]}this.A=t=this.format(t);if(\"function\"===typeof this.method)this.method.call(this.target[0]||r[0],t,n,r);else this.target[this.method](t,n,r)}};l.prototype.format=function(e){return this.g.C(e)};l.prototype.valueOf=function(e){return this.g.t(e)};e.noUiSlider={Link:l};e.fn.noUiSlider=function(e,t){return(t?O:A).call(this,e)};e.fn.val=function(){var t=Array.prototype.slice.call(arguments,0),n,r,s,o;if(!t.length)return this.hasClass(P[0])?this[0].D():_.apply(this);\"object\"===typeof t[1]?(n=t[1].set,r=t[1].link,s=t[1].update,o=t[1].animate):!0===t[1]&&(n=!0);return this.each(function(){e(this).hasClass(P[0])?this.F(i(t[0]),n,r,s,o):_.apply(e(this),t)})}})(window.jQuery||window.Zepto);\n\n/*!jQuery Knob*/\n/*\n * Downward compatible, touchable dial\n *\n * Version: 1.2.8\n *\n * Copyright (c) 2012 Anthony Terrien\n * Under MIT License (http://www.opensource.org/licenses/mit-license.php)\n *\n * Thanks to vor, eskimoblood, spiffistan, FabrizioC\n */\n(function(e){\"use strict\";var t={},n=Math.max,r=Math.min;t.c={};t.c.d=e(document);t.c.t=function(e){return e.originalEvent.touches.length-1};t.o=function(){var n=this;this.o=null;this.$=null;this.i=null;this.g=null;this.v=null;this.cv=null;this.x=0;this.y=0;this.w=0;this.h=0;this.$c=null;this.c=null;this.t=0;this.isInit=false;this.fgColor=null;this.pColor=null;this.dH=null;this.cH=null;this.eH=null;this.rH=null;this.scale=1;this.relative=false;this.relativeWidth=false;this.relativeHeight=false;this.$div=null;this.run=function(){var t=function(e,t){var r;for(r in t){n.o[r]=t[r]}n._carve().init();n._configure()._draw()};if(this.$.data(\"kontroled\"))return;this.$.data(\"kontroled\",true);this.extend();this.o=e.extend({min:this.$.data(\"min\")!==undefined?this.$.data(\"min\"):0,max:this.$.data(\"max\")!==undefined?this.$.data(\"max\"):100,stopper:true,readOnly:this.$.data(\"readonly\")||this.$.attr(\"readonly\")===\"readonly\",cursor:this.$.data(\"cursor\")===true&&30||this.$.data(\"cursor\")||0,thickness:this.$.data(\"thickness\")&&Math.max(Math.min(this.$.data(\"thickness\"),1),.01)||.35,lineCap:this.$.data(\"linecap\")||\"butt\",width:this.$.data(\"width\")||200,height:this.$.data(\"height\")||200,displayInput:this.$.data(\"displayinput\")==null||this.$.data(\"displayinput\"),displayPrevious:this.$.data(\"displayprevious\"),fgColor:this.$.data(\"fgcolor\")||\"#87CEEB\",inputColor:this.$.data(\"inputcolor\"),font:this.$.data(\"font\")||\"Arial\",fontWeight:this.$.data(\"font-weight\")||\"bold\",inline:false,step:this.$.data(\"step\")||1,rotation:this.$.data(\"rotation\"),draw:null,change:null,cancel:null,release:null,format:function(e){return e},parse:function(e){return parseFloat(e)}},this.o);this.o.flip=this.o.rotation===\"anticlockwise\"||this.o.rotation===\"acw\";if(!this.o.inputColor){this.o.inputColor=this.o.fgColor}if(this.$.is(\"fieldset\")){this.v={};this.i=this.$.find(\"input\");this.i.each(function(t){var r=e(this);n.i[t]=r;n.v[t]=n.o.parse(r.val());r.bind(\"change blur\",function(){var e={};e[t]=r.val();n.val(e)})});this.$.find(\"legend\").remove()}else{this.i=this.$;this.v=this.o.parse(this.$.val());this.v===\"\"&&(this.v=this.o.min);this.$.bind(\"change blur\",function(){n.val(n._validate(n.o.parse(n.$.val())))})}!this.o.displayInput&&this.$.hide();this.$c=e(document.createElement(\"canvas\")).attr({width:this.o.width,height:this.o.height});this.$div=e('<div style=\"'+(this.o.inline?\"display:inline;\":\"\")+\"width:\"+this.o.width+\"px;height:\"+this.o.height+\"px;\"+'\"></div>');this.$.wrap(this.$div).before(this.$c);this.$div=this.$.parent();if(typeof G_vmlCanvasManager!==\"undefined\"){G_vmlCanvasManager.initElement(this.$c[0])}this.c=this.$c[0].getContext?this.$c[0].getContext(\"2d\"):null;if(!this.c){throw{name:\"CanvasNotSupportedException\",message:\"Canvas not supported. Please use excanvas on IE8.0.\",toString:function(){return this.name+\": \"+this.message}}}this.scale=(window.devicePixelRatio||1)/(this.c.webkitBackingStorePixelRatio||this.c.mozBackingStorePixelRatio||this.c.msBackingStorePixelRatio||this.c.oBackingStorePixelRatio||this.c.backingStorePixelRatio||1);this.relativeWidth=this.o.width%1!==0&&this.o.width.indexOf(\"%\");this.relativeHeight=this.o.height%1!==0&&this.o.height.indexOf(\"%\");this.relative=this.relativeWidth||this.relativeHeight;this._carve();if(this.v instanceof Object){this.cv={};this.copy(this.v,this.cv)}else{this.cv=this.v}this.$.bind(\"configure\",t).parent().bind(\"configure\",t);this._listen()._configure()._xy().init();this.isInit=true;this.$.val(this.o.format(this.v));this._draw();return this};this._carve=function(){if(this.relative){var e=this.relativeWidth?this.$div.parent().width()*parseInt(this.o.width)/100:this.$div.parent().width(),t=this.relativeHeight?this.$div.parent().height()*parseInt(this.o.height)/100:this.$div.parent().height();this.w=this.h=Math.min(e,t)}else{this.w=this.o.width;this.h=this.o.height}this.$div.css({width:this.w+\"px\",height:this.h+\"px\"});this.$c.attr({width:this.w,height:this.h});if(this.scale!==1){this.$c[0].width=this.$c[0].width*this.scale;this.$c[0].height=this.$c[0].height*this.scale;this.$c.width(this.w);this.$c.height(this.h)}return this};this._draw=function(){var e=true;n.g=n.c;n.clear();n.dH&&(e=n.dH());e!==false&&n.draw()};this._touch=function(e){var r=function(e){var t=n.xy2val(e.originalEvent.touches[n.t].pageX,e.originalEvent.touches[n.t].pageY);if(t==n.cv)return;if(n.cH&&n.cH(t)===false)return;n.change(n._validate(t));n._draw()};this.t=t.c.t(e);r(e);t.c.d.bind(\"touchmove.k\",r).bind(\"touchend.k\",function(){t.c.d.unbind(\"touchmove.k touchend.k\");n.val(n.cv)});return this};this._mouse=function(e){var r=function(e){var t=n.xy2val(e.pageX,e.pageY);if(t==n.cv)return;if(n.cH&&n.cH(t)===false)return;n.change(n._validate(t));n._draw()};r(e);t.c.d.bind(\"mousemove.k\",r).bind(\"keyup.k\",function(e){if(e.keyCode===27){t.c.d.unbind(\"mouseup.k mousemove.k keyup.k\");if(n.eH&&n.eH()===false)return;n.cancel()}}).bind(\"mouseup.k\",function(e){t.c.d.unbind(\"mousemove.k mouseup.k keyup.k\");n.val(n.cv)});return this};this._xy=function(){var e=this.$c.offset();this.x=e.left;this.y=e.top;return this};this._listen=function(){if(!this.o.readOnly){this.$c.bind(\"mousedown\",function(e){e.preventDefault();n._xy()._mouse(e)}).bind(\"touchstart\",function(e){e.preventDefault();n._xy()._touch(e)});this.listen()}else{this.$.attr(\"readonly\",\"readonly\")}if(this.relative){e(window).resize(function(){n._carve().init();n._draw()})}return this};this._configure=function(){if(this.o.draw)this.dH=this.o.draw;if(this.o.change)this.cH=this.o.change;if(this.o.cancel)this.eH=this.o.cancel;if(this.o.release)this.rH=this.o.release;if(this.o.displayPrevious){this.pColor=this.h2rgba(this.o.fgColor,\"0.4\");this.fgColor=this.h2rgba(this.o.fgColor,\"0.6\")}else{this.fgColor=this.o.fgColor}return this};this._clear=function(){this.$c[0].width=this.$c[0].width};this._validate=function(e){return~~((e<0?-.5:.5)+e/this.o.step)*this.o.step};this.listen=function(){};this.extend=function(){};this.init=function(){};this.change=function(e){};this.val=function(e){};this.xy2val=function(e,t){};this.draw=function(){};this.clear=function(){this._clear()};this.h2rgba=function(e,t){var n;e=e.substring(1,7);n=[parseInt(e.substring(0,2),16),parseInt(e.substring(2,4),16),parseInt(e.substring(4,6),16)];return\"rgba(\"+n[0]+\",\"+n[1]+\",\"+n[2]+\",\"+t+\")\"};this.copy=function(e,t){for(var n in e){t[n]=e[n]}}};t.Dial=function(){t.o.call(this);this.startAngle=null;this.xy=null;this.radius=null;this.lineWidth=null;this.cursorExt=null;this.w2=null;this.PI2=2*Math.PI;this.extend=function(){this.o=e.extend({bgColor:this.$.data(\"bgcolor\")||\"#FFFFFF\",angleOffset:this.$.data(\"angleoffset\")||0,angleArc:this.$.data(\"anglearc\")||360,inline:true},this.o)};this.val=function(e,t){if(null!=e){e=this.o.parse(e);if(t!==false&&e!=this.v&&this.rH&&this.rH(e)===false)return;this.cv=this.o.stopper?n(r(e,this.o.max),this.o.min):e;this.v=this.cv;this.$.val(this.o.format(this.v));this._draw()}else{return this.v}};this.xy2val=function(e,t){var i,s;i=Math.atan2(e-(this.x+this.w2),-(t-this.y-this.w2))-this.angleOffset;if(this.o.flip){i=this.angleArc-i-this.PI2}if(this.angleArc!=this.PI2&&i<0&&i>-.5){i=0}else if(i<0){i+=this.PI2}s=~~(.5+i*(this.o.max-this.o.min)/this.angleArc)+this.o.min;this.o.stopper&&(s=n(r(s,this.o.max),this.o.min));return s};this.listen=function(){var t=this,i,s,o=function(e){e.preventDefault();var o=e.originalEvent,u=o.detail||o.wheelDeltaX,a=o.detail||o.wheelDeltaY,f=t._validate(t.o.parse(t.$.val()))+(u>0||a>0?t.o.step:u<0||a<0?-t.o.step:0);f=n(r(f,t.o.max),t.o.min);t.val(f,false);if(t.rH){clearTimeout(i);i=setTimeout(function(){t.rH(f);i=null},100);if(!s){s=setTimeout(function(){if(i)t.rH(f);s=null},200)}}},u,a,f=1,l={37:-t.o.step,38:t.o.step,39:t.o.step,40:-t.o.step};this.$.bind(\"keydown\",function(i){var s=i.keyCode;if(s>=96&&s<=105){s=i.keyCode=s-48}u=parseInt(String.fromCharCode(s));if(isNaN(u)){s!==13&&s!==8&&s!==9&&s!==189&&(s!==190||t.$.val().match(/\\./))&&i.preventDefault();if(e.inArray(s,[37,38,39,40])>-1){i.preventDefault();var o=t.o.parse(t.$.val())+l[s]*f;t.o.stopper&&(o=n(r(o,t.o.max),t.o.min));t.change(o);t._draw();a=window.setTimeout(function(){f*=2},30)}}}).bind(\"keyup\",function(e){if(isNaN(u)){if(a){window.clearTimeout(a);a=null;f=1;t.val(t.$.val())}}else{t.$.val()>t.o.max&&t.$.val(t.o.max)||t.$.val()<t.o.min&&t.$.val(t.o.min)}});this.$c.bind(\"mousewheel DOMMouseScroll\",o);this.$.bind(\"mousewheel DOMMouseScroll\",o)};this.init=function(){if(this.v<this.o.min||this.v>this.o.max)this.v=this.o.min;this.$.val(this.v);this.w2=this.w/2;this.cursorExt=this.o.cursor/100;this.xy=this.w2*this.scale;this.lineWidth=this.xy*this.o.thickness;this.lineCap=this.o.lineCap;this.radius=this.xy-this.lineWidth/2;this.o.angleOffset&&(this.o.angleOffset=isNaN(this.o.angleOffset)?0:this.o.angleOffset);this.o.angleArc&&(this.o.angleArc=isNaN(this.o.angleArc)?this.PI2:this.o.angleArc);this.angleOffset=this.o.angleOffset*Math.PI/180;this.angleArc=this.o.angleArc*Math.PI/180;this.startAngle=1.5*Math.PI+this.angleOffset;this.endAngle=1.5*Math.PI+this.angleOffset+this.angleArc;var e=n(String(Math.abs(this.o.max)).length,String(Math.abs(this.o.min)).length,2)+2;this.o.displayInput&&this.i.css({width:(this.w/2+4>>0)+\"px\",height:(this.w/3>>0)+\"px\",position:\"absolute\",\"vertical-align\":\"middle\",\"margin-top\":(this.w/3>>0)+\"px\",\"margin-left\":\"-\"+(this.w*3/4+2>>0)+\"px\",border:0,background:\"none\",font:this.o.fontWeight+\" \"+(this.w/e>>0)+\"px \"+this.o.font,\"text-align\":\"center\",color:this.o.inputColor||this.o.fgColor,padding:\"0px\",\"-webkit-appearance\":\"none\"})||this.i.css({width:\"0px\",visibility:\"hidden\"})};this.change=function(e){this.cv=e;this.$.val(this.o.format(e))};this.angle=function(e){return(e-this.o.min)*this.angleArc/(this.o.max-this.o.min)};this.arc=function(e){var t,n;e=this.angle(e);if(this.o.flip){t=this.endAngle+1e-5;n=t-e-1e-5}else{t=this.startAngle-1e-5;n=t+e+1e-5}this.o.cursor&&(t=n-this.cursorExt)&&(n=n+this.cursorExt);return{s:t,e:n,d:this.o.flip&&!this.o.cursor}};this.draw=function(){var e=this.g,t=this.arc(this.cv),n,r=1;e.lineWidth=this.lineWidth;e.lineCap=this.lineCap;if(this.o.displayPrevious){n=this.arc(this.v);e.beginPath();e.strokeStyle=this.pColor;e.arc(this.xy,this.xy,this.radius,n.s,n.e,n.d);e.stroke();r=this.cv==this.v}e.beginPath();e.strokeStyle=r?this.o.fgColor:this.fgColor;e.arc(this.xy,this.xy,this.radius,t.s,t.e,t.d);e.stroke()};this.cancel=function(){this.val(this.v)}};e.fn.dial=e.fn.knob=function(n){return this.each(function(){var r=new t.Dial;r.o=n;r.$=e(this);r.run()}).parent()}})(jQuery);\n\n/*\n* jQuery Form Plugin; v20131228\n* http://jquery.malsup.com/form/\n* Copyright (c) 2013 M. Alsup; Dual licensed: MIT/GPL\n* https://github.com/malsup/form#copyright-and-license\n*/\n;!function(a){\"use strict\";\"function\"==typeof define&&define.amd?define([\"jquery\"],a):a(\"undefined\"!=typeof jQuery?jQuery:window.Zepto)}(function(a){\"use strict\";function b(b){var c=b.data;b.isDefaultPrevented()||(b.preventDefault(),a(b.target).ajaxSubmit(c))}function c(b){var c=b.target,d=a(c);if(!d.is(\"[type=submit],[type=image]\")){var e=d.closest(\"[type=submit]\");if(0===e.length)return;c=e[0]}var f=this;if(f.clk=c,\"image\"==c.type)if(void 0!==b.offsetX)f.clk_x=b.offsetX,f.clk_y=b.offsetY;else if(\"function\"==typeof a.fn.offset){var g=d.offset();f.clk_x=b.pageX-g.left,f.clk_y=b.pageY-g.top}else f.clk_x=b.pageX-c.offsetLeft,f.clk_y=b.pageY-c.offsetTop;setTimeout(function(){f.clk=f.clk_x=f.clk_y=null},100)}function d(){if(a.fn.ajaxSubmit.debug){var b=\"[jquery.form] \"+Array.prototype.join.call(arguments,\"\");window.console&&window.console.log?window.console.log(b):window.opera&&window.opera.postError&&window.opera.postError(b)}}var e={};e.fileapi=void 0!==a(\"<input type='file'/>\").get(0).files,e.formdata=void 0!==window.FormData;var f=!!a.fn.prop;a.fn.attr2=function(){if(!f)return this.attr.apply(this,arguments);var a=this.prop.apply(this,arguments);return a&&a.jquery||\"string\"==typeof a?a:this.attr.apply(this,arguments)},a.fn.ajaxSubmit=function(b){function c(c){var d,e,f=a.param(c,b.traditional).split(\"&\"),g=f.length,h=[];for(d=0;g>d;d++)f[d]=f[d].replace(/\\+/g,\" \"),e=f[d].split(\"=\"),h.push([decodeURIComponent(e[0]),decodeURIComponent(e[1])]);return h}function g(d){for(var e=new FormData,f=0;f<d.length;f++)e.append(d[f].name,d[f].value);if(b.extraData){var g=c(b.extraData);for(f=0;f<g.length;f++)g[f]&&e.append(g[f][0],g[f][1])}b.data=null;var h=a.extend(!0,{},a.ajaxSettings,b,{contentType:!1,processData:!1,cache:!1,type:i||\"POST\"});b.uploadProgress&&(h.xhr=function(){var c=a.ajaxSettings.xhr();return c.upload&&c.upload.addEventListener(\"progress\",function(a){var c=0,d=a.loaded||a.position,e=a.total;a.lengthComputable&&(c=Math.ceil(d/e*100)),b.uploadProgress(a,d,e,c)},!1),c}),h.data=null;var j=h.beforeSend;return h.beforeSend=function(a,c){c.data=b.formData?b.formData:e,j&&j.call(this,a,c)},a.ajax(h)}function h(c){function e(a){var b=null;try{a.contentWindow&&(b=a.contentWindow.document)}catch(c){d(\"cannot get iframe.contentWindow document: \"+c)}if(b)return b;try{b=a.contentDocument?a.contentDocument:a.document}catch(c){d(\"cannot get iframe.contentDocument: \"+c),b=a.document}return b}function g(){function b(){try{var a=e(r).readyState;d(\"state = \"+a),a&&\"uninitialized\"==a.toLowerCase()&&setTimeout(b,50)}catch(c){d(\"Server abort: \",c,\" (\",c.name,\")\"),h(A),w&&clearTimeout(w),w=void 0}}var c=l.attr2(\"target\"),f=l.attr2(\"action\"),g=\"multipart/form-data\",j=l.attr(\"enctype\")||l.attr(\"encoding\")||g;x.setAttribute(\"target\",o),(!i||/post/i.test(i))&&x.setAttribute(\"method\",\"POST\"),f!=m.url&&x.setAttribute(\"action\",m.url),m.skipEncodingOverride||i&&!/post/i.test(i)||l.attr({encoding:\"multipart/form-data\",enctype:\"multipart/form-data\"}),m.timeout&&(w=setTimeout(function(){v=!0,h(z)},m.timeout));var k=[];try{if(m.extraData)for(var n in m.extraData)m.extraData.hasOwnProperty(n)&&(a.isPlainObject(m.extraData[n])&&m.extraData[n].hasOwnProperty(\"name\")&&m.extraData[n].hasOwnProperty(\"value\")?k.push(a('<input type=\"hidden\" name=\"'+m.extraData[n].name+'\">').val(m.extraData[n].value).appendTo(x)[0]):k.push(a('<input type=\"hidden\" name=\"'+n+'\">').val(m.extraData[n]).appendTo(x)[0]));m.iframeTarget||q.appendTo(\"body\"),r.attachEvent?r.attachEvent(\"onload\",h):r.addEventListener(\"load\",h,!1),setTimeout(b,15);try{x.submit()}catch(p){var s=document.createElement(\"form\").submit;s.apply(x)}}finally{x.setAttribute(\"action\",f),x.setAttribute(\"enctype\",j),c?x.setAttribute(\"target\",c):l.removeAttr(\"target\"),a(k).remove()}}function h(b){if(!s.aborted&&!F){if(E=e(r),E||(d(\"cannot access response document\"),b=A),b===z&&s)return s.abort(\"timeout\"),y.reject(s,\"timeout\"),void 0;if(b==A&&s)return s.abort(\"server abort\"),y.reject(s,\"error\",\"server abort\"),void 0;if(E&&E.location.href!=m.iframeSrc||v){r.detachEvent?r.detachEvent(\"onload\",h):r.removeEventListener(\"load\",h,!1);var c,f=\"success\";try{if(v)throw\"timeout\";var g=\"xml\"==m.dataType||E.XMLDocument||a.isXMLDoc(E);if(d(\"isXml=\"+g),!g&&window.opera&&(null===E.body||!E.body.innerHTML)&&--G)return d(\"requeing onLoad callback, DOM not available\"),setTimeout(h,250),void 0;var i=E.body?E.body:E.documentElement;s.responseText=i?i.innerHTML:null,s.responseXML=E.XMLDocument?E.XMLDocument:E,g&&(m.dataType=\"xml\"),s.getResponseHeader=function(a){var b={\"content-type\":m.dataType};return b[a.toLowerCase()]},i&&(s.status=Number(i.getAttribute(\"status\"))||s.status,s.statusText=i.getAttribute(\"statusText\")||s.statusText);var j=(m.dataType||\"\").toLowerCase(),k=/(json|script|text)/.test(j);if(k||m.textarea){var l=E.getElementsByTagName(\"textarea\")[0];if(l)s.responseText=l.value,s.status=Number(l.getAttribute(\"status\"))||s.status,s.statusText=l.getAttribute(\"statusText\")||s.statusText;else if(k){var o=E.getElementsByTagName(\"pre\")[0],p=E.getElementsByTagName(\"body\")[0];o?s.responseText=o.textContent?o.textContent:o.innerText:p&&(s.responseText=p.textContent?p.textContent:p.innerText)}}else\"xml\"==j&&!s.responseXML&&s.responseText&&(s.responseXML=H(s.responseText));try{D=J(s,j,m)}catch(t){f=\"parsererror\",s.error=c=t||f}}catch(t){d(\"error caught: \",t),f=\"error\",s.error=c=t||f}s.aborted&&(d(\"upload aborted\"),f=null),s.status&&(f=s.status>=200&&s.status<300||304===s.status?\"success\":\"error\"),\"success\"===f?(m.success&&m.success.call(m.context,D,\"success\",s),y.resolve(s.responseText,\"success\",s),n&&a.event.trigger(\"ajaxSuccess\",[s,m])):f&&(void 0===c&&(c=s.statusText),m.error&&m.error.call(m.context,s,f,c),y.reject(s,\"error\",c),n&&a.event.trigger(\"ajaxError\",[s,m,c])),n&&a.event.trigger(\"ajaxComplete\",[s,m]),n&&!--a.active&&a.event.trigger(\"ajaxStop\"),m.complete&&m.complete.call(m.context,s,f),F=!0,m.timeout&&clearTimeout(w),setTimeout(function(){m.iframeTarget?q.attr(\"src\",m.iframeSrc):q.remove(),s.responseXML=null},100)}}}var j,k,m,n,o,q,r,s,t,u,v,w,x=l[0],y=a.Deferred();if(y.abort=function(a){s.abort(a)},c)for(k=0;k<p.length;k++)j=a(p[k]),f?j.prop(\"disabled\",!1):j.removeAttr(\"disabled\");if(m=a.extend(!0,{},a.ajaxSettings,b),m.context=m.context||m,o=\"jqFormIO\"+(new Date).getTime(),m.iframeTarget?(q=a(m.iframeTarget),u=q.attr2(\"name\"),u?o=u:q.attr2(\"name\",o)):(q=a('<iframe name=\"'+o+'\" src=\"'+m.iframeSrc+'\" />'),q.css({position:\"absolute\",top:\"-1000px\",left:\"-1000px\"})),r=q[0],s={aborted:0,responseText:null,responseXML:null,status:0,statusText:\"n/a\",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(b){var c=\"timeout\"===b?\"timeout\":\"aborted\";d(\"aborting upload... \"+c),this.aborted=1;try{r.contentWindow.document.execCommand&&r.contentWindow.document.execCommand(\"Stop\")}catch(e){}q.attr(\"src\",m.iframeSrc),s.error=c,m.error&&m.error.call(m.context,s,c,b),n&&a.event.trigger(\"ajaxError\",[s,m,c]),m.complete&&m.complete.call(m.context,s,c)}},n=m.global,n&&0===a.active++&&a.event.trigger(\"ajaxStart\"),n&&a.event.trigger(\"ajaxSend\",[s,m]),m.beforeSend&&m.beforeSend.call(m.context,s,m)===!1)return m.global&&a.active--,y.reject(),y;if(s.aborted)return y.reject(),y;t=x.clk,t&&(u=t.name,u&&!t.disabled&&(m.extraData=m.extraData||{},m.extraData[u]=t.value,\"image\"==t.type&&(m.extraData[u+\".x\"]=x.clk_x,m.extraData[u+\".y\"]=x.clk_y)));var z=1,A=2,B=a(\"meta[name=csrf-token]\").attr(\"content\"),C=a(\"meta[name=csrf-param]\").attr(\"content\");C&&B&&(m.extraData=m.extraData||{},m.extraData[C]=B),m.forceSync?g():setTimeout(g,10);var D,E,F,G=50,H=a.parseXML||function(a,b){return window.ActiveXObject?(b=new ActiveXObject(\"Microsoft.XMLDOM\"),b.async=\"false\",b.loadXML(a)):b=(new DOMParser).parseFromString(a,\"text/xml\"),b&&b.documentElement&&\"parsererror\"!=b.documentElement.nodeName?b:null},I=a.parseJSON||function(a){return window.eval(\"(\"+a+\")\")},J=function(b,c,d){var e=b.getResponseHeader(\"content-type\")||\"\",f=\"xml\"===c||!c&&e.indexOf(\"xml\")>=0,g=f?b.responseXML:b.responseText;return f&&\"parsererror\"===g.documentElement.nodeName&&a.error&&a.error(\"parsererror\"),d&&d.dataFilter&&(g=d.dataFilter(g,c)),\"string\"==typeof g&&(\"json\"===c||!c&&e.indexOf(\"json\")>=0?g=I(g):(\"script\"===c||!c&&e.indexOf(\"javascript\")>=0)&&a.globalEval(g)),g};return y}if(!this.length)return d(\"ajaxSubmit: skipping submit process - no element selected\"),this;var i,j,k,l=this;\"function\"==typeof b?b={success:b}:void 0===b&&(b={}),i=b.type||this.attr2(\"method\"),j=b.url||this.attr2(\"action\"),k=\"string\"==typeof j?a.trim(j):\"\",k=k||window.location.href||\"\",k&&(k=(k.match(/^([^#]+)/)||[])[1]),b=a.extend(!0,{url:k,success:a.ajaxSettings.success,type:i||a.ajaxSettings.type,iframeSrc:/^https/i.test(window.location.href||\"\")?\"javascript:false\":\"about:blank\"},b);var m={};if(this.trigger(\"form-pre-serialize\",[this,b,m]),m.veto)return d(\"ajaxSubmit: submit vetoed via form-pre-serialize trigger\"),this;if(b.beforeSerialize&&b.beforeSerialize(this,b)===!1)return d(\"ajaxSubmit: submit aborted via beforeSerialize callback\"),this;var n=b.traditional;void 0===n&&(n=a.ajaxSettings.traditional);var o,p=[],q=this.formToArray(b.semantic,p);if(b.data&&(b.extraData=b.data,o=a.param(b.data,n)),b.beforeSubmit&&b.beforeSubmit(q,this,b)===!1)return d(\"ajaxSubmit: submit aborted via beforeSubmit callback\"),this;if(this.trigger(\"form-submit-validate\",[q,this,b,m]),m.veto)return d(\"ajaxSubmit: submit vetoed via form-submit-validate trigger\"),this;var r=a.param(q,n);o&&(r=r?r+\"&\"+o:o),\"GET\"==b.type.toUpperCase()?(b.url+=(b.url.indexOf(\"?\")>=0?\"&\":\"?\")+r,b.data=null):b.data=r;var s=[];if(b.resetForm&&s.push(function(){l.resetForm()}),b.clearForm&&s.push(function(){l.clearForm(b.includeHidden)}),!b.dataType&&b.target){var t=b.success||function(){};s.push(function(c){var d=b.replaceTarget?\"replaceWith\":\"html\";a(b.target)[d](c).each(t,arguments)})}else b.success&&s.push(b.success);if(b.success=function(a,c,d){for(var e=b.context||this,f=0,g=s.length;g>f;f++)s[f].apply(e,[a,c,d||l,l])},b.error){var u=b.error;b.error=function(a,c,d){var e=b.context||this;u.apply(e,[a,c,d,l])}}if(b.complete){var v=b.complete;b.complete=function(a,c){var d=b.context||this;v.apply(d,[a,c,l])}}var w=a(\"input[type=file]:enabled\",this).filter(function(){return\"\"!==a(this).val()}),x=w.length>0,y=\"multipart/form-data\",z=l.attr(\"enctype\")==y||l.attr(\"encoding\")==y,A=e.fileapi&&e.formdata;d(\"fileAPI :\"+A);var B,C=(x||z)&&!A;b.iframe!==!1&&(b.iframe||C)?b.closeKeepAlive?a.get(b.closeKeepAlive,function(){B=h(q)}):B=h(q):B=(x||z)&&A?g(q):a.ajax(b),l.removeData(\"jqxhr\").data(\"jqxhr\",B);for(var D=0;D<p.length;D++)p[D]=null;return this.trigger(\"form-submit-notify\",[this,b]),this},a.fn.ajaxForm=function(e){if(e=e||{},e.delegation=e.delegation&&a.isFunction(a.fn.on),!e.delegation&&0===this.length){var f={s:this.selector,c:this.context};return!a.isReady&&f.s?(d(\"DOM not ready, queuing ajaxForm\"),a(function(){a(f.s,f.c).ajaxForm(e)}),this):(d(\"terminating; zero elements found by selector\"+(a.isReady?\"\":\" (DOM not ready)\")),this)}return e.delegation?(a(document).off(\"submit.form-plugin\",this.selector,b).off(\"click.form-plugin\",this.selector,c).on(\"submit.form-plugin\",this.selector,e,b).on(\"click.form-plugin\",this.selector,e,c),this):this.ajaxFormUnbind().bind(\"submit.form-plugin\",e,b).bind(\"click.form-plugin\",e,c)},a.fn.ajaxFormUnbind=function(){return this.unbind(\"submit.form-plugin click.form-plugin\")},a.fn.formToArray=function(b,c){var d=[];if(0===this.length)return d;var f,g=this[0],h=this.attr(\"id\"),i=b?g.getElementsByTagName(\"*\"):g.elements;if(i&&(i=a(i).get()),h&&(f=a(\":input[form=\"+h+\"]\").get(),f.length&&(i=(i||[]).concat(f))),!i||!i.length)return d;var j,k,l,m,n,o,p;for(j=0,o=i.length;o>j;j++)if(n=i[j],l=n.name,l&&!n.disabled)if(b&&g.clk&&\"image\"==n.type)g.clk==n&&(d.push({name:l,value:a(n).val(),type:n.type}),d.push({name:l+\".x\",value:g.clk_x},{name:l+\".y\",value:g.clk_y}));else if(m=a.fieldValue(n,!0),m&&m.constructor==Array)for(c&&c.push(n),k=0,p=m.length;p>k;k++)d.push({name:l,value:m[k]});else if(e.fileapi&&\"file\"==n.type){c&&c.push(n);var q=n.files;if(q.length)for(k=0;k<q.length;k++)d.push({name:l,value:q[k],type:n.type});else d.push({name:l,value:\"\",type:n.type})}else null!==m&&\"undefined\"!=typeof m&&(c&&c.push(n),d.push({name:l,value:m,type:n.type,required:n.required}));if(!b&&g.clk){var r=a(g.clk),s=r[0];l=s.name,l&&!s.disabled&&\"image\"==s.type&&(d.push({name:l,value:r.val()}),d.push({name:l+\".x\",value:g.clk_x},{name:l+\".y\",value:g.clk_y}))}return d},a.fn.formSerialize=function(b){return a.param(this.formToArray(b))},a.fn.fieldSerialize=function(b){var c=[];return this.each(function(){var d=this.name;if(d){var e=a.fieldValue(this,b);if(e&&e.constructor==Array)for(var f=0,g=e.length;g>f;f++)c.push({name:d,value:e[f]});else null!==e&&\"undefined\"!=typeof e&&c.push({name:this.name,value:e})}}),a.param(c)},a.fn.fieldValue=function(b){for(var c=[],d=0,e=this.length;e>d;d++){var f=this[d],g=a.fieldValue(f,b);null===g||\"undefined\"==typeof g||g.constructor==Array&&!g.length||(g.constructor==Array?a.merge(c,g):c.push(g))}return c},a.fieldValue=function(b,c){var d=b.name,e=b.type,f=b.tagName.toLowerCase();if(void 0===c&&(c=!0),c&&(!d||b.disabled||\"reset\"==e||\"button\"==e||(\"checkbox\"==e||\"radio\"==e)&&!b.checked||(\"submit\"==e||\"image\"==e)&&b.form&&b.form.clk!=b||\"select\"==f&&-1==b.selectedIndex))return null;if(\"select\"==f){var g=b.selectedIndex;if(0>g)return null;for(var h=[],i=b.options,j=\"select-one\"==e,k=j?g+1:i.length,l=j?g:0;k>l;l++){var m=i[l];if(m.selected){var n=m.value;if(n||(n=m.attributes&&m.attributes.value&&!m.attributes.value.specified?m.text:m.value),j)return n;h.push(n)}}return h}return a(b).val()},a.fn.clearForm=function(b){return this.each(function(){a(\"input,select,textarea\",this).clearFields(b)})},a.fn.clearFields=a.fn.clearInputs=function(b){var c=/^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i;return this.each(function(){var d=this.type,e=this.tagName.toLowerCase();c.test(d)||\"textarea\"==e?this.value=\"\":\"checkbox\"==d||\"radio\"==d?this.checked=!1:\"select\"==e?this.selectedIndex=-1:\"file\"==d?/MSIE/.test(navigator.userAgent)?a(this).replaceWith(a(this).clone(!0)):a(this).val(\"\"):b&&(b===!0&&/hidden/.test(d)||\"string\"==typeof b&&a(this).is(b))&&(this.value=\"\")})},a.fn.resetForm=function(){return this.each(function(){(\"function\"==typeof this.reset||\"object\"==typeof this.reset&&!this.reset.nodeType)&&this.reset()})},a.fn.enable=function(a){return void 0===a&&(a=!0),this.each(function(){this.disabled=!a})},a.fn.selected=function(b){return void 0===b&&(b=!0),this.each(function(){var c=this.type;if(\"checkbox\"==c||\"radio\"==c)this.checked=b;else if(\"option\"==this.tagName.toLowerCase()){var d=a(this).parent(\"select\");b&&d[0]&&\"select-one\"==d[0].type&&d.find(\"option\").selected(!1),this.selected=b}})},a.fn.ajaxSubmit.debug=!1});\n\n// PutCursorAtEnd\n(function(e){jQuery.fn.putCursorAtEnd=function(){return this.each(function(){e(this).focus();if(this.setSelectionRange){var t=e(this).val().length*2;this.setSelectionRange(t,t)}else{e(this).val(e(this).val())}})}})(jQuery);"
  },
  {
    "path": "gateway/src/main/resources/static/js/lib/touchscreens.js",
    "content": "/*\n * jQuery.fastClick.js\n *\n * Work around the 300ms delay for the click event in some mobile browsers.\n *\n * @license MIT\n * @author Dave Hulbert (dave1010)\n * @version 1.0.0 2013-01-17\n */\nfunction FastClick(a){function b(a,b){return function(){return a.apply(b,arguments)}}var c;this.trackingClick=!1;this.trackingClickStart=0;this.targetElement=null;this.lastTouchIdentifier=this.touchStartY=this.touchStartX=0;this.touchBoundary=10;this.layer=a;FastClick.notNeeded(a)||(deviceIsAndroid&&(a.addEventListener(\"mouseover\",b(this.onMouse,this),!0),a.addEventListener(\"mousedown\",b(this.onMouse,this),!0),a.addEventListener(\"mouseup\",b(this.onMouse,this),!0)),a.addEventListener(\"click\",b(this.onClick,\nthis),!0),a.addEventListener(\"touchstart\",b(this.onTouchStart,this),!1),a.addEventListener(\"touchmove\",b(this.onTouchMove,this),!1),a.addEventListener(\"touchend\",b(this.onTouchEnd,this),!1),a.addEventListener(\"touchcancel\",b(this.onTouchCancel,this),!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,e){var f=Node.prototype.removeEventListener;\"click\"===b?f.call(a,b,c.hijacked||c,e):f.call(a,b,c,e)},a.addEventListener=function(b,c,e){var f=Node.prototype.addEventListener;\n\"click\"===b?f.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),e):f.call(a,b,c,e)}),\"function\"===typeof a.onclick&&(c=a.onclick,a.addEventListener(\"click\",function(a){c(a)},!1),a.onclick=null))}var deviceIsAndroid=0<navigator.userAgent.indexOf(\"Android\"),deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),deviceIsIOS4=deviceIsIOS&&/OS 4_\\d(_\\d)?/.test(navigator.userAgent),deviceIsIOSWithBadTarget=deviceIsIOS&&/OS ([6-9]|\\d{2})_\\d/.test(navigator.userAgent);\nFastClick.prototype.needsClick=function(a){switch(a.nodeName.toLowerCase()){case \"button\":case \"select\":case \"textarea\":if(a.disabled)return!0;break;case \"input\":if(deviceIsIOS&&\"file\"===a.type||a.disabled)return!0;break;case \"label\":case \"video\":return!0}return/\\bneedsclick\\b/.test(a.className)};\nFastClick.prototype.needsFocus=function(a){switch(a.nodeName.toLowerCase()){case \"textarea\":return!0;case \"select\":return!deviceIsAndroid;case \"input\":switch(a.type){case \"button\":case \"checkbox\":case \"file\":case \"image\":case \"radio\":case \"submit\":return!1}return!a.disabled&&!a.readOnly;default:return/\\bneedsfocus\\b/.test(a.className)}};\nFastClick.prototype.sendClick=function(a,b){var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur();d=b.changedTouches[0];c=document.createEvent(\"MouseEvents\");c.initMouseEvent(this.determineEventType(a),!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null);c.forwardedTouchEvent=!0;a.dispatchEvent(c)};FastClick.prototype.determineEventType=function(a){return deviceIsAndroid&&\"select\"===a.tagName.toLowerCase()?\"mousedown\":\"click\"};\nFastClick.prototype.focus=function(a){var b;deviceIsIOS&&a.setSelectionRange&&0!==a.type.indexOf(\"date\")&&\"time\"!==a.type?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()};FastClick.prototype.updateScrollParent=function(a){var b,c;b=a.fastClickScrollParent;if(!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c;a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)};\nFastClick.prototype.getTargetElementFromEventTarget=function(a){return a.nodeType===Node.TEXT_NODE?a.parentNode:a};\nFastClick.prototype.onTouchStart=function(a){var b,c,d;if(1<a.targetTouches.length)return!0;b=this.getTargetElementFromEventTarget(a.target);c=a.targetTouches[0];if(deviceIsIOS){d=window.getSelection();if(d.rangeCount&&!d.isCollapsed)return!0;if(!deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier;this.updateScrollParent(b)}}this.trackingClick=!0;this.trackingClickStart=a.timeStamp;this.targetElement=b;this.touchStartX=c.pageX;\nthis.touchStartY=c.pageY;200>a.timeStamp-this.lastClickTime&&a.preventDefault();return!0};FastClick.prototype.touchHasMoved=function(a){a=a.changedTouches[0];var b=this.touchBoundary;return Math.abs(a.pageX-this.touchStartX)>b||Math.abs(a.pageY-this.touchStartY)>b?!0:!1};FastClick.prototype.onTouchMove=function(a){if(!this.trackingClick)return!0;if(this.targetElement!==this.getTargetElementFromEventTarget(a.target)||this.touchHasMoved(a))this.trackingClick=!1,this.targetElement=null;return!0};\nFastClick.prototype.findControl=function(a){return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector(\"button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea\")};\nFastClick.prototype.onTouchEnd=function(a){var b,c,d=this.targetElement;if(!this.trackingClick)return!0;if(200>a.timeStamp-this.lastClickTime)return this.cancelNextClick=!0;this.cancelNextClick=!1;this.lastClickTime=a.timeStamp;b=this.trackingClickStart;this.trackingClick=!1;this.trackingClickStart=0;deviceIsIOSWithBadTarget&&(c=a.changedTouches[0],d=document.elementFromPoint(c.pageX-window.pageXOffset,c.pageY-window.pageYOffset)||d,d.fastClickScrollParent=this.targetElement.fastClickScrollParent);\nc=d.tagName.toLowerCase();if(\"label\"===c){if(b=this.findControl(d)){this.focus(d);if(deviceIsAndroid)return!1;d=b}}else if(this.needsFocus(d)){if(100<a.timeStamp-b||deviceIsIOS&&window.top!==window&&\"input\"===c)return this.targetElement=null,!1;this.focus(d);this.sendClick(d,a);deviceIsIOS4&&\"select\"===c||(this.targetElement=null,a.preventDefault());return!1}if(deviceIsIOS&&!deviceIsIOS4&&(b=d.fastClickScrollParent)&&b.fastClickLastScrollTop!==b.scrollTop)return!0;this.needsClick(d)||(a.preventDefault(),\nthis.sendClick(d,a));return!1};FastClick.prototype.onTouchCancel=function(){this.trackingClick=!1;this.targetElement=null};FastClick.prototype.onMouse=function(a){return this.targetElement&&!a.forwardedTouchEvent&&a.cancelable?!this.needsClick(this.targetElement)||this.cancelNextClick?(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1):!0:!0};\nFastClick.prototype.onClick=function(a){if(this.trackingClick)return this.targetElement=null,this.trackingClick=!1,!0;if(\"submit\"===a.target.type&&0===a.detail)return!0;(a=this.onMouse(a))||(this.targetElement=null);return a};\nFastClick.prototype.destroy=function(){var a=this.layer;deviceIsAndroid&&(a.removeEventListener(\"mouseover\",this.onMouse,!0),a.removeEventListener(\"mousedown\",this.onMouse,!0),a.removeEventListener(\"mouseup\",this.onMouse,!0));a.removeEventListener(\"click\",this.onClick,!0);a.removeEventListener(\"touchstart\",this.onTouchStart,!1);a.removeEventListener(\"touchmove\",this.onTouchMove,!1);a.removeEventListener(\"touchend\",this.onTouchEnd,!1);a.removeEventListener(\"touchcancel\",this.onTouchCancel,!1)};\nFastClick.notNeeded=function(a){var b,c;if(\"undefined\"===typeof window.ontouchstart)return!0;if(c=+(/Chrome\\/([0-9]+)/.exec(navigator.userAgent)||[,0])[1])if(deviceIsAndroid){if((b=document.querySelector(\"meta[name=viewport]\"))&&(-1!==b.content.indexOf(\"user-scalable=no\")||31<c&&window.innerWidth<=window.screen.width))return!0}else return!0;return\"none\"===a.style.msTouchAction?!0:!1};FastClick.attach=function(a){return new FastClick(a)};\n\"undefined\"!==typeof define&&define.amd?define(function(){return FastClick}):\"undefined\"!==typeof module&&module.exports?(module.exports=FastClick.attach,module.exports.FastClick=FastClick):window.FastClick=FastClick;\n\n/*\n* @fileOverview TouchSwipe - jQuery Plugin\n* @version 1.6.5\n*\n* @author Matt Bryson http://www.github.com/mattbryson\n* @see https://github.com/mattbryson/TouchSwipe-Jquery-Plugin\n* @see http://labs.skinkers.com/touchSwipe/\n* @see http://plugins.jquery.com/project/touchSwipe\n*\n* Copyright (c) 2010 Matt Bryson\n* Dual licensed under the MIT or GPL Version 2 licenses.\n*/\n(function(a){if(typeof define===\"function\"&&define.amd&&define.amd.jQuery){define([\"jquery\"],a)}else{a(jQuery)}}(function(e){var o=\"left\",n=\"right\",d=\"up\",v=\"down\",c=\"in\",w=\"out\",l=\"none\",r=\"auto\",k=\"swipe\",s=\"pinch\",x=\"tap\",i=\"doubletap\",b=\"longtap\",A=\"horizontal\",t=\"vertical\",h=\"all\",q=10,f=\"start\",j=\"move\",g=\"end\",p=\"cancel\",a=\"ontouchstart\" in window,y=\"TouchSwipe\";var m={fingers:1,threshold:75,cancelThreshold:null,pinchThreshold:20,maxTimeThreshold:null,fingerReleaseThreshold:250,longTapThreshold:500,doubleTapThreshold:200,swipe:null,swipeLeft:null,swipeRight:null,swipeUp:null,swipeDown:null,swipeStatus:null,pinchIn:null,pinchOut:null,pinchStatus:null,click:null,tap:null,doubleTap:null,longTap:null,triggerOnTouchEnd:true,triggerOnTouchLeave:false,allowPageScroll:\"auto\",fallbackToMouseEvents:true,excludedElements:\"label, button, input, select, textarea, a, .noSwipe\"};e.fn.swipe=function(D){var C=e(this),B=C.data(y);if(B&&typeof D===\"string\"){if(B[D]){return B[D].apply(this,Array.prototype.slice.call(arguments,1))}else{e.error(\"Method \"+D+\" does not exist on jQuery.swipe\")}}else{if(!B&&(typeof D===\"object\"||!D)){return u.apply(this,arguments)}}return C};e.fn.swipe.defaults=m;e.fn.swipe.phases={PHASE_START:f,PHASE_MOVE:j,PHASE_END:g,PHASE_CANCEL:p};e.fn.swipe.directions={LEFT:o,RIGHT:n,UP:d,DOWN:v,IN:c,OUT:w};e.fn.swipe.pageScroll={NONE:l,HORIZONTAL:A,VERTICAL:t,AUTO:r};e.fn.swipe.fingers={ONE:1,TWO:2,THREE:3,ALL:h};function u(B){if(B&&(B.allowPageScroll===undefined&&(B.swipe!==undefined||B.swipeStatus!==undefined))){B.allowPageScroll=l}if(B.click!==undefined&&B.tap===undefined){B.tap=B.click}if(!B){B={}}B=e.extend({},e.fn.swipe.defaults,B);return this.each(function(){var D=e(this);var C=D.data(y);if(!C){C=new z(this,B);D.data(y,C)}})}function z(a0,aq){var av=(a||!aq.fallbackToMouseEvents),G=av?\"touchstart\":\"mousedown\",au=av?\"touchmove\":\"mousemove\",R=av?\"touchend\":\"mouseup\",P=av?null:\"mouseleave\",az=\"touchcancel\";var ac=0,aL=null,Y=0,aX=0,aV=0,D=1,am=0,aF=0,J=null;var aN=e(a0);var W=\"start\";var T=0;var aM=null;var Q=0,aY=0,a1=0,aa=0,K=0;var aS=null;try{aN.bind(G,aJ);aN.bind(az,a5)}catch(ag){e.error(\"events not supported \"+G+\",\"+az+\" on jQuery.swipe\")}this.enable=function(){aN.bind(G,aJ);aN.bind(az,a5);return aN};this.disable=function(){aG();return aN};this.destroy=function(){aG();aN.data(y,null);return aN};this.option=function(a8,a7){if(aq[a8]!==undefined){if(a7===undefined){return aq[a8]}else{aq[a8]=a7}}else{e.error(\"Option \"+a8+\" does not exist on jQuery.swipe.options\")}return null};function aJ(a9){if(ax()){return}if(e(a9.target).closest(aq.excludedElements,aN).length>0){return}var ba=a9.originalEvent?a9.originalEvent:a9;var a8,a7=a?ba.touches[0]:ba;W=f;if(a){T=ba.touches.length}else{a9.preventDefault()}ac=0;aL=null;aF=null;Y=0;aX=0;aV=0;D=1;am=0;aM=af();J=X();O();if(!a||(T===aq.fingers||aq.fingers===h)||aT()){ae(0,a7);Q=ao();if(T==2){ae(1,ba.touches[1]);aX=aV=ap(aM[0].start,aM[1].start)}if(aq.swipeStatus||aq.pinchStatus){a8=L(ba,W)}}else{a8=false}if(a8===false){W=p;L(ba,W);return a8}else{ak(true)}return null}function aZ(ba){var bd=ba.originalEvent?ba.originalEvent:ba;if(W===g||W===p||ai()){return}var a9,a8=a?bd.touches[0]:bd;var bb=aD(a8);aY=ao();if(a){T=bd.touches.length}W=j;if(T==2){if(aX==0){ae(1,bd.touches[1]);aX=aV=ap(aM[0].start,aM[1].start)}else{aD(bd.touches[1]);aV=ap(aM[0].end,aM[1].end);aF=an(aM[0].end,aM[1].end)}D=a3(aX,aV);am=Math.abs(aX-aV)}if((T===aq.fingers||aq.fingers===h)||!a||aT()){aL=aH(bb.start,bb.end);ah(ba,aL);ac=aO(bb.start,bb.end);Y=aI();aE(aL,ac);if(aq.swipeStatus||aq.pinchStatus){a9=L(bd,W)}if(!aq.triggerOnTouchEnd||aq.triggerOnTouchLeave){var a7=true;if(aq.triggerOnTouchLeave){var bc=aU(this);a7=B(bb.end,bc)}if(!aq.triggerOnTouchEnd&&a7){W=ay(j)}else{if(aq.triggerOnTouchLeave&&!a7){W=ay(g)}}if(W==p||W==g){L(bd,W)}}}else{W=p;L(bd,W)}if(a9===false){W=p;L(bd,W)}}function I(a7){var a8=a7.originalEvent;if(a){if(a8.touches.length>0){C();return true}}if(ai()){T=aa}a7.preventDefault();aY=ao();Y=aI();if(a6()){W=p;L(a8,W)}else{if(aq.triggerOnTouchEnd||(aq.triggerOnTouchEnd==false&&W===j)){W=g;L(a8,W)}else{if(!aq.triggerOnTouchEnd&&a2()){W=g;aB(a8,W,x)}else{if(W===j){W=p;L(a8,W)}}}}ak(false);return null}function a5(){T=0;aY=0;Q=0;aX=0;aV=0;D=1;O();ak(false)}function H(a7){var a8=a7.originalEvent;if(aq.triggerOnTouchLeave){W=ay(g);L(a8,W)}}function aG(){aN.unbind(G,aJ);aN.unbind(az,a5);aN.unbind(au,aZ);aN.unbind(R,I);if(P){aN.unbind(P,H)}ak(false)}function ay(bb){var ba=bb;var a9=aw();var a8=aj();var a7=a6();if(!a9||a7){ba=p}else{if(a8&&bb==j&&(!aq.triggerOnTouchEnd||aq.triggerOnTouchLeave)){ba=g}else{if(!a8&&bb==g&&aq.triggerOnTouchLeave){ba=p}}}return ba}function L(a9,a7){var a8=undefined;if(F()||S()){a8=aB(a9,a7,k)}else{if((M()||aT())&&a8!==false){a8=aB(a9,a7,s)}}if(aC()&&a8!==false){a8=aB(a9,a7,i)}else{if(al()&&a8!==false){a8=aB(a9,a7,b)}else{if(ad()&&a8!==false){a8=aB(a9,a7,x)}}}if(a7===p){a5(a9)}if(a7===g){if(a){if(a9.touches.length==0){a5(a9)}}else{a5(a9)}}return a8}function aB(ba,a7,a9){var a8=undefined;if(a9==k){aN.trigger(\"swipeStatus\",[a7,aL||null,ac||0,Y||0,T]);if(aq.swipeStatus){a8=aq.swipeStatus.call(aN,ba,a7,aL||null,ac||0,Y||0,T);if(a8===false){return false}}if(a7==g&&aR()){aN.trigger(\"swipe\",[aL,ac,Y,T]);if(aq.swipe){a8=aq.swipe.call(aN,ba,aL,ac,Y,T);if(a8===false){return false}}switch(aL){case o:aN.trigger(\"swipeLeft\",[aL,ac,Y,T]);if(aq.swipeLeft){a8=aq.swipeLeft.call(aN,ba,aL,ac,Y,T)}break;case n:aN.trigger(\"swipeRight\",[aL,ac,Y,T]);if(aq.swipeRight){a8=aq.swipeRight.call(aN,ba,aL,ac,Y,T)}break;case d:aN.trigger(\"swipeUp\",[aL,ac,Y,T]);if(aq.swipeUp){a8=aq.swipeUp.call(aN,ba,aL,ac,Y,T)}break;case v:aN.trigger(\"swipeDown\",[aL,ac,Y,T]);if(aq.swipeDown){a8=aq.swipeDown.call(aN,ba,aL,ac,Y,T)}break}}}if(a9==s){aN.trigger(\"pinchStatus\",[a7,aF||null,am||0,Y||0,T,D]);if(aq.pinchStatus){a8=aq.pinchStatus.call(aN,ba,a7,aF||null,am||0,Y||0,T,D);if(a8===false){return false}}if(a7==g&&a4()){switch(aF){case c:aN.trigger(\"pinchIn\",[aF||null,am||0,Y||0,T,D]);if(aq.pinchIn){a8=aq.pinchIn.call(aN,ba,aF||null,am||0,Y||0,T,D)}break;case w:aN.trigger(\"pinchOut\",[aF||null,am||0,Y||0,T,D]);if(aq.pinchOut){a8=aq.pinchOut.call(aN,ba,aF||null,am||0,Y||0,T,D)}break}}}if(a9==x){if(a7===p||a7===g){clearTimeout(aS);if(V()&&!E()){K=ao();aS=setTimeout(e.proxy(function(){K=null;aN.trigger(\"tap\",[ba.target]);if(aq.tap){a8=aq.tap.call(aN,ba,ba.target)}},this),aq.doubleTapThreshold)}else{K=null;aN.trigger(\"tap\",[ba.target]);if(aq.tap){a8=aq.tap.call(aN,ba,ba.target)}}}}else{if(a9==i){if(a7===p||a7===g){clearTimeout(aS);K=null;aN.trigger(\"doubletap\",[ba.target]);if(aq.doubleTap){a8=aq.doubleTap.call(aN,ba,ba.target)}}}else{if(a9==b){if(a7===p||a7===g){clearTimeout(aS);K=null;aN.trigger(\"longtap\",[ba.target]);if(aq.longTap){a8=aq.longTap.call(aN,ba,ba.target)}}}}}return a8}function aj(){var a7=true;if(aq.threshold!==null){a7=ac>=aq.threshold}return a7}function a6(){var a7=false;if(aq.cancelThreshold!==null&&aL!==null){a7=(aP(aL)-ac)>=aq.cancelThreshold}return a7}function ab(){if(aq.pinchThreshold!==null){return am>=aq.pinchThreshold}return true}function aw(){var a7;if(aq.maxTimeThreshold){if(Y>=aq.maxTimeThreshold){a7=false}else{a7=true}}else{a7=true}return a7}function ah(a7,a8){if(aq.allowPageScroll===l||aT()){a7.preventDefault()}else{var a9=aq.allowPageScroll===r;switch(a8){case o:if((aq.swipeLeft&&a9)||(!a9&&aq.allowPageScroll!=A)){a7.preventDefault()}break;case n:if((aq.swipeRight&&a9)||(!a9&&aq.allowPageScroll!=A)){a7.preventDefault()}break;case d:if((aq.swipeUp&&a9)||(!a9&&aq.allowPageScroll!=t)){a7.preventDefault()}break;case v:if((aq.swipeDown&&a9)||(!a9&&aq.allowPageScroll!=t)){a7.preventDefault()}break}}}function a4(){var a8=aK();var a7=U();var a9=ab();return a8&&a7&&a9}function aT(){return !!(aq.pinchStatus||aq.pinchIn||aq.pinchOut)}function M(){return !!(a4()&&aT())}function aR(){var ba=aw();var bc=aj();var a9=aK();var a7=U();var a8=a6();var bb=!a8&&a7&&a9&&bc&&ba;return bb}function S(){return !!(aq.swipe||aq.swipeStatus||aq.swipeLeft||aq.swipeRight||aq.swipeUp||aq.swipeDown)}function F(){return !!(aR()&&S())}function aK(){return((T===aq.fingers||aq.fingers===h)||!a)}function U(){return aM[0].end.x!==0}function a2(){return !!(aq.tap)}function V(){return !!(aq.doubleTap)}function aQ(){return !!(aq.longTap)}function N(){if(K==null){return false}var a7=ao();return(V()&&((a7-K)<=aq.doubleTapThreshold))}function E(){return N()}function at(){return((T===1||!a)&&(isNaN(ac)||ac===0))}function aW(){return((Y>aq.longTapThreshold)&&(ac<q))}function ad(){return !!(at()&&a2())}function aC(){return !!(N()&&V())}function al(){return !!(aW()&&aQ())}function C(){a1=ao();aa=event.touches.length+1}function O(){a1=0;aa=0}function ai(){var a7=false;if(a1){var a8=ao()-a1;if(a8<=aq.fingerReleaseThreshold){a7=true}}return a7}function ax(){return !!(aN.data(y+\"_intouch\")===true)}function ak(a7){if(a7===true){aN.bind(au,aZ);aN.bind(R,I);if(P){aN.bind(P,H)}}else{aN.unbind(au,aZ,false);aN.unbind(R,I,false);if(P){aN.unbind(P,H,false)}}aN.data(y+\"_intouch\",a7===true)}function ae(a8,a7){var a9=a7.identifier!==undefined?a7.identifier:0;aM[a8].identifier=a9;aM[a8].start.x=aM[a8].end.x=a7.pageX||a7.clientX;aM[a8].start.y=aM[a8].end.y=a7.pageY||a7.clientY;return aM[a8]}function aD(a7){var a9=a7.identifier!==undefined?a7.identifier:0;var a8=Z(a9);a8.end.x=a7.pageX||a7.clientX;a8.end.y=a7.pageY||a7.clientY;return a8}function Z(a8){for(var a7=0;a7<aM.length;a7++){if(aM[a7].identifier==a8){return aM[a7]}}}function af(){var a7=[];for(var a8=0;a8<=5;a8++){a7.push({start:{x:0,y:0},end:{x:0,y:0},identifier:0})}return a7}function aE(a7,a8){a8=Math.max(a8,aP(a7));J[a7].distance=a8}function aP(a7){if(J[a7]){return J[a7].distance}return undefined}function X(){var a7={};a7[o]=ar(o);a7[n]=ar(n);a7[d]=ar(d);a7[v]=ar(v);return a7}function ar(a7){return{direction:a7,distance:0}}function aI(){return aY-Q}function ap(ba,a9){var a8=Math.abs(ba.x-a9.x);var a7=Math.abs(ba.y-a9.y);return Math.round(Math.sqrt(a8*a8+a7*a7))}function a3(a7,a8){var a9=(a8/a7)*1;return a9.toFixed(2)}function an(){if(D<1){return w}else{return c}}function aO(a8,a7){return Math.round(Math.sqrt(Math.pow(a7.x-a8.x,2)+Math.pow(a7.y-a8.y,2)))}function aA(ba,a8){var a7=ba.x-a8.x;var bc=a8.y-ba.y;var a9=Math.atan2(bc,a7);var bb=Math.round(a9*180/Math.PI);if(bb<0){bb=360-Math.abs(bb)}return bb}function aH(a8,a7){var a9=aA(a8,a7);if((a9<=45)&&(a9>=0)){return o}else{if((a9<=360)&&(a9>=315)){return o}else{if((a9>=135)&&(a9<=225)){return n}else{if((a9>45)&&(a9<135)){return v}else{return d}}}}}function ao(){var a7=new Date();return a7.getTime()}function aU(a7){a7=e(a7);var a9=a7.offset();var a8={left:a9.left,right:a9.left+a7.outerWidth(),top:a9.top,bottom:a9.top+a7.outerHeight()};return a8}function B(a7,a8){return(a7.x>a8.left&&a7.x<a8.right&&a7.y>a8.top&&a7.y<a8.bottom)}}}));"
  },
  {
    "path": "gateway/src/main/resources/static/js/login.js",
    "content": "/**\r * Registration form\r */\r\r$('#signup').submit(function(e) {\r\r    e.preventDefault();\r\r    var username = $(\"input[id='backloginform']\").val();\r    var password = $(\"input[id='backpasswordform']\").val();\r\r    if (username.length < 3 || password.length < 6) {\r        alert(\"Username must be at least 3 characters and password - at least 6. Be tricky!\");\r        return;\r    }\r\r\tif (username && password) {\r        $.ajax({\r            url: 'accounts/',\r            datatype: 'json',\r            type: \"post\",\r            contentType: \"application/json\",\r            data: JSON.stringify({\r                username: username,\r                password: password\r            }),\r            success: function (data) {\r\r                requestOauthToken(username, password);\r                initAccount(getCurrentAccount());\r\r                $('#registrationforms, .fliptext, #createaccount').fadeOut(300);\r                $('#mailform').fadeIn(500);\r                setTimeout(function(){ $(\"#backmailform\").focus() }, 10);\r            },\r            error: function (xhr, ajaxOptions, thrownError) {\r                if (xhr.status == 400) {\r                    alert(\"Sorry, account with the same name already exists.\");\r                } else {\r                    alert(\"An error during account creation. Please, try again.\");\r                }\r            }\r        });\r\r\t} else {\r        alert(\"Please, fill all the fields.\");\r    }\r});\r\r/**\r * E-mail form\r */\r\r$('#mail').submit(function(e) {\r\r\te.preventDefault();\r\r    var email = $(\"input[name='usermail']\").val();\r\r\tif (email) {\r        $.ajax({\r            url: 'notifications/recipients/current',\r            datatype: 'json',\r            type: 'put',\r            contentType: \"application/json\",\r            data: JSON.stringify({\r                email: email,\r                scheduledNotifications: {\r                    \"REMIND\": {\r                        \"active\": true,\r                        \"frequency\": \"MONTHLY\"\r                    }\r                }\r            }),\r            headers: {'Authorization': 'Bearer ' + getOauthTokenFromStorage()},\r            async: true,\r            success: function () {\r                setTimeout(initGreetingPage, 200);\r                setTimeout(function(){ $('#backmailform').val(''); $(\"#lastlogo\").show(); }, 300);\r            },\r            error: function (xhr, ajaxOptions, thrownError) {\r                if (xhr.status == 400) {\r                    alert(\"Sorry, it seems your email address is invalid.\");\r                } else {\r                    alert(\"An error during saving notifications options\");\r                }\r            }\r        });\r\t}\r});\r\r/**\r * Login\r */\r\rfunction login() {\r\r\t$(\"#piggy\").toggleClass(\"loadingspin\");\r\t$(\"#secondenter\").hide();\r\t$(\"#preloader, #lastlogo\").show();\r\r    var username = $(\"input[id='frontloginform']\").val();\r    var password = $(\"input[id='frontpasswordform']\").val();\r\r    if (requestOauthToken(username, password)) {\r\r        initAccount(getCurrentAccount());\r\r        var userAvatar = $(\"<img />\").attr(\"src\",\"images/userpic.jpg\");\r        $(userAvatar).load(function() {\r            setTimeout(initGreetingPage, 500);\r        });\r    } else {\r        $(\"#preloader, #enter, #secondenter\").hide();\r        flipForm();\r        $('.frontforms').val('');\r        $(\"#frontloginform\").focus();\r        alert(\"Something went wrong. Please, check your credentials\");\r    }\r}\r\r/**\r * Logout\r */\r\rfunction logout() {\r    removeOauthTokenFromStorage();\r    location.reload();\r}\r\r/**\r * Demo\r */\r\r$(\".demobutton\").bind(\"click\", function(){\r    $.ajax({\r        url: 'accounts/demo',\r        datatype: 'json',\r        type: 'get',\r        async: false,\r        success: function (data) {\r\r            global.savePermit = false;\r            initAccount(data);\r\r            var userAvatar = $(\"<img />\").attr(\"src\",\"images/userpic.jpg\");\r            $(userAvatar).load(function() {\r                setTimeout(initGreetingPage, 500);\r            });\r        },\r        error: function () {\r            alert(\"Something went wrong. Please, try again\");\r        }\r    });\r});\r\r$(\"#skipmail\").bind(\"click\", function(){\r    $(\"#lastlogo\").show();\r    setTimeout(initGreetingPage, 300);\r});\r\r/**\r * Login form effects\r */\r\rfunction initialShaking(){\r\tautoShake();\r\tsetTimeout(autoShake, 1900);\r}\r\rfunction autoShake() {\r\t$(\"#piggy\").toggleClass(\"auto-shake\");\r}\r\rfunction OnHoverShaking() {\r\thoverShake();\r\tsetTimeout(hoverShake, 1700);\r}\r\rfunction hoverShake() {\r\t$(\"#piggy\").toggleClass(\"hover-shake\");\r}\r\rfunction toggleInfo() {\r\t$(\"#infopage\").toggle();\r}\r\rfunction flipForm() {\r\t$(\"#cube\").toggleClass(\"flippedform\");\r\t$(\"#frontpasswordform\").focus();\r}\r\r$(\"#piggy\").on(\"click mouseover\", function(){\r\tif ($(this).hasClass(\"skakelogo\") === false && $(this).hasClass(\"hover-shake\") === false) {\r\t\tOnHoverShaking();\r\t}\r});\r\r$(\".fliptext\").bind(\"click\", function(){\r\tsetTimeout( function() { $(\"#plusavatar\").addClass(\"avataranimation\"); } , 1000);\r\t$(\"#flipper\").toggleClass(\"flippedcard\");\r});\r\r$(\".flipinfo\").on(\"click\", function() {\r\t$(\"#flipper\").toggleClass(\"flippedcardinfo\");\r\ttoggleInfo();\r});\r\r$(\".frominfo, #infotitle, #infosubtitle\").on(\"click\", function() {\r\t$(\"#flipper\").toggleClass(\"flippedcardinfo\");\r\tsetTimeout(toggleInfo, 400);\r});\r\r$(\"#enter\").on(\"click\", function() {flipForm()});\r$(\"#secondenter\").on(\"click\", function() {login()});\r\r$(\"#frontloginform\").keyup(function (e) {\r\tif( $(this).val().length >= 3 ) {\r\t\t$(\"#enter\").show();\r\t\tif (e.which == 13) {\r\t\t\tflipForm();\r\t\t\t$(\"#enter\").hide();\r\t\t}\r\t\treturn;\r\t} else {\r\t\t$(\"#enter\").hide();\r\t}\r});\r\r$(\"#frontpasswordform\").keyup(function(e) {\r\tif ( $(this).val().length >= 6) {\r\t\t$(\"#secondenter\").show();\r\t\tif(e.which == 13) {\r\t\t\t$(this).blur();\r            login();\r\t\t}\r\t\treturn;\r\t} else {\r\t\t$(\"#secondenter\").hide();\r\t}\r});"
  },
  {
    "path": "gateway/src/main/resources/static/js/main.js",
    "content": "var user = {},\n    savings = {},\n    incomes = {},\n    expenses = {};\n\nfunction initAccount(account) {\n    user = new User(account.name, account.lastSeen, account.saving.currency, account.note);\n    savings = new Savings (account.saving.amount, account.saving.deposit, account.saving.capitalization, account.saving.interest);\n\n    if (account.incomes) {\n        for (i = 0; i < account.incomes.length; i++) {\n            AddIncome(i + 1, account.incomes[i].title, account.incomes[i].icon, account.incomes[i].currency, account.incomes[i].period, account.incomes[i].amount);\n        }\n    }\n\n    if (account.expenses) {\n        for (j = 0; j < account.expenses.length; j++) {\n            AddExpense(j + 1, account.expenses[j].title, account.expenses[j].icon, account.expenses[j].currency, account.expenses[j].period, account.expenses[j].amount);\n        }\n    }\n}\n\nfunction User(username, lastSeen, currency, note) {\n\n    var seen = new Date(lastSeen);\n\n    this.login = username;\n    this.lastSeen = (seen.getMonth() + 1) + \"/\" + seen.getDate()  + \"/\" + seen.getFullYear();;\n    this.checkedCurr = currency;\n    this.lastCurr = currency;\n    this.checkedPercent = 1;\n    this.notes = note;\n}\n\nfunction Savings(money, deposit, capitalization, interest) {\n    this.freeMoney = money;\n    this.deposit = deposit;\n    this.capitalization = capitalization;\n    this.percent = interest;\n}\n\nfunction AddIncome(income_id, title, icon, currency, period, amount){\n    incomes[income_id] = {\n        income_id: income_id,\n        title: title,\n        icon: icon,\n        currency: currency,\n        period: period,\n        amount: amount.toString()\n    }\n}\n\nfunction AddExpense(expense_id, title, icon, currency, period, amount){\n    expenses[expense_id] = {\n        expense_id: expense_id,\n        title: title,\n        icon: icon,\n        currency: currency,\n        period: period,\n        amount: amount.toString()\n    }\n}\n\n// Log out button\n$('#minus').click(function() {\n    logout();\n});\n\nvar entityMap = {\n    \"&\": \"&amp;\",\n    \"<\": \"&lt;\",\n    \">\": \"&gt;\",\n    '\"': '&quot;',\n    \"'\": '&#39;',\n    \"/\": '&#x2F;'\n};\n\nfunction escape(string) {\n    return String(string).replace(/[&<>\"'\\/]/g, function (s) {\n        return entityMap[s];\n    });\n}\n\nfunction sanitize(obj) {\n\n    $.each( obj, function( index, item ){\n        $.each( item, function( key, value ){\n            obj[index][key] = escape(value);\n        });\n    });\n\n    return obj;\n}\n\nfunction initGreetingPage() {\n\n    $(\"#preloader, #lastlogo\").show();\n    $(\"#loginpage\").fadeOut(50);\n    $(\".avatar\").css({\"background\": \"url(images/userpic.jpg) center center no-repeat\", \"background-size\": \"100% 100%\"});\n    $(\"#logo_greeting\").fadeIn(0, function() {\n        $(\"#centerbox\").show(0,function() {\n            setTimeout( function() { showGreetingUnits() } , 300)\n        });\n    });\n\n    $(\".plus\").click(function() {\n        setTimeout(initSettingsPage, 200);\n    });\n\n    // Init statisctic page first time\n    expensesSumMonth = 0; incomesSumMonth = 0;\n    $(\"#circle-select-1, #circle-select-2, #circle-select-3\").empty();\n    getConverted(incomes);\n    getConverted(expenses);\n\n    initStatisticPage();\n    setTimeout(function() { initSavingsCircles(user.checkedPercent, 0.2, savings.freeMoney, savings.freeMoney, 700) }, 200);\n    initSavingsSlider();\n    $(\"#savings-slider\").data({\"checkedPercent\": user.checkedPercent});\n\n    $(\"#lefttitle\").prepend(escape(user.login));\n    $(\"#righttitle\").append(user.lastSeen);\n\n    // Fill data on settings page beforehand\n    addSavings();\n    addItems();\n    addNotes();\n}\n\nfunction initSettingsPage() {\n    switch (user.checkedCurr) {\n        case \"RUB\": $(\"#rublesign\").css({\"background-position\": \"-150px 0\"});\n            break;\n        case \"EUR\": $(\"#rublesign\").css({\"background-position\": \"-386px 0\"});\n            break;\n        case \"USD\": $(\"#rublesign\").css({\"background-position\": \"-354px 0\"});\n            break;\n    }\n\n    $(\"#settings_hat\").show();\n    $(\"#avatarcontainer\").addClass(\"reverse\");\n    $(\"#overlay, .modal-content\").show(); // Fix transition and visibility: hidden Chrome issue\n    $(\".modalvalue\").autoNumeric(\"init\");\n    $(\".modalcurrency, .modalperiod, .selectcircles\").selectbox();\n    hideGreetingUnits();\n    setTimeout( function() { $(\"#logo_greeting\").hide(); $(\"#logo_settings\").show(); $(\"#avatarcontainer\").fadeOut(100); drawChartLine(91); } , 290);\n    setTimeout( function() { $(\"#settingspage\").fadeIn(100); } , 700);\n    setTimeout( function() { $(\"#expenseslider\").fadeIn(100); $(\"#bubble\").fadeIn(500); } , 800);\n}\nfunction greetingPageAgain() {\n    $(\"#avatarcontainer\").removeClass().addClass(\"forward plus\");\n    $(\".avatar\").css({\"background\": \"url(images/userpic.jpg) center center no-repeat\", \"background-size\": \"100% 100%\"});\n    $(\"#logo_greeting\").fadeIn(0, function() {\n        $(\"#centerbox, #avatarcontainer\").show(0,function() {\n            setTimeout( function() { showGreetingUnits(); } , 300);\n        });\n    });\n\n    /* Initiate settings page on press plus or enter\n     $(document).on(\"keyup\", function (e) {\n     if (e.which == 13) {\n     setTimeout(initSettingsPage, 400);\n     }\n     });\n     */\n\n    $(\".plus\").click(function() {\n        setTimeout(initSettingsPage, 200);\n    });\n    $(\"#righttitle, #lefttitle\").empty();\n    $(\"#righttitle\").append('<span class=\"bluetext\">last seen: </span>' + user.lastSeen);\n    $(\"#lefttitle\").append(escape(user.login) + '<span class=\"bluetext\"> metrics</span>');\n}\nfunction showGreetingUnits() {\n    $(\"#lefttitle\").fadeIn(500);\n    $(\"#righttitle\").fadeIn(500);\n    $(\"#bottombuttons\").fadeIn(500);\n}\nfunction hideGreetingUnits() {\n    $(\"#lefttitle\").fadeOut(100);\n    $(\"#righttitle\").fadeOut(100);\n    $(\"#bottombuttons\").fadeOut(100);\n}\n\n// Filling user notes\nfunction addNotes() {\n    $(\"textarea#notes\").val(user.notes);\n}\n\n// Filling Savings column\nfunction addSavings() {\n    switch (savings.deposit) {\n        case true: $(\"#deposit\").prop('checked', true);\n            break;\n    }\n    switch (savings.capitalization) {\n        case true: $(\"#capitalization\").prop('checked', true);\n            break;\n    }\n    $(\"#savingsvalue\").val(savings.freeMoney);\n    $(\"#percentvalue\").val(savings.percent);\n    $(\"#savingsvalue\").autoNumeric(\"init\");\n    $(\"#percentvalue\").autoNumeric(\"init\");\n    moveRuble();\n}\n\n// Filling Incomes and Expenses columns\nfunction addItems() {\n    Object.keys(incomes).forEach(function(key) {\n        var value = incomes[key].amount.replace(/(\\d)(?=(\\d\\d\\d)+([^\\d]|$))/g, '$1 </span><span class=\"lightdigit20\">');\n        $(\"#incomeslider\").append('<div onclick=\"itemClick(this)\" class=\"incomeitem\" id=\"income-' + incomes[key].income_id + '\"><span class=\"title11museo300\">' + incomes[key].title + '</span><p class=\"title9museo300\"><span class=\"bolddigit20\">' + value + ' </span>' + checkCurrency(incomes[key].currency) + checkPeriod(incomes[key].period) + '</span></p><div class=\"itembackground\"></div></div>');\n        $(\"#income-\" + incomes[key].income_id).data({\"id\": incomes[key].income_id, \"icon\": incomes[key].icon, \"amount\": incomes[key].amount, \"title\": incomes[key].title, \"currency\": incomes[key].currency ,\"period\": incomes[key].period}).children(\"div\").addClass(incomes[key].icon);\n    });\n    Object.keys(expenses).forEach(function(key) {\n        var value = expenses[key].amount.replace(/(\\d)(?=(\\d\\d\\d)+([^\\d]|$))/g, '$1 </span><span class=\"lightdigit20\">');\n        $(\"#expenseslider\").append('<div onclick=\"itemClick(this)\" class=\"expenseitem\" id=\"expense-' + expenses[key].expense_id + '\"><span class=\"title11museo300\">' + expenses[key].title + '</span><p class=\"title9museo300\"><span class=\"bolddigit20\">' + value + ' </span>' + checkCurrency(expenses[key].currency) + checkPeriod(expenses[key].period) + '</span></p><div class=\"itembackground\"></div></div>');\n        $(\"#expense-\" + expenses[key].expense_id).data({\"id\": expenses[key].expense_id, \"icon\": expenses[key].icon, \"amount\": expenses[key].amount, \"title\": expenses[key].title, \"currency\": expenses[key].currency ,\"period\": expenses[key].period}).children(\"div\").addClass(expenses[key].icon);\n    });\n\n    // Show big ADD ITEM button when column is empty\n    checkSlidersLength();\n\n    // Markup changes according to number of column items\n    itemsPosition(\"expense\");\n    itemsPosition(\"income\");\n}\n\n// According to number of column items - show/hide up&down buttons and change position:absolute\nfunction itemsPosition(transaction) {\n    if ($(\"div#\" + transaction + \"slider\").children().length <= 4) {\n        $(\"#\" + transaction + \"slider\").css({\"position\": \"relative\"});\n        $(\"#\" + transaction + \"down, #\" + transaction + \"up\").hide();\n    }\n    else {\n        $(\"#\" + transaction + \"slider\").css({\"position\": \"absolute\"});\n        $(\"#\" + transaction + \"down, #\" + transaction + \"up\").show();\n    }\n}\n\nfunction checkSlidersLength() {\n    switch ($(\"div#incomeslider\").children().length) {\n        case 0: $(\"#noincomes\").show();\n            $(\"#incomesplusitem\").hide();\n            break;\n        default:\n            $(\"#noincomes\").hide();\n            $(\"#incomesplusitem\").show();\n            break;\n    }\n    switch ($(\"div#expenseslider\").children().length) {\n        case 0: $(\"#noexpenses\").show();\n            $(\"#expensesplusitem\").hide();\n            break;\n        default:\n            $(\"#noexpenses\").hide();\n            $(\"#expensesplusitem\").show();\n            break;\n    }\n}\n\nfunction checkPeriod(period) {\n    var periodText;\n    switch (period) {\n        case \"YEAR\":\n            periodText = \" / per year\";\n            break;\n        case \"QUARTER\":\n            periodText = \" / per quater\";\n            break;\n        case \"MONTH\":\n            periodText = \" / per month\";\n            break;\n        case \"DAY\":\n            periodText = \" / per day\";\n            break;\n        case \"HOUR\":\n            periodText = \" / per hour\";\n            break;\n    }\n    return periodText\n}\n\nfunction checkCurrency(currency) {\n    var currencyText;\n    switch (currency) {\n        case \"RUB\": currencyText=\"rub.\";\n            break;\n        case \"USD\": currencyText=\"$\";\n            break;\n        case \"EUR\": currencyText=\"&euro;\";\n            break;\n    }\n    return currencyText\n}\n\n// SLIDE COLUMNS UP AND DOWN\n\n// Eliminate the frequent calling slide functions\nfunction debounce(f, ms) {\n    var state = null;\n    var cooldown = 1;\n    return function() {\n        if (state) return;\n        f.apply(this, arguments);\n        state = cooldown;\n        setTimeout(function() { state = null }, ms);\n    }\n}\n\nUp \t = debounce(Up, 300);\nDown = debounce(Down, 300);\n\ninitStatisticPage \t= debounce(initStatisticPage, 900);\nlaunchStatistic \t= debounce(launchStatistic, 2400);\nfadeStatistic \t\t= debounce(fadeStatistic, 1900);\njsonDataSave \t\t= debounce(jsonDataSave, 1800);\nstartOfExpenseList \t= debounce(startOfExpenseList, 500);\nendOfExpenseList \t= debounce(endOfExpenseList, 500);\nstartOfIncomeList \t= debounce(startOfIncomeList, 500);\nendOfIncomeList \t= debounce(endOfIncomeList, 500);\ninitGreetingPage \t= debounce(initGreetingPage, 2000);\n\n// Slide up\nfunction Up(transaction) {\n    var length = $(\"#\" + transaction + \"slider\").children().length;\n    var itemWidth = 71;\n    if (Math.abs($(\"#\" + transaction + \"slider\").position().top % itemWidth) > 0.01) return;\n    else if ($(\"#\" + transaction + \"slider\").position().top > -(length-4)*itemWidth) {\n        var pixels=$(\"#\" + transaction + \"slider\").position().top + (length-5)*itemWidth;\n        var slide = \"translateY(\"+ pixels + \"px)\";\n        $(\"#\" + transaction + \"slider\").css({\n            \"-webkit-transform\": slide,\n            \"-moz-transform\": slide,\n            \"-o-transform\": slide,\n            \"-ms-transform\": slide,\n            \"transform\": slide\n        });\n    }\n    else {\n        if (transaction == \"expense\") {\n            endOfExpenseList(transaction);\n            setTimeout(endOfExpenseList, 500);\n        }\n        else {\n            endOfIncomeList(transaction);\n            setTimeout(endOfIncomeList, 500);\n        }\n    }\n}\n// Slide down\nfunction Down(transaction) {\n    var length = $(\"div#\" + transaction + \"slider\").children().length;\n    var itemWidth = 71;\n    if (Math.abs($(\"#\" + transaction + \"slider\").position().top % itemWidth) > 0.01) return;\n    else if ($(\"#\" + transaction + \"slider\").position().top < -20) {\n        var pixels=$(\"#\" + transaction + \"slider\").position().top + (length-3)*itemWidth;\n        var slide = \"translateY(\"+ pixels + \"px)\";\n        $(\"#\" + transaction + \"slider\").css({\n            \"-webkit-transform\": slide,\n            \"-moz-transform\": slide,\n            \"-o-transform\": slide,\n            \"-ms-transform\": slide,\n            \"transform\": slide\n        });\n    }\n    else {\n        if (transaction == \"expense\") {\n            startOfExpenseList(transaction);\n            setTimeout(startOfExpenseList, 530);\n        }\n        else {\n            startOfIncomeList(transaction);\n            setTimeout(startOfIncomeList, 530);\n        }\n    }\n}\n\n// Bounce end of list functions\nfunction startOfExpenseList(transaction) {\n    $(\"#expensewrapper\").toggleClass(\"startoflist\");\n}\nfunction endOfExpenseList(transaction) {\n    $(\"#expensewrapper\").toggleClass(\"endoflist\");\n}\nfunction startOfIncomeList(transaction) {\n    $(\"#incomewrapper\").toggleClass(\"startoflist\");\n}\nfunction endOfIncomeList(transaction) {\n    $(\"#incomewrapper\").toggleClass(\"endoflist\");\n}\n\n// MODAL WINDOWS FUNCTIONS\n\n// ADD ITEMS\nfunction addNewItem() {\n    var itemTitle = $(\".modaltitle\").val(),\n        itemIcon = $(\".initicons\").data(\"iconselected\"),\n        itemCurrency = $(\".modalcurrency\").val(),\n        itemPeriod = $(\".modalperiod\").val(),\n        itemValue = $(\".modalvalue\").autoNumeric(\"get\"),\n        itemId = 0;\n    if (checkModalFields(itemValue, itemTitle)) { // If inputs are proper filled, add incomes or expenses\n        if ($(\".initicons\").data (\"incomes-expenses\") == \"incomes\") {\n            Object.keys(incomes).forEach(function(keys) {\tif (incomes[keys].income_id > itemId) {\titemId = (incomes[keys].income_id)\t}});\n            AddIncome(++itemId, itemTitle, itemIcon, itemCurrency, itemPeriod, itemValue);\n            addNewDiv(\"income\", itemId, itemTitle, itemIcon, itemCurrency, itemPeriod, itemValue);\n        }\n        else {\n            Object.keys(expenses).forEach(function(keys) {\tif (expenses[keys].expense_id > itemId) {\titemId = (expenses[keys].expense_id)\t}});\n            AddExpense(++itemId, itemTitle, itemIcon, itemCurrency, itemPeriod, itemValue);\n            addNewDiv(\"expense\", itemId, itemTitle, itemIcon, itemCurrency, itemPeriod, itemValue);\n        }\n        turnOffModal();\n    }\n    checkSlidersLength();\n    itemsPosition(\"expense\");\n    itemsPosition(\"income\");\n    setTimeout(function() { runConvert() }, 230);\n}\nfunction addNewDiv(whichColumn, itemId, itemTitle, itemIcon, itemCurrency, itemPeriod, itemValue) {\n    var value = itemValue.replace(/(\\d)(?=(\\d\\d\\d)+([^\\d]|$))/g, '$1 </span><span class=\"lightdigit20\">');\n    $(\"#\" + whichColumn + \"slider\").append('<div onclick=\"itemClick(this)\" class=\"' + whichColumn + 'item\" id=\"' + whichColumn +'-' + itemId + '\"><span class=\"title11museo300\">' + itemTitle + '</span><p class=\"title9museo300\"><span class=\"bolddigit20\">' + value + ' </span>' + checkCurrency(itemCurrency) + checkPeriod(itemPeriod) + '</span></p><div class=\"itembackground\"></div></div>');\n    $(\"#\" + whichColumn + \"-\" + itemId).addClass(\"newitemadded\").data({\"id\": itemId, \"icon\": itemIcon, \"amount\": itemValue, \"title\": itemTitle, \"currency\": itemCurrency ,\"period\": itemPeriod}).children(\"div\").addClass(itemIcon);\n    setTimeout(function() { $(\"#\" + whichColumn + \"-\" + itemId).removeClass(\"newitemadded\") }, 4100);\n}\n// EDIT ITEMS\nfunction itemClick(item) {\n    // Add delete-button\n    $(\".modal-delete\").show();\n    var itemDiv = $(\"#\" + item.id),\n        itemIcon = itemDiv.data(\"icon\"),\n        itemCurrency = itemDiv.data(\"currency\"),\n        itemPeriod = itemDiv.data(\"period\"),\n        itemValue = itemDiv.data(\"amount\"),\n        itemTitle = itemDiv.data(\"title\"),\n        itemId = itemDiv.data(\"id\"),\n        incomesExpenses = \"expense\",\n        whichColumn = expenses;\n\n    if (itemDiv.hasClass(\"incomeitem\")) {\n        $(\".mainmodaltitle\").empty().append(\"Change income\");\n        incomesExpenses = \"income\";\n        whichColumn = incomes;\n    }\n    else {\n        $(\".mainmodaltitle\").empty().append(\"Change expense\");\n    }\n    $(\".initicons\").data({\"iconselected\": itemIcon, \"add-edit\": \"edit\", \"incomes-expenses\": incomesExpenses + \"s\"});\n    $(\"#chooseicon\").removeClass().addClass(itemIcon);\n    $(\".modalvalue\").show().autoNumeric(\"set\", itemValue);\n    $(\".modaltitle\").show().val(itemTitle);\n    $(\".modalcurrency\").val(itemCurrency);\n    $(\".modalperiod\").val(itemPeriod);\n    $(\".modalcurrency, .modalperiod\").trigger(\"refresh\");\n    $(\"#overlay, #add-modal\").addClass(\"modal-show\");\n    setTimeout(function() { $('.modalvalue').putCursorAtEnd() }, 50);\n    saveOldItem = function() {\n        if (checkModalFields($(\".modalvalue\").val(), $(\".modaltitle\").val())) { // If inputs are proper filled, save changes\n            whichColumn[itemId].title = $(\".modaltitle\").val();\n            whichColumn[itemId].amount = $(\".modalvalue\").autoNumeric(\"get\");\n            whichColumn[itemId].icon = $(\".initicons\").data(\"iconselected\");\n            whichColumn[itemId].currency = $(\".modalcurrency\").val();\n            whichColumn[itemId].period = $(\".modalperiod\").val();\n            editOldDiv(incomesExpenses, itemId, $(\".modaltitle\").val(), $(\".initicons\").data(\"iconselected\"), $(\".modalcurrency\").val(), $(\".modalperiod\").val(), $(\".modalvalue\").autoNumeric(\"get\"));\n            turnOffModal();\n        }\n        checkSlidersLength();\n        itemsPosition(\"expense\");\n        itemsPosition(\"income\");\n        setTimeout(function() { runConvert() }, 230);\n    };\n    deleteItem = function() {\n        turnOffModal();\n        delete whichColumn[itemId];\n        itemDiv.css({\"height\": \"0px\", \"background-color\": \"#ffe3e3\"});\n        setTimeout(function() { $(\"#\" + incomesExpenses + \"-\" + itemId).remove(); checkSlidersLength() }, 300);\n\n        // Way to proper move items list\n        var slider = $(\"#\" + incomesExpenses + \"slider\");\n        if (slider.position().top > -71 && slider.children().length > 4) {\n            setTimeout(function() { Up(incomesExpenses) }, 300);\n        }\n        itemsPosition(\"expense\");\n        itemsPosition(\"income\");\n        setTimeout(function() { runConvert() }, 230);\n\n        // Save changes on server\n        jsonDataSave();\n    }\n}\nfunction editOldDiv(whichColumn, itemId, itemTitle, itemIcon, itemCurrency, itemPeriod, itemValue) {\n    var value = itemValue.replace(/(\\d)(?=(\\d\\d\\d)+([^\\d]|$))/g, '$1 </span><span class=\"lightdigit20\">');\n    $(\"#\" + whichColumn + \"-\" + itemId).replaceWith('<div onclick=\"itemClick(this)\" class=\"' + whichColumn + 'item\" id=\"' + whichColumn +'-' + itemId + '\"><span class=\"title11museo300\">' + itemTitle + '</span><p class=\"title9museo300\"><span class=\"bolddigit20\">' + value + ' </span>' + checkCurrency(itemCurrency) + checkPeriod(itemPeriod) + '</span></p><div class=\"itembackground\"></div></div>');\n    $(\"#\" + whichColumn + \"-\" + itemId).addClass(\"newitemadded\").data({\"id\": itemId, \"icon\": itemIcon, \"amount\": itemValue, \"title\": itemTitle, \"currency\": itemCurrency ,\"period\": itemPeriod}).children(\"div\").addClass(itemIcon);\n    setTimeout(function() { $(\"#\" + whichColumn + \"-\" + itemId).removeClass(\"newitemadded\") }, 4100);\n}\n// prepare calculations for 4 page\nfunction runConvert() {\n    expensesSumMonth = 0; incomesSumMonth = 0;\n    $(\"#circle-select-1, #circle-select-2, #circle-select-3\").empty();\n    getConverted(incomes);\n    getConverted(expenses);\n}\n\n\n\n// EVENT HANDLERS\n\n// MODAL: Call add items modal window\n$(\"#noincomes, #noexpenses, .zoomplus, .plusitemborder\").click(function() {\n    $(\".mainmodaltitle\").empty();\n    if ($(this).hasClass(\"incomebutton\")) {\n        $(\".initicons\").data({\"iconselected\": \"wallet\", \"add-edit\": \"add\", \"incomes-expenses\": \"incomes\"});\n        $(\"#chooseicon\").removeClass().addClass(\"wallet\");\n        $(\".mainmodaltitle\").append(\"Add income\");\n    }\n    else {\n        $(\".initicons\").data({\"iconselected\": \"cart\", \"add-edit\": \"add\", \"incomes-expenses\": \"expenses\"});\n        $(\"#chooseicon\").removeClass().addClass(\"cart\");\n        $(\".mainmodaltitle\").append(\"Add expense\");\n    }\n    $(\".modalvalue, .modaltitle\").show();\n    $(\"#overlay, #add-modal\").addClass(\"modal-show\");\n    setTimeout(function() { $('.modalvalue').putCursorAtEnd() }, 50);\n});\n\n// MODAL: Save button handlers\n$(\".modal-save\").click(addOrSaveItems);\n$(\".modaltitle, .modalvalue\").keyup(function(e) {\n    if(e.which == 13) {\taddOrSaveItems(); this.blur() }\n});\n\nfunction addOrSaveItems () {\n    if($(\".initicons\").data(\"add-edit\") == \"add\") {\n        addNewItem();\n    }\n    else {\n        saveOldItem();\n    }\n    jsonDataSave();\n}\n\n// MODAL: Delete button handler\n$(\".modal-delete\").click(function() {\n    deleteItem();\n});\n\n// MODAL: Set choosen icon\n$(\".imgbox\").click(function() {\n    $(\".modaltable\").addClass(\"modalreverse\");\n    setTimeout( function() { $(\".modalincomessurface, .modalexpensessurface\").fadeOut(150); } , 160);\n    var icon = $(this).children(\"div\").attr('class').split(' ')[1];\n    $(\".initicons\").data(\"iconselected\", icon);\n    $(\"#chooseicon\").removeClass().addClass(icon);\n});\n\n// MODAL: Check that fields are not empty\nfunction checkModalFields (itemValue, itemTitle) {\n    if (itemValue == 0) { // Check value input\n        $(\".modalvalue\").addClass(\"modalvalueerror\");\n        setTimeout(function() { $(\".modalvalue\").removeClass(\"modalvalueerror\") }, 500);\n        return false;\n    }\n    else if (itemTitle.length == 0) { // Check title input\n        $(\".modaltitle\").addClass(\"modaltitleerror\");\n        setTimeout(function() { $(\".modaltitle\").removeClass(\"modaltitleerror\") }, 500);\n        return false;\n    }\n    else {\n        return true;\n    }\n}\n\n// MODAL: Start icons table \n$(\".initicons\").click(function() {\n    $(\".modaltable\").removeClass(\"modalreverse\");\n    if ($(\".initicons\").data(\"incomes-expenses\") == \"incomes\") {\n        $(\".modalincomessurface\").fadeIn(100);\n    }\n    else {\n        $(\".modalexpensessurface\").fadeIn(100);\n    }\n});\n\n// MODAL: Turn off modal/notes window and reset all forms\n$(\"#overlay, .modal-close\").click(turnOffModal);\nfunction turnOffModal() {\n    $(\"#overlay, #add-modal, #add-notes\").removeClass(\"modal-show\");\n    $(\".modalincomessurface, .modalexpensessurface\").fadeOut(150);\n    setTimeout(function() { $(\".modalvalue\").val('0').hide(); $(\".modaltitle\").val('').hide(); $(\".modal-delete\").hide(); $(\"textarea#notes\").val(user.notes).hide(); }, 200);\n}\n\n// MODAL: Change font size according to input value length\n$(\".modalvalue\").bind(\"keyup keydown keypress select focus click\", modalFontSize);\nfunction modalFontSize() {\n    var length=$(\".modalvalue\").val().length;\n    if (length > 10) {\n        $(\".modalvalue\").css({\"font-size\": \"60px\"});\n    }\n    else if (length > 9) {\n        $(\".modalvalue\").css({\"font-size\": \"66px\"});\n    }\n    else if (length > 8 ) {\n        $(\".modalvalue\").css({\"font-size\": \"76px\"});\n    }\n    else {\n        $(\".modalvalue\").css({\"font-size\": \"86px\"});\n    }\n}\n\n// MODAL: Set zero when input is empty\n$(\".modalvalue\").bind(\"blur\", function() {\n    if (this.value.length == 0) {\n        $(\".modalvalue\").val(\"0\");\n    }\n});\n\n// COLUMNS: UP and DOWN buttons\n$(\"#expenseup\").click(function() {\n    Up(\"expense\");\n});\n$(\"#expensedown\").click(function() {\n    Down(\"expense\");\n});\n$(\"#incomeup\").click(function() {\n    Up(\"income\");\n});\n$(\"#incomedown\").click(function() {\n    Down(\"income\");\n});\n\n// COLUMNS: Touch screen swipe\n$(\"#expensewrapper\").swipe( {\n    swipe:function(event, direction, distance, duration, fingerCount) {\n        if (direction == \"up\") {\n            Up(\"expense\");\n        }\n        if (direction == \"down\") {\n            Down(\"expense\");\n        }\n    },\n    threshold:0\n});\n$(\"#incomewrapper\").swipe( {\n    swipe:function(event, direction, distance, duration, fingerCount) {\n        if (direction == \"up\") {\n            Up(\"income\");\n        }\n        if (direction == \"down\") {\n            Down(\"income\");\n        }\n    },\n    threshold:0\n});\n// COLUMNS: Mouse wheel\n$('#expensewrapper').bind('DOMMouseScroll mousewheel', function (e){\n    if(e.originalEvent.wheelDelta > 70) {\n        Down(\"expense\");\n    }\n    if(e.originalEvent.wheelDelta < -70) {\n        Up(\"expense\");\n    }\n    if(e.originalEvent.detail < 0) {\n        Down(\"expense\");\n    }\n    if(e.originalEvent.detail > 0) {\n        Up(\"expense\");\n    }\n    return false;\n});\n\n$('#incomewrapper').bind('DOMMouseScroll mousewheel', function (e){\n    if(e.originalEvent.wheelDelta > 70) {\n        Down(\"income\");\n    }\n    if(e.originalEvent.wheelDelta < -70) {\n        Up(\"income\");\n    }\n    if(e.originalEvent.detail < 0) {\n        Down(\"income\");\n    }\n    if(e.originalEvent.detail > 0) {\n        Up(\"income\");\n    }\n    return false;\n});\n\n// SAVINGS: Click on toggles\n$(\"#deposit\").click(function() {\n    if ($(\"#deposit\").prop('checked') == false) {\n        $(\"#capitalization\").prop('disabled', true);\n        $(\"#percentvalue\").prop('disabled', true);\n        $(\"#capitalization\").prop('checked', false);\n    }\n    else {\n        $(\"#capitalization\").prop('disabled', false);\n        $(\"#percentvalue\").prop('disabled', false);\n    }\n});\n// SAVINGS: Moving ruble sign according to input value length\n$(\"#savingsvalue\").bind(\"keyup keydown keypress select click\", function() {savings.freeMoney = $(\"#savingsvalue\").autoNumeric(\"get\"); moveRuble();}).keyup(function(e) { if (e.which == 13) {\tthis.blur() } });\nfunction moveRuble() {\n    var length=$(\"#savingsvalue\").val().length;\n    length = (length<2)? 1: length;\n    $(\"#savingsvalue\").attr('size', length);\n    if (length > 9) {\n        $(\"#savingsvalue\").css({\"font-size\": \"31px\"});\n        $(\"#rublesign\").css({\"left\": (length*15 + 17) + \"px\", \"top\": \"115px\"});\n    }\n    else if (length > 7 ) {\n        $(\"#savingsvalue\").css({\"font-size\": \"38px\"});\n        $(\"#rublesign\").css({\"left\": (length*18 + 18) + \"px\", \"top\": \"120px\"});\n    }\n    else {\n        $(\"#savingsvalue\").css({\"font-size\": \"44px\"});\n        $(\"#rublesign\").css({\"left\": (length*22 + 20) + \"px\", \"top\": \"125px\"});\n    }\n}\n\n//SAVINGS: change currency on sign click\n$(\"#rublesign\").on(\"click\", function() {\n    switch (user.checkedCurr) {\n        case \"RUB\": user.checkedCurr = \"EUR\"; $(\"#rublesign\").css({\"background-position\": \"-386px 0\"});\n            break;\n        case \"EUR\": user.checkedCurr = \"USD\"; $(\"#rublesign\").css({\"background-position\": \"-354px 0\"});\n            break;\n        case \"USD\": user.checkedCurr = \"RUB\"; $(\"#rublesign\").css({\"background-position\": \"-150px 0\"});\n            break;\n    }\n    changeCurrency();\n    $(\"#savingsvalue\").autoNumeric('set', Math.round (savings.freeMoney) );\n    moveRuble();\n    // Update savings slider\n    $('#savings-slider').noUiSlider({\n        start: (incomesSumMonth-expensesSumMonth) * $(\"#savings-slider\").data(\"checkedPercent\"),\n        step: (incomesSumMonth-expensesSumMonth) / 20,\n        range: {\n            'min': [ 0 ],\n            'max': [ incomesSumMonth-expensesSumMonth ]\n        }\n    }, true);\n    runConvert();\n});\n\n// SAVINGS: Set zero when input is empty\n$(\"#savingsvalue\").bind(\"blur\", function() {\n    if (this.value.length == 0) {\n        $(\"#savingsvalue\").val(\"0\");\n    }\n});\n$(\"#percentvalue\").keyup(function(e) { if(e.which == 13) {\tthis.blur() } }).bind(\"blur\", function() {\n    if (this.value == \" %\") {\n        $(\"#percentvalue\").val(\"0 %\");\n    }\n});\n\n// NOTES: call window\n$(\"#bubble\").click(function() {\n    $(\"#overlay, #add-notes\").addClass(\"modal-show\");\n    $(\".notes-input\").show();\n});\n\n// NOTES: save button handler\n$(\".notes-save\").click(function() {\n    user.notes = $(\"textarea#notes\").val();\n    turnOffModal();\n    jsonDataSave();\n});\n\n// Bubble animation\n$(\"#bubble\").bind(\"hover\",  function() {\n    $(\"#indicator\").toggleClass(\"bubble-animation\");\n});\n\n// ScrollTop\n$(\"input, textarea\").bind(\"blur\", function() {\n    $('body').animate({ scrollTop: '0' }, 50)\n});\n\n// PAGE CHANGING\n\n// Cancel swipe on other fields\n$(\"body\").swipe( {\n    swipe:function(event, direction, distance, duration, fingerCount) {\n        $('body').animate({ scrollTop: '0' }, 50)\n    },\n    threshold:0\n});\n\n// Go back to Greeting Page\n$(\"#logoclickplace\").click(function() {\n    $(\"#logo_settings, #overlay, .modal-content, #settingspage, #expenseslider, #bubble\").fadeOut(300);\n    setTimeout(greetingPageAgain, 250);\n});\n\n// All-PAGE SWIPE BETWEEN 3-4 PAGES\n$(\"#swipefield, #savings, #savebutton, #settings_hat\").bind('DOMMouseScroll mousewheel', function (e){\n    if(e.originalEvent.wheelDelta < -50) {\n        launchStatistic();\n    }\n    return false;\n});\n$(\".bottompage\").bind('DOMMouseScroll mousewheel', function (e){\n    if(e.originalEvent.wheelDelta > 100) {\n        fadeStatistic();\n    }\n    return false;\n});\n$(\"#swipefield, #savings, #savebutton, #settings_hat\").swipe( {\n    swipe:function(event, direction, distance, duration, fingerCount) {\n        if (direction == \"up\" || direction == \"left\") {\n            if (global.mobileClient) launchStatistic();\n        }\n    },\n    threshold:0\n});\n$(\".bottompage\").swipe( {\n    swipe:function(event, direction, distance, duration, fingerCount) {\n        if (direction == \"down\" || direction == \"right\") {\n            if (global.mobileClient) fadeStatistic();\n        }\n    },\n    threshold:0\n});\n$(\"#savebutton\").on(\"click\", function() {\n    launchStatistic();\n});\n$(\"#logo_statistic\").on(\"click\", function() {\n    fadeStatistic();\n});\n\n// Launch 4 page\nfunction launchStatistic() {\n    if ($(\"#incomeslider\").children().length > 0 && $(\"#expenseslider\").children().length > 0) {\n\n        $(\".bottompage\").css({\"display\": \"block\"});\n\n        // Fill objects form Savings fields\n        savings.percent = $(\"#percentvalue\").autoNumeric(\"get\");\n        savings.deposit = $(\"#deposit\").prop(\"checked\");\n        savings.capitalization = $(\"#capitalization\").prop(\"checked\");\n\n        // Launch 4 page\n        $(\"#lastlogoflipper\").addClass(\"flippedcard\");\n        initStatisticPage();\n        setTimeout(function() { initSavingsCircles(user.checkedPercent, 0.2, savings.freeMoney, savings.freeMoney, 700) }, 1600);\n        setTimeout(function() { $(\".toppage, .bottompage\").addClass(\"sectionDown\"); }, 400);\n\n        // Update savings slider\n        $('#savings-slider').noUiSlider({\n            start: (incomesSumMonth-expensesSumMonth) * $(\"#savings-slider\").data(\"checkedPercent\"),\n            step: (incomesSumMonth-expensesSumMonth) / 20,\n            range: {\n                'min': [ 0 ],\n                'max': [Math.abs(incomesSumMonth-expensesSumMonth)]\n            }\n        }, true);\n\n    }\n    else {\n        alert(\"Please, add at least one item for each column\")\n    }\n\n    jsonDataSave();\n\n}\n\nfunction jsonDataSave() {\n    if (global.savePermit) {\n        $.ajax({\n            url: 'accounts/current',\n            datatype: 'json',\n            type: \"put\",\n            contentType: \"application/json\",\n            headers: {'Authorization': 'Bearer ' + getOauthTokenFromStorage()},\n            data: JSON.stringify({\n                note: user.notes,\n                incomes: $.map(incomes, function(value) {return [value]}),\n                expenses: $.map(expenses, function(value) {return [value]}),\n                saving: {\n                    amount: Math.ceil(savings.freeMoney),\n                    capitalization: savings.capitalization,\n                    deposit: savings.deposit,\n                    currency: user.checkedCurr,\n                    interest: savings.percent\n                }\n            }),\n            success: function () {\n                $(\"#leftborder, #rightborder, #centerborder\").addClass(\"saveaction\");\n                setTimeout(function() {\n                    $(\"#leftborder, #rightborder, #centerborder\").removeClass(\"saveaction\");\n                }, 400);\n            },\n            error: function () {\n                alert(\"An error during data saving. Please, try again later\");\n            }\n        });\n    }\n}\n\nfunction fadeStatistic() {\n\n    switch (user.checkedCurr) {\n        case \"RUB\": $(\"#rublesign\").css({\"background-position\": \"-150px 0\"});\n            break;\n        case \"EUR\": $(\"#rublesign\").css({\"background-position\": \"-386px 0\"});\n            break;\n        case \"USD\": $(\"#rublesign\").css({\"background-position\": \"-354px 0\"});\n            break;\n    }\n    $(\"#savingsvalue\").autoNumeric('set', savings.freeMoney);\n    moveRuble();\n\n    $(\".toppage, .bottompage\").removeClass(\"sectionDown\");\n    setTimeout(function() { $(\"#lastlogoflipper\").removeClass(\"flippedcard\"); }, 220);\n\n    setTimeout(function() { drawChartLine(91); $(\".bottompage\").css({\"display\": \"none\"}); }, 500);\n}"
  },
  {
    "path": "gateway/src/test/java/com/piggymetrics/gateway/GatewayApplicationTests.java",
    "content": "package com.piggymetrics.gateway;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class GatewayApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n\t@Test\n\tpublic void fire() {\n\n\t}\n\n}\n"
  },
  {
    "path": "gateway/src/test/resources/bootstrap.yml",
    "content": "eureka:\n  client:\n    enabled: false"
  },
  {
    "path": "mongodb/Dockerfile",
    "content": "FROM mongo:3\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD init.sh /init.sh\nADD ./dump /\n\nRUN \\\n chmod +x /init.sh && \\\n apt-get update && apt-get dist-upgrade -y --force-yes && apt-get install dos2unix && \\\n apt-get install psmisc -y -q && \\\n apt-get autoremove -y && apt-get clean && \\\n rm -rf /var/cache/* && rm -rf /var/lib/apt/lists/* && \\\n dos2unix -n /init.sh /initx.sh && chmod +x /initx.sh\n\nENTRYPOINT [\"/initx.sh\"]"
  },
  {
    "path": "mongodb/dump/account-service-dump.js",
    "content": "/**\n * Creates pre-filled demo account\n */\n\nprint('dump start');\n\ndb.accounts.update(\n    { \"_id\": \"demo\" },\n    {\n    \"_id\": \"demo\",\n    \"lastSeen\": new Date(),\n    \"note\": \"demo note\",\n    \"expenses\": [\n        {\n            \"amount\": 1300,\n            \"currency\": \"USD\",\n            \"icon\": \"home\",\n            \"period\": \"MONTH\",\n            \"title\": \"Rent\"\n        },\n        {\n            \"amount\": 120,\n            \"currency\": \"USD\",\n            \"icon\": \"utilities\",\n            \"period\": \"MONTH\",\n            \"title\": \"Utilities\"\n        },\n        {\n            \"amount\": 20,\n            \"currency\": \"USD\",\n            \"icon\": \"meal\",\n            \"period\": \"DAY\",\n            \"title\": \"Meal\"\n        },\n        {\n            \"amount\": 240,\n            \"currency\": \"USD\",\n            \"icon\": \"gas\",\n            \"period\": \"MONTH\",\n            \"title\": \"Gas\"\n        },\n        {\n            \"amount\": 3500,\n            \"currency\": \"EUR\",\n            \"icon\": \"island\",\n            \"period\": \"YEAR\",\n            \"title\": \"Vacation\"\n        },\n        {\n            \"amount\": 30,\n            \"currency\": \"EUR\",\n            \"icon\": \"phone\",\n            \"period\": \"MONTH\",\n            \"title\": \"Phone\"\n        },\n        {\n            \"amount\": 700,\n            \"currency\": \"USD\",\n            \"icon\": \"sport\",\n            \"period\": \"YEAR\",\n            \"title\": \"Gym\"\n        }\n    ],\n    \"incomes\": [\n        {\n            \"amount\": 42000,\n            \"currency\": \"USD\",\n            \"icon\": \"wallet\",\n            \"period\": \"YEAR\",\n            \"title\": \"Salary\"\n        },\n        {\n            \"amount\": 500,\n            \"currency\": \"USD\",\n            \"icon\": \"edu\",\n            \"period\": \"MONTH\",\n            \"title\": \"Scholarship\"\n        }\n    ],\n    \"saving\": {\n            \"amount\": 5900,\n            \"capitalization\": false,\n            \"currency\": \"USD\",\n            \"deposit\": true,\n            \"interest\": 3.32\n        }\n    },\n    { upsert: true }\n);\n\nprint('dump complete');"
  },
  {
    "path": "mongodb/init.sh",
    "content": "#!/bin/bash\nif test -z \"$MONGODB_PASSWORD\"; then\n    echo \"MONGODB_PASSWORD not defined\"\n    exit 1\nfi\n\nauth=\"-u user -p $MONGODB_PASSWORD\"\n\n# MONGODB USER CREATION\n(\necho \"setup mongodb auth\"\ncreate_user=\"if (!db.getUser('user')) { db.createUser({ user: 'user', pwd: '$MONGODB_PASSWORD', roles: [ {role:'readWrite', db:'piggymetrics'} ]}) }\"\nuntil mongo piggymetrics --eval \"$create_user\" || mongo piggymetrics $auth --eval \"$create_user\"; do sleep 5; done\nkillall mongod\nsleep 1\nkillall -9 mongod\n) &\n\n# INIT DUMP EXECUTION\n(\nif test -n \"$INIT_DUMP\"; then\n    echo \"execute dump file\"\n\tuntil mongo piggymetrics $auth $INIT_DUMP; do sleep 5; done\nfi\n) &\n\necho \"start mongodb without auth\"\nchown -R mongodb /data/db\ngosu mongodb mongod \"$@\"\n\necho \"restarting with auth on\"\nsleep 5\nexec gosu mongodb /usr/local/bin/docker-entrypoint.sh --auth \"$@\"\n"
  },
  {
    "path": "monitoring/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/monitoring.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/monitoring.jar\"]\n\nEXPOSE 8080"
  },
  {
    "path": "monitoring/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>monitoring</artifactId>\n\t<version>0.0.1-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>monitoring</name>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>${project.name}</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "monitoring/src/main/java/com/piggymetrics/monitoring/MonitoringApplication.java",
    "content": "package com.piggymetrics.monitoring;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;\n\n@SpringBootApplication\n@EnableHystrixDashboard\npublic class MonitoringApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(MonitoringApplication.class, args);\n\t}\n}\n"
  },
  {
    "path": "monitoring/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: monitoring\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user"
  },
  {
    "path": "monitoring/src/test/java/com/piggymetrics/monitoring/MonitoringApplicationTests.java",
    "content": "package com.piggymetrics.monitoring;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class MonitoringApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "monitoring/src/test/resources/bootstrap.yml",
    "content": "eureka:\n  client:\n    enabled: false"
  },
  {
    "path": "notification-service/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/notification-service.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/notification-service.jar\"]\n\nEXPOSE 8000"
  },
  {
    "path": "notification-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>notification-service</artifactId>\n\t<version>1.0.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>notification-service</name>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-oauth2</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-security</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-openfeign</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-sleuth</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-data-mongodb</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-bus-amqp</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-mail</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>de.flapdoodle.embed</groupId>\n\t\t\t<artifactId>de.flapdoodle.embed.mongo</artifactId>\n\t\t\t<version>1.50.3</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.jayway.jsonpath</groupId>\n\t\t\t<artifactId>json-path</artifactId>\n\t\t\t<version>2.2.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>notification-service</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t<version>0.7.6.201602180812</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>report</id>\n\t\t\t\t\t\t<phase>test</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>report</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/NotificationServiceApplication.java",
    "content": "package com.piggymetrics.notification;\n\nimport com.piggymetrics.notification.repository.converter.FrequencyReaderConverter;\nimport com.piggymetrics.notification.repository.converter.FrequencyWriterConverter;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.cloud.openfeign.EnableFeignClients;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.mongodb.core.convert.CustomConversions;\nimport org.springframework.scheduling.annotation.EnableScheduling;\nimport org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;\n\nimport java.util.Arrays;\n\n@SpringBootApplication\n@EnableDiscoveryClient\n@EnableOAuth2Client\n@EnableFeignClients\n@EnableGlobalMethodSecurity(prePostEnabled = true)\n@EnableScheduling\npublic class NotificationServiceApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(NotificationServiceApplication.class, args);\n\t}\n\n\t@Configuration\n\tstatic class CustomConversionsConfig {\n\n\t\t@Bean\n\t\tpublic CustomConversions customConversions() {\n\t\t\treturn new CustomConversions(Arrays.asList(new FrequencyReaderConverter(),\n\t\t\t\t\tnew FrequencyWriterConverter()));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/client/AccountServiceClient.java",
    "content": "package com.piggymetrics.notification.client;\n\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\n@FeignClient(name = \"account-service\")\npublic interface AccountServiceClient {\n\n\t@RequestMapping(method = RequestMethod.GET, value = \"/accounts/{accountName}\", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)\n\tString getAccount(@PathVariable(\"accountName\") String accountName);\n\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/config/ResourceServerConfig.java",
    "content": "package com.piggymetrics.notification.config;\n\nimport feign.RequestInterceptor;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;\nimport org.springframework.security.oauth2.client.OAuth2RestTemplate;\nimport org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;\n\n/**\n * @author cdov\n */\n@Configuration\n@EnableResourceServer\npublic class ResourceServerConfig extends ResourceServerConfigurerAdapter {\n    @Bean\n    @ConfigurationProperties(prefix = \"security.oauth2.client\")\n    public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {\n        return new ClientCredentialsResourceDetails();\n    }\n    @Bean\n    public RequestInterceptor oauth2FeignRequestInterceptor(){\n        return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());\n    }\n\n    @Bean\n    public OAuth2RestTemplate clientCredentialsRestTemplate() {\n        return new OAuth2RestTemplate(clientCredentialsResourceDetails());\n    }\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java",
    "content": "package com.piggymetrics.notification.controller;\n\nimport com.piggymetrics.notification.domain.Recipient;\nimport com.piggymetrics.notification.service.RecipientService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.validation.Valid;\nimport java.security.Principal;\n\n@RestController\n@RequestMapping(\"/recipients\")\npublic class RecipientController {\n\n\t@Autowired\n\tprivate RecipientService recipientService;\n\n\t@RequestMapping(path = \"/current\", method = RequestMethod.GET)\n\tpublic Object getCurrentNotificationsSettings(Principal principal) {\n\t\treturn recipientService.findByAccountName(principal.getName());\n\t}\n\n\t@RequestMapping(path = \"/current\", method = RequestMethod.PUT)\n\tpublic Object saveCurrentNotificationsSettings(Principal principal, @Valid @RequestBody Recipient recipient) {\n\t\treturn recipientService.save(principal.getName(), recipient);\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/domain/Frequency.java",
    "content": "package com.piggymetrics.notification.domain;\n\nimport java.util.stream.Stream;\n\npublic enum Frequency {\n\n\tWEEKLY(7), MONTHLY(30), QUARTERLY(90);\n\n\tprivate int days;\n\n\tFrequency(int days) {\n\t\tthis.days = days;\n\t}\n\n\tpublic int getDays() {\n\t\treturn days;\n\t}\n\n\tpublic static Frequency withDays(int days) {\n\t\treturn Stream.of(Frequency.values())\n\t\t\t\t.filter(f -> f.getDays() == days)\n\t\t\t\t.findFirst()\n\t\t\t\t.orElseThrow(IllegalArgumentException::new);\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/domain/NotificationSettings.java",
    "content": "package com.piggymetrics.notification.domain;\n\nimport javax.validation.constraints.NotNull;\nimport java.util.Date;\n\npublic class NotificationSettings {\n\n\t@NotNull\n\tprivate Boolean active;\n\n\t@NotNull\n\tprivate Frequency frequency;\n\n\tprivate Date lastNotified;\n\n\tpublic Boolean getActive() {\n\t\treturn active;\n\t}\n\n\tpublic void setActive(Boolean active) {\n\t\tthis.active = active;\n\t}\n\n\tpublic Frequency getFrequency() {\n\t\treturn frequency;\n\t}\n\n\tpublic void setFrequency(Frequency frequency) {\n\t\tthis.frequency = frequency;\n\t}\n\n\tpublic Date getLastNotified() {\n\t\treturn lastNotified;\n\t}\n\n\tpublic void setLastNotified(Date lastNotified) {\n\t\tthis.lastNotified = lastNotified;\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/domain/NotificationType.java",
    "content": "package com.piggymetrics.notification.domain;\n\npublic enum NotificationType {\n\n\tBACKUP(\"backup.email.subject\", \"backup.email.text\", \"backup.email.attachment\"),\n\tREMIND(\"remind.email.subject\", \"remind.email.text\", null);\n\n\tprivate String subject;\n\tprivate String text;\n\tprivate String attachment;\n\n\tNotificationType(String subject, String text, String attachment) {\n\t\tthis.subject = subject;\n\t\tthis.text = text;\n\t\tthis.attachment = attachment;\n\t}\n\n\tpublic String getSubject() {\n\t\treturn subject;\n\t}\n\n\tpublic String getText() {\n\t\treturn text;\n\t}\n\n\tpublic String getAttachment() {\n\t\treturn attachment;\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/domain/Recipient.java",
    "content": "package com.piggymetrics.notification.domain;\n\nimport org.hibernate.validator.constraints.Email;\nimport org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotNull;\nimport java.util.Map;\n\n@Document(collection = \"recipients\")\npublic class Recipient {\n\n\t@Id\n\tprivate String accountName;\n\n\t@NotNull\n\t@Email\n\tprivate String email;\n\n\t@Valid\n\tprivate Map<NotificationType, NotificationSettings> scheduledNotifications;\n\n\tpublic String getAccountName() {\n\t\treturn accountName;\n\t}\n\n\tpublic void setAccountName(String accountName) {\n\t\tthis.accountName = accountName;\n\t}\n\n\tpublic String getEmail() {\n\t\treturn email;\n\t}\n\n\tpublic void setEmail(String email) {\n\t\tthis.email = email;\n\t}\n\n\tpublic Map<NotificationType, NotificationSettings> getScheduledNotifications() {\n\t\treturn scheduledNotifications;\n\t}\n\n\tpublic void setScheduledNotifications(Map<NotificationType, NotificationSettings> scheduledNotifications) {\n\t\tthis.scheduledNotifications = scheduledNotifications;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Recipient{\" +\n\t\t\t\t\"accountName='\" + accountName + '\\'' +\n\t\t\t\t\", email='\" + email + '\\'' +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/repository/RecipientRepository.java",
    "content": "package com.piggymetrics.notification.repository;\n\nimport com.piggymetrics.notification.domain.Recipient;\nimport org.springframework.data.mongodb.repository.Query;\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.List;\n\n@Repository\npublic interface RecipientRepository extends CrudRepository<Recipient, String> {\n\n\tRecipient findByAccountName(String name);\n\n\t@Query(\"{ $and: [ {'scheduledNotifications.BACKUP.active': true }, { $where: 'this.scheduledNotifications.BACKUP.lastNotified < \" +\n\t\t\t\"new Date(new Date().setDate(new Date().getDate() - this.scheduledNotifications.BACKUP.frequency ))' }] }\")\n\tList<Recipient> findReadyForBackup();\n\n\t@Query(\"{ $and: [ {'scheduledNotifications.REMIND.active': true }, { $where: 'this.scheduledNotifications.REMIND.lastNotified < \" +\n\t\t\t\"new Date(new Date().setDate(new Date().getDate() - this.scheduledNotifications.REMIND.frequency ))' }] }\")\n\tList<Recipient> findReadyForRemind();\n\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/repository/converter/FrequencyReaderConverter.java",
    "content": "package com.piggymetrics.notification.repository.converter;\n\nimport com.piggymetrics.notification.domain.Frequency;\nimport org.springframework.core.convert.converter.Converter;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class FrequencyReaderConverter implements Converter<Integer, Frequency> {\n\n\t@Override\n\tpublic Frequency convert(Integer days) {\n\t\treturn Frequency.withDays(days);\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/repository/converter/FrequencyWriterConverter.java",
    "content": "package com.piggymetrics.notification.repository.converter;\n\nimport com.piggymetrics.notification.domain.Frequency;\nimport org.springframework.core.convert.converter.Converter;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class FrequencyWriterConverter implements Converter<Frequency, Integer> {\n\n\t@Override\n\tpublic Integer convert(Frequency frequency) {\n\t\treturn frequency.getDays();\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/service/EmailService.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\n\nimport javax.mail.MessagingException;\nimport java.io.IOException;\n\npublic interface EmailService {\n\n\tvoid send(NotificationType type, Recipient recipient, String attachment) throws MessagingException, IOException;\n\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/service/EmailServiceImpl.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.core.env.Environment;\nimport org.springframework.core.io.ByteArrayResource;\nimport org.springframework.mail.javamail.JavaMailSender;\nimport org.springframework.mail.javamail.MimeMessageHelper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.StringUtils;\n\nimport javax.mail.MessagingException;\nimport javax.mail.internet.MimeMessage;\nimport java.io.IOException;\nimport java.text.MessageFormat;\n\n@Service\n@RefreshScope\npublic class EmailServiceImpl implements EmailService {\n\n\tprivate final Logger log = LoggerFactory.getLogger(getClass());\n\n\t@Autowired\n\tprivate JavaMailSender mailSender;\n\n\t@Autowired\n\tprivate Environment env;\n\n\t@Override\n\tpublic void send(NotificationType type, Recipient recipient, String attachment) throws MessagingException, IOException {\n\n\t\tfinal String subject = env.getProperty(type.getSubject());\n\t\tfinal String text = MessageFormat.format(env.getProperty(type.getText()), recipient.getAccountName());\n\n\t\tMimeMessage message = mailSender.createMimeMessage();\n\n\t\tMimeMessageHelper helper = new MimeMessageHelper(message, true);\n\t\thelper.setTo(recipient.getEmail());\n\t\thelper.setSubject(subject);\n\t\thelper.setText(text);\n\n\t\tif (StringUtils.hasLength(attachment)) {\n\t\t\thelper.addAttachment(env.getProperty(type.getAttachment()), new ByteArrayResource(attachment.getBytes()));\n\t\t}\n\n\t\tmailSender.send(message);\n\n\t\tlog.info(\"{} email notification has been send to {}\", type, recipient.getEmail());\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/service/NotificationService.java",
    "content": "package com.piggymetrics.notification.service;\n\npublic interface NotificationService {\n\n\tvoid sendBackupNotifications();\n\n\tvoid sendRemindNotifications();\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/service/NotificationServiceImpl.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.piggymetrics.notification.client.AccountServiceClient;\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\n@Service\npublic class NotificationServiceImpl implements NotificationService {\n\n\tprivate final Logger log = LoggerFactory.getLogger(getClass());\n\n\t@Autowired\n\tprivate AccountServiceClient client;\n\n\t@Autowired\n\tprivate RecipientService recipientService;\n\n\t@Autowired\n\tprivate EmailService emailService;\n\n\t@Override\n\t@Scheduled(cron = \"${backup.cron}\")\n\tpublic void sendBackupNotifications() {\n\n\t\tfinal NotificationType type = NotificationType.BACKUP;\n\n\t\tList<Recipient> recipients = recipientService.findReadyToNotify(type);\n\t\tlog.info(\"found {} recipients for backup notification\", recipients.size());\n\n\t\trecipients.forEach(recipient -> CompletableFuture.runAsync(() -> {\n\t\t\ttry {\n\t\t\t\tString attachment = client.getAccount(recipient.getAccountName());\n\t\t\t\temailService.send(type, recipient, attachment);\n\t\t\t\trecipientService.markNotified(type, recipient);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlog.error(\"an error during backup notification for {}\", recipient, t);\n\t\t\t}\n\t\t}));\n\t}\n\n\t@Override\n\t@Scheduled(cron = \"${remind.cron}\")\n\tpublic void sendRemindNotifications() {\n\n\t\tfinal NotificationType type = NotificationType.REMIND;\n\n\t\tList<Recipient> recipients = recipientService.findReadyToNotify(type);\n\t\tlog.info(\"found {} recipients for remind notification\", recipients.size());\n\n\t\trecipients.forEach(recipient -> CompletableFuture.runAsync(() -> {\n\t\t\ttry {\n\t\t\t\temailService.send(type, recipient, null);\n\t\t\t\trecipientService.markNotified(type, recipient);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlog.error(\"an error during remind notification for {}\", recipient, t);\n\t\t\t}\n\t\t}));\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/service/RecipientService.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\n\nimport java.util.List;\n\npublic interface RecipientService {\n\n\t/**\n\t * Finds recipient by account name\n\t *\n\t * @param accountName\n\t * @return recipient\n\t */\n\tRecipient findByAccountName(String accountName);\n\n\t/**\n\t * Finds recipients, which are ready to be notified\n\t * at the moment\n\t *\n\t * @param type\n\t * @return recipients to notify\n\t */\n\tList<Recipient> findReadyToNotify(NotificationType type);\n\n\t/**\n\t * Creates or updates recipient settings\n\t *\n\t * @param accountName\n\t * @param recipient\n\t * @return updated recipient\n\t */\n\tRecipient save(String accountName, Recipient recipient);\n\n\t/**\n\t * Updates {@link NotificationType} {@code lastNotified} property with current date\n\t * for given recipient.\n\t *\n\t * @param type\n\t * @param recipient\n\t */\n\tvoid markNotified(NotificationType type, Recipient recipient);\n}\n"
  },
  {
    "path": "notification-service/src/main/java/com/piggymetrics/notification/service/RecipientServiceImpl.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport com.piggymetrics.notification.repository.RecipientRepository;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.Assert;\n\nimport java.util.Date;\nimport java.util.List;\n\n@Service\npublic class RecipientServiceImpl implements RecipientService {\n\n\tprivate final Logger log = LoggerFactory.getLogger(getClass());\n\n\t@Autowired\n\tprivate RecipientRepository repository;\n\n\t@Override\n\tpublic Recipient findByAccountName(String accountName) {\n\t\tAssert.hasLength(accountName);\n\t\treturn repository.findByAccountName(accountName);\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic Recipient save(String accountName, Recipient recipient) {\n\n\t\trecipient.setAccountName(accountName);\n\t\trecipient.getScheduledNotifications().values()\n\t\t\t\t.forEach(settings -> {\n\t\t\t\t\tif (settings.getLastNotified() == null) {\n\t\t\t\t\t\tsettings.setLastNotified(new Date());\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\trepository.save(recipient);\n\n\t\tlog.info(\"recipient {} settings has been updated\", recipient);\n\n\t\treturn recipient;\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic List<Recipient> findReadyToNotify(NotificationType type) {\n\t\tswitch (type) {\n\t\t\tcase BACKUP:\n\t\t\t\treturn repository.findReadyForBackup();\n\t\t\tcase REMIND:\n\t\t\t\treturn repository.findReadyForRemind();\n\t\t\tdefault:\n\t\t\t\tthrow new IllegalArgumentException();\n\t\t}\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic void markNotified(NotificationType type, Recipient recipient) {\n\t\trecipient.getScheduledNotifications().get(type).setLastNotified(new Date());\n\t\trepository.save(recipient);\n\t}\n}\n"
  },
  {
    "path": "notification-service/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: notification-service\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user"
  },
  {
    "path": "notification-service/src/test/java/com/piggymetrics/notification/NotificationServiceApplicationTests.java",
    "content": "package com.piggymetrics.notification;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class NotificationServiceApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "notification-service/src/test/java/com/piggymetrics/notification/controller/RecipientControllerTest.java",
    "content": "package com.piggymetrics.notification.controller;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.collect.ImmutableMap;\nimport com.piggymetrics.notification.domain.Frequency;\nimport com.piggymetrics.notification.domain.NotificationSettings;\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport com.piggymetrics.notification.service.RecipientService;\nimport com.sun.security.auth.UserPrincipal;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\n\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class RecipientControllerTest {\n\n\tprivate static final ObjectMapper mapper = new ObjectMapper();\n\n\t@InjectMocks\n\tprivate RecipientController recipientController;\n\n\t@Mock\n\tprivate RecipientService recipientService;\n\n\tprivate MockMvc mockMvc;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t\tthis.mockMvc = MockMvcBuilders.standaloneSetup(recipientController).build();\n\t}\n\n\t@Test\n\tpublic void shouldSaveCurrentRecipientSettings() throws Exception {\n\n\t\tRecipient recipient = getStubRecipient();\n\t\tString json = mapper.writeValueAsString(recipient);\n\n\t\tmockMvc.perform(put(\"/recipients/current\").principal(new UserPrincipal(recipient.getAccountName())).contentType(MediaType.APPLICATION_JSON).content(json))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldGetCurrentRecipientSettings() throws Exception {\n\n\t\tRecipient recipient = getStubRecipient();\n\t\twhen(recipientService.findByAccountName(recipient.getAccountName())).thenReturn(recipient);\n\n\t\tmockMvc.perform(get(\"/recipients/current\").principal(new UserPrincipal(recipient.getAccountName())))\n\t\t\t\t.andExpect(jsonPath(\"$.accountName\").value(recipient.getAccountName()))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\tprivate Recipient getStubRecipient() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(true);\n\t\tremind.setFrequency(Frequency.WEEKLY);\n\t\tremind.setLastNotified(null);\n\n\t\tNotificationSettings backup = new NotificationSettings();\n\t\tbackup.setActive(false);\n\t\tbackup.setFrequency(Frequency.MONTHLY);\n\t\tbackup.setLastNotified(null);\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.BACKUP, backup,\n\t\t\t\tNotificationType.REMIND, remind\n\t\t));\n\n\t\treturn recipient;\n\t}\n}"
  },
  {
    "path": "notification-service/src/test/java/com/piggymetrics/notification/repository/RecipientRepositoryTest.java",
    "content": "package com.piggymetrics.notification.repository;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.piggymetrics.notification.domain.Frequency;\nimport com.piggymetrics.notification.domain.NotificationSettings;\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport org.apache.commons.lang.time.DateUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.Date;\nimport java.util.List;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(SpringRunner.class)\n@DataMongoTest\npublic class RecipientRepositoryTest {\n\n\t@Autowired\n\tprivate RecipientRepository repository;\n\n\t@Test\n\tpublic void shouldFindByAccountName() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(true);\n\t\tremind.setFrequency(Frequency.WEEKLY);\n\t\tremind.setLastNotified(new Date(0));\n\n\t\tNotificationSettings backup = new NotificationSettings();\n\t\tbackup.setActive(false);\n\t\tbackup.setFrequency(Frequency.MONTHLY);\n\t\tbackup.setLastNotified(new Date());\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.BACKUP, backup,\n\t\t\t\tNotificationType.REMIND, remind\n\t\t));\n\n\t\trepository.save(recipient);\n\n\t\tRecipient found = repository.findByAccountName(recipient.getAccountName());\n\t\tassertEquals(recipient.getAccountName(), found.getAccountName());\n\t\tassertEquals(recipient.getEmail(), found.getEmail());\n\n\t\tassertEquals(recipient.getScheduledNotifications().get(NotificationType.BACKUP).getActive(),\n\t\t\t\tfound.getScheduledNotifications().get(NotificationType.BACKUP).getActive());\n\t\tassertEquals(recipient.getScheduledNotifications().get(NotificationType.BACKUP).getFrequency(),\n\t\t\t\tfound.getScheduledNotifications().get(NotificationType.BACKUP).getFrequency());\n\t\tassertEquals(recipient.getScheduledNotifications().get(NotificationType.BACKUP).getLastNotified(),\n\t\t\t\tfound.getScheduledNotifications().get(NotificationType.BACKUP).getLastNotified());\n\n\t\tassertEquals(recipient.getScheduledNotifications().get(NotificationType.REMIND).getActive(),\n\t\t\t\tfound.getScheduledNotifications().get(NotificationType.REMIND).getActive());\n\t\tassertEquals(recipient.getScheduledNotifications().get(NotificationType.REMIND).getFrequency(),\n\t\t\t\tfound.getScheduledNotifications().get(NotificationType.REMIND).getFrequency());\n\t\tassertEquals(recipient.getScheduledNotifications().get(NotificationType.REMIND).getLastNotified(),\n\t\t\t\tfound.getScheduledNotifications().get(NotificationType.REMIND).getLastNotified());\n\t}\n\n\t@Test\n\tpublic void shouldFindReadyForRemindWhenFrequencyIsWeeklyAndLastNotifiedWas8DaysAgo() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(true);\n\t\tremind.setFrequency(Frequency.WEEKLY);\n\t\tremind.setLastNotified(DateUtils.addDays(new Date(), -8));\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.REMIND, remind\n\t\t));\n\n\t\trepository.save(recipient);\n\n\t\tList<Recipient> found = repository.findReadyForRemind();\n\t\tassertFalse(found.isEmpty());\n\t}\n\n\t@Test\n\tpublic void shouldNotFindReadyForRemindWhenFrequencyIsWeeklyAndLastNotifiedWasYesterday() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(true);\n\t\tremind.setFrequency(Frequency.WEEKLY);\n\t\tremind.setLastNotified(DateUtils.addDays(new Date(), -1));\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.REMIND, remind\n\t\t));\n\n\t\trepository.save(recipient);\n\n\t\tList<Recipient> found = repository.findReadyForRemind();\n\t\tassertTrue(found.isEmpty());\n\t}\n\n\t@Test\n\tpublic void shouldNotFindReadyForRemindWhenNotificationIsNotActive() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(false);\n\t\tremind.setFrequency(Frequency.WEEKLY);\n\t\tremind.setLastNotified(DateUtils.addDays(new Date(), -30));\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.REMIND, remind\n\t\t));\n\n\t\trepository.save(recipient);\n\n\t\tList<Recipient> found = repository.findReadyForRemind();\n\t\tassertTrue(found.isEmpty());\n\t}\n\n\t@Test\n\tpublic void shouldNotFindReadyForBackupWhenFrequencyIsQuaterly() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(true);\n\t\tremind.setFrequency(Frequency.QUARTERLY);\n\t\tremind.setLastNotified(DateUtils.addDays(new Date(), -91));\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.BACKUP, remind\n\t\t));\n\n\t\trepository.save(recipient);\n\n\t\tList<Recipient> found = repository.findReadyForBackup();\n\t\tassertFalse(found.isEmpty());\n\t}\n}"
  },
  {
    "path": "notification-service/src/test/java/com/piggymetrics/notification/service/EmailServiceImplTest.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.springframework.core.env.Environment;\nimport org.springframework.mail.javamail.JavaMailSender;\n\nimport javax.mail.MessagingException;\nimport javax.mail.Session;\nimport javax.mail.internet.MimeMessage;\nimport java.io.IOException;\nimport java.util.Properties;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class EmailServiceImplTest {\n\n\t@InjectMocks\n\tprivate EmailServiceImpl emailService;\n\n\t@Mock\n\tprivate JavaMailSender mailSender;\n\n\t@Mock\n\tprivate Environment env;\n\n\t@Captor\n\tprivate ArgumentCaptor<MimeMessage> captor;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t\twhen(mailSender.createMimeMessage())\n\t\t\t\t.thenReturn(new MimeMessage(Session.getDefaultInstance(new Properties())));\n\t}\n\n\t@Test\n\tpublic void shouldSendBackupEmail() throws MessagingException, IOException {\n\n\t\tfinal String subject = \"subject\";\n\t\tfinal String text = \"text\";\n\t\tfinal String attachment = \"attachment.json\";\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\n\t\twhen(env.getProperty(NotificationType.BACKUP.getSubject())).thenReturn(subject);\n\t\twhen(env.getProperty(NotificationType.BACKUP.getText())).thenReturn(text);\n\t\twhen(env.getProperty(NotificationType.BACKUP.getAttachment())).thenReturn(attachment);\n\n\t\temailService.send(NotificationType.BACKUP, recipient, \"{\\\"name\\\":\\\"test\\\"\");\n\n\t\tverify(mailSender).send(captor.capture());\n\n\t\tMimeMessage message = captor.getValue();\n\t\tassertEquals(subject, message.getSubject());\n\t\t// TODO check other fields\n\t}\n\n\t@Test\n\tpublic void shouldSendRemindEmail() throws MessagingException, IOException {\n\n\t\tfinal String subject = \"subject\";\n\t\tfinal String text = \"text\";\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\n\t\twhen(env.getProperty(NotificationType.REMIND.getSubject())).thenReturn(subject);\n\t\twhen(env.getProperty(NotificationType.REMIND.getText())).thenReturn(text);\n\n\t\temailService.send(NotificationType.REMIND, recipient, null);\n\n\t\tverify(mailSender).send(captor.capture());\n\n\t\tMimeMessage message = captor.getValue();\n\t\tassertEquals(subject, message.getSubject());\n\t\t// TODO check other fields\n\t}\n}"
  },
  {
    "path": "notification-service/src/test/java/com/piggymetrics/notification/service/NotificationServiceImplTest.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.google.common.collect.ImmutableList;\nimport com.piggymetrics.notification.client.AccountServiceClient;\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport javax.mail.MessagingException;\nimport java.io.IOException;\n\nimport static org.mockito.Mockito.*;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class NotificationServiceImplTest {\n\n\t@InjectMocks\n\tprivate NotificationServiceImpl notificationService;\n\n\t@Mock\n\tprivate RecipientService recipientService;\n\n\t@Mock\n\tprivate AccountServiceClient client;\n\n\t@Mock\n\tprivate EmailService emailService;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t}\n\n\t@Test\n\tpublic void shouldSendBackupNotificationsEvenWhenErrorsOccursForSomeRecipients() throws IOException, MessagingException, InterruptedException {\n\n\t\tfinal String attachment = \"json\";\n\n\t\tRecipient withError = new Recipient();\n\t\twithError.setAccountName(\"with-error\");\n\n\t\tRecipient withNoError = new Recipient();\n\t\twithNoError.setAccountName(\"with-no-error\");\n\n\t\twhen(client.getAccount(withError.getAccountName())).thenThrow(new RuntimeException());\n\t\twhen(client.getAccount(withNoError.getAccountName())).thenReturn(attachment);\n\n\t\twhen(recipientService.findReadyToNotify(NotificationType.BACKUP)).thenReturn(ImmutableList.of(withNoError, withError));\n\n\t\tnotificationService.sendBackupNotifications();\n\n\t\t// TODO test concurrent code in a right way\n\n\t\tverify(emailService, timeout(100)).send(NotificationType.BACKUP, withNoError, attachment);\n\t\tverify(recipientService, timeout(100)).markNotified(NotificationType.BACKUP, withNoError);\n\n\t\tverify(recipientService, never()).markNotified(NotificationType.BACKUP, withError);\n\t}\n\n\t@Test\n\tpublic void shouldSendRemindNotificationsEvenWhenErrorsOccursForSomeRecipients() throws IOException, MessagingException, InterruptedException {\n\n\t\tfinal String attachment = \"json\";\n\n\t\tRecipient withError = new Recipient();\n\t\twithError.setAccountName(\"with-error\");\n\n\t\tRecipient withNoError = new Recipient();\n\t\twithNoError.setAccountName(\"with-no-error\");\n\n\t\twhen(recipientService.findReadyToNotify(NotificationType.REMIND)).thenReturn(ImmutableList.of(withNoError, withError));\n\t\tdoThrow(new RuntimeException()).when(emailService).send(NotificationType.REMIND, withError, null);\n\n\t\tnotificationService.sendRemindNotifications();\n\n\t\t// TODO test concurrent code in a right way\n\n\t\tverify(emailService, timeout(100)).send(NotificationType.REMIND, withNoError, null);\n\t\tverify(recipientService, timeout(100)).markNotified(NotificationType.REMIND, withNoError);\n\n\t\tverify(recipientService, never()).markNotified(NotificationType.REMIND, withError);\n\t}\n}"
  },
  {
    "path": "notification-service/src/test/java/com/piggymetrics/notification/service/RecipientServiceImplTest.java",
    "content": "package com.piggymetrics.notification.service;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.piggymetrics.notification.domain.Frequency;\nimport com.piggymetrics.notification.domain.NotificationSettings;\nimport com.piggymetrics.notification.domain.NotificationType;\nimport com.piggymetrics.notification.domain.Recipient;\nimport com.piggymetrics.notification.repository.RecipientRepository;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.util.Date;\nimport java.util.List;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class RecipientServiceImplTest {\n\n\t@InjectMocks\n\tprivate RecipientServiceImpl recipientService;\n\n\t@Mock\n\tprivate RecipientRepository repository;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t}\n\n\t@Test\n\tpublic void shouldFindByAccountName() {\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\n\t\twhen(repository.findByAccountName(recipient.getAccountName())).thenReturn(recipient);\n\t\tRecipient found = recipientService.findByAccountName(recipient.getAccountName());\n\n\t\tassertEquals(recipient, found);\n\t}\n\n\t@Test(expected = IllegalArgumentException.class)\n\tpublic void shouldFailToFindRecipientWhenAccountNameIsEmpty() {\n\t\trecipientService.findByAccountName(\"\");\n\t}\n\n\t@Test\n\tpublic void shouldSaveRecipient() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(true);\n\t\tremind.setFrequency(Frequency.WEEKLY);\n\t\tremind.setLastNotified(null);\n\n\t\tNotificationSettings backup = new NotificationSettings();\n\t\tbackup.setActive(false);\n\t\tbackup.setFrequency(Frequency.MONTHLY);\n\t\tbackup.setLastNotified(new Date());\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.BACKUP, backup,\n\t\t\t\tNotificationType.REMIND, remind\n\t\t));\n\n\t\tRecipient saved = recipientService.save(\"test\", recipient);\n\n\t\tverify(repository).save(recipient);\n\t\tassertNotNull(saved.getScheduledNotifications().get(NotificationType.REMIND).getLastNotified());\n\t\tassertEquals(\"test\", saved.getAccountName());\n\t}\n\n\t@Test\n\tpublic void shouldFindReadyToNotifyWhenNotificationTypeIsBackup() {\n\t\tfinal List<Recipient> recipients = ImmutableList.of(new Recipient());\n\t\twhen(repository.findReadyForBackup()).thenReturn(recipients);\n\n\t\tList<Recipient> found = recipientService.findReadyToNotify(NotificationType.BACKUP);\n\t\tassertEquals(recipients, found);\n\t}\n\n\t@Test\n\tpublic void shouldFindReadyToNotifyWhenNotificationTypeIsRemind() {\n\t\tfinal List<Recipient> recipients = ImmutableList.of(new Recipient());\n\t\twhen(repository.findReadyForRemind()).thenReturn(recipients);\n\n\t\tList<Recipient> found = recipientService.findReadyToNotify(NotificationType.REMIND);\n\t\tassertEquals(recipients, found);\n\t}\n\n\t@Test\n\tpublic void shouldMarkAsNotified() {\n\n\t\tNotificationSettings remind = new NotificationSettings();\n\t\tremind.setActive(true);\n\t\tremind.setFrequency(Frequency.WEEKLY);\n\t\tremind.setLastNotified(null);\n\n\t\tRecipient recipient = new Recipient();\n\t\trecipient.setAccountName(\"test\");\n\t\trecipient.setEmail(\"test@test.com\");\n\t\trecipient.setScheduledNotifications(ImmutableMap.of(\n\t\t\t\tNotificationType.REMIND, remind\n\t\t));\n\n\t\trecipientService.markNotified(NotificationType.REMIND, recipient);\n\t\tassertNotNull(recipient.getScheduledNotifications().get(NotificationType.REMIND).getLastNotified());\n\t\tverify(repository).save(recipient);\n\t}\n}"
  },
  {
    "path": "notification-service/src/test/resources/application.yml",
    "content": "remind:\n  cron: 0 0 0 * * *\n  email:\n    text: \"Hey, {0}! We''ve missed you here on PiggyMetrics. It''s time to check your budget statistics.\\r\\n\\r\\nCheers,\\r\\nPiggyMetrics team\"\n    subject: PiggyMetrics reminder\n\nbackup:\n  cron: 0 0 12 * * *\n  email:\n    text: \"Howdy, {0}. Your account backup is ready.\\r\\n\\r\\nCheers,\\r\\nPiggyMetrics team\"\n    subject: PiggyMetrics account backup\n    attachment: backup.json\n\nspring:\n  data:\n    mongodb:\n      database: piggymetrics\n      port: 0\n  mail:\n    host: smtp.gmail.com\n    port: 465\n    username: test\n    password: test\n    properties:\n      mail:\n        smtp:\n          auth: true\n          socketFactory:\n            port: 465\n            class: javax.net.ssl.SSLSocketFactory\n            fallback: false\n          ssl:\n            enable: true\n"
  },
  {
    "path": "notification-service/src/test/resources/bootstrap.yml",
    "content": "eureka:\n  client:\n    enabled: false"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t\t xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<groupId>com.piggymetrics</groupId>\n\t<artifactId>piggymetrics</artifactId>\n\t<version>1.0-SNAPSHOT</version>\n\t<packaging>pom</packaging>\n\t<name>piggymetrics</name>\n\n\t<parent>\n\t\t<groupId>org.springframework.boot</groupId>\n\t\t<artifactId>spring-boot-starter-parent</artifactId>\n\t\t<version>2.0.3.RELEASE</version>\n\t\t<relativePath/> <!-- lookup parent from repository -->\n\t</parent>\n\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>\n\t\t<java.version>1.8</java.version>\n\t</properties>\n\n\t<dependencyManagement>\n\t\t<dependencies>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-dependencies</artifactId>\n\t\t\t\t<version>${spring-cloud.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t</dependencies>\n\t</dependencyManagement>\n\t\n\t<modules>\n\t\t<module>config</module>\n\t\t<module>monitoring</module>\n\t\t<module>registry</module>\n\t\t<module>gateway</module>\n\t\t<module>auth-service</module>\n\t\t<module>account-service</module>\n\t\t<module>statistics-service</module>\n\t\t<module>notification-service</module>\n\t\t<module>turbine-stream-service</module>\n\t</modules>\n\n</project>\n"
  },
  {
    "path": "registry/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/registry.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/registry.jar\"]\n\nEXPOSE 8761"
  },
  {
    "path": "registry/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>registry</artifactId>\n\t<version>0.0.1-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>registry</name>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>\n        </dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\t\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>${project.name}</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\t\n</project>\n"
  },
  {
    "path": "registry/src/main/java/com/piggymetrics/registry/RegistryApplication.java",
    "content": "package com.piggymetrics.registry;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;\n\n@SpringBootApplication\n@EnableEurekaServer\npublic class RegistryApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(RegistryApplication.class, args);\n\t}\n}\n"
  },
  {
    "path": "registry/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: registry\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user\n\neureka:\n  instance:\n    prefer-ip-address: true\n  client:\n    registerWithEureka: false\n    fetchRegistry: false\n    server:\n      waitTimeInMsWhenSyncEmpty: 0"
  },
  {
    "path": "statistics-service/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Alexander Lukyanchikov <sqshq@sqshq.com>\n\nADD ./target/statistics-service.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/statistics-service.jar\"]\n\nEXPOSE 7000"
  },
  {
    "path": "statistics-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>statistics-service</artifactId>\n\t<version>1.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>statistics-service</name>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-security</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-oauth2</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-openfeign</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-sleuth</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-data-mongodb</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-bus-amqp</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.google.guava</groupId>\n\t\t\t<artifactId>guava</artifactId>\n\t\t\t<version>19.0</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>de.flapdoodle.embed</groupId>\n\t\t\t<artifactId>de.flapdoodle.embed.mongo</artifactId>\n\t\t\t<version>1.50.3</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.jayway.jsonpath</groupId>\n\t\t\t<artifactId>json-path</artifactId>\n\t\t\t<version>2.2.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\t\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>statistics-service</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t<version>0.7.6.201602180812</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>report</id>\n\t\t\t\t\t\t<phase>test</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>report</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/StatisticsApplication.java",
    "content": "package com.piggymetrics.statistics;\n\nimport com.piggymetrics.statistics.repository.converter.DataPointIdReaderConverter;\nimport com.piggymetrics.statistics.repository.converter.DataPointIdWriterConverter;\nimport com.piggymetrics.statistics.service.security.CustomUserInfoTokenServices;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.cloud.openfeign.EnableFeignClients;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.mongodb.core.convert.CustomConversions;\nimport org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;\nimport org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;\n\nimport java.util.Arrays;\n\n@SpringBootApplication\n@EnableDiscoveryClient\n@EnableOAuth2Client\n@EnableFeignClients\n@EnableGlobalMethodSecurity(prePostEnabled = true)\npublic class StatisticsApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(StatisticsApplication.class, args);\n\t}\n\n\t@Configuration\n\tstatic class CustomConversionsConfig {\n\n\t\t@Bean\n\t\tpublic CustomConversions customConversions() {\n\t\t\treturn new CustomConversions(Arrays.asList(new DataPointIdReaderConverter(),\n\t\t\t\t\tnew DataPointIdWriterConverter()));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/client/ExchangeRatesClient.java",
    "content": "package com.piggymetrics.statistics.client;\n\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.ExchangeRatesContainer;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RequestParam;\n\n@FeignClient(url = \"${rates.url}\", name = \"rates-client\", fallback = ExchangeRatesClientFallback.class)\npublic interface ExchangeRatesClient {\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/latest\")\n    ExchangeRatesContainer getRates(@RequestParam(\"base\") Currency base);\n\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/client/ExchangeRatesClientFallback.java",
    "content": "package com.piggymetrics.statistics.client;\n\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.ExchangeRatesContainer;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Collections;\n\n@Component\npublic class ExchangeRatesClientFallback implements ExchangeRatesClient {\n\n    @Override\n    public ExchangeRatesContainer getRates(Currency base) {\n        ExchangeRatesContainer container = new ExchangeRatesContainer();\n        container.setBase(Currency.getBase());\n        container.setRates(Collections.emptyMap());\n        return container;\n    }\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/config/ResourceServerConfig.java",
    "content": "package com.piggymetrics.statistics.config;\n\nimport com.piggymetrics.statistics.service.security.CustomUserInfoTokenServices;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;\nimport org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;\nimport org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;\n\n/**\n * @author cdov\n */\n@EnableResourceServer\n@Configuration\npublic class ResourceServerConfig extends ResourceServerConfigurerAdapter {\n    @Autowired\n    private ResourceServerProperties sso;\n\n    @Bean\n    public ResourceServerTokenServices tokenServices() {\n        return new CustomUserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());\n    }\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/controller/StatisticsController.java",
    "content": "package com.piggymetrics.statistics.controller;\n\nimport com.piggymetrics.statistics.domain.Account;\nimport com.piggymetrics.statistics.domain.timeseries.DataPoint;\nimport com.piggymetrics.statistics.service.StatisticsService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.validation.Valid;\nimport java.security.Principal;\nimport java.util.List;\n\n@RestController\npublic class StatisticsController {\n\n\t@Autowired\n\tprivate StatisticsService statisticsService;\n\n\t@RequestMapping(value = \"/current\", method = RequestMethod.GET)\n\tpublic List<DataPoint> getCurrentAccountStatistics(Principal principal) {\n\t\treturn statisticsService.findByAccountName(principal.getName());\n\t}\n\n\t@PreAuthorize(\"#oauth2.hasScope('server') or #accountName.equals('demo')\")\n\t@RequestMapping(value = \"/{accountName}\", method = RequestMethod.GET)\n\tpublic List<DataPoint> getStatisticsByAccountName(@PathVariable String accountName) {\n\t\treturn statisticsService.findByAccountName(accountName);\n\t}\n\n\t@PreAuthorize(\"#oauth2.hasScope('server')\")\n\t@RequestMapping(value = \"/{accountName}\", method = RequestMethod.PUT)\n\tpublic void saveAccountStatistics(@PathVariable String accountName, @Valid @RequestBody Account account) {\n\t\tstatisticsService.save(accountName, account);\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/Account.java",
    "content": "package com.piggymetrics.statistics.domain;\n\nimport org.codehaus.jackson.annotate.JsonIgnoreProperties;\nimport org.springframework.data.mongodb.core.mapping.Document;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotNull;\nimport java.util.List;\n\n@Document(collection = \"accounts\")\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class Account {\n\n\t@Valid\n\t@NotNull\n\tprivate List<Item> incomes;\n\n\t@Valid\n\t@NotNull\n\tprivate List<Item> expenses;\n\n\t@Valid\n\t@NotNull\n\tprivate Saving saving;\n\n\tpublic List<Item> getIncomes() {\n\t\treturn incomes;\n\t}\n\n\tpublic void setIncomes(List<Item> incomes) {\n\t\tthis.incomes = incomes;\n\t}\n\n\tpublic List<Item> getExpenses() {\n\t\treturn expenses;\n\t}\n\n\tpublic void setExpenses(List<Item> expenses) {\n\t\tthis.expenses = expenses;\n\t}\n\n\tpublic Saving getSaving() {\n\t\treturn saving;\n\t}\n\n\tpublic void setSaving(Saving saving) {\n\t\tthis.saving = saving;\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/Currency.java",
    "content": "package com.piggymetrics.statistics.domain;\n\npublic enum Currency {\n\n\tUSD, EUR, RUB;\n\n\tpublic static Currency getBase() {\n\t\treturn USD;\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/ExchangeRatesContainer.java",
    "content": "package com.piggymetrics.statistics.domain;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDate;\nimport java.util.Map;\n\n@JsonIgnoreProperties(ignoreUnknown = true, value = {\"date\"})\npublic class ExchangeRatesContainer {\n\n\tprivate LocalDate date = LocalDate.now();\n\n\tprivate Currency base;\n\n\tprivate Map<String, BigDecimal> rates;\n\n\tpublic LocalDate getDate() {\n\t\treturn date;\n\t}\n\n\tpublic void setDate(LocalDate date) {\n\t\tthis.date = date;\n\t}\n\n\tpublic Currency getBase() {\n\t\treturn base;\n\t}\n\n\tpublic void setBase(Currency base) {\n\t\tthis.base = base;\n\t}\n\n\tpublic Map<String, BigDecimal> getRates() {\n\t\treturn rates;\n\t}\n\n\tpublic void setRates(Map<String, BigDecimal> rates) {\n\t\tthis.rates = rates;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"RateList{\" +\n\t\t\t\t\"date=\" + date +\n\t\t\t\t\", base=\" + base +\n\t\t\t\t\", rates=\" + rates +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/Item.java",
    "content": "package com.piggymetrics.statistics.domain;\n\nimport org.hibernate.validator.constraints.Length;\n\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\npublic class Item {\n\n\t@NotNull\n\t@Length(min = 1, max = 20)\n\tprivate String title;\n\n\t@NotNull\n\tprivate BigDecimal amount;\n\n\t@NotNull\n\tprivate Currency currency;\n\n\t@NotNull\n\tprivate TimePeriod period;\n\n\tpublic String getTitle() {\n\t\treturn title;\n\t}\n\n\tpublic void setTitle(String title) {\n\t\tthis.title = title;\n\t}\n\n\tpublic BigDecimal getAmount() {\n\t\treturn amount;\n\t}\n\n\tpublic void setAmount(BigDecimal amount) {\n\t\tthis.amount = amount;\n\t}\n\n\tpublic Currency getCurrency() {\n\t\treturn currency;\n\t}\n\n\tpublic void setCurrency(Currency currency) {\n\t\tthis.currency = currency;\n\t}\n\n\tpublic TimePeriod getPeriod() {\n\t\treturn period;\n\t}\n\n\tpublic void setPeriod(TimePeriod period) {\n\t\tthis.period = period;\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/Saving.java",
    "content": "package com.piggymetrics.statistics.domain;\n\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\npublic class Saving {\n\n\t@NotNull\n\tprivate BigDecimal amount;\n\n\t@NotNull\n\tprivate Currency currency;\n\n\t@NotNull\n\tprivate BigDecimal interest;\n\n\t@NotNull\n\tprivate Boolean deposit;\n\n\t@NotNull\n\tprivate Boolean capitalization;\n\n\tpublic BigDecimal getAmount() {\n\t\treturn amount;\n\t}\n\n\tpublic void setAmount(BigDecimal amount) {\n\t\tthis.amount = amount;\n\t}\n\n\tpublic Currency getCurrency() {\n\t\treturn currency;\n\t}\n\n\tpublic void setCurrency(Currency currency) {\n\t\tthis.currency = currency;\n\t}\n\n\tpublic BigDecimal getInterest() {\n\t\treturn interest;\n\t}\n\n\tpublic void setInterest(BigDecimal interest) {\n\t\tthis.interest = interest;\n\t}\n\n\tpublic Boolean getDeposit() {\n\t\treturn deposit;\n\t}\n\n\tpublic void setDeposit(Boolean deposit) {\n\t\tthis.deposit = deposit;\n\t}\n\n\tpublic Boolean getCapitalization() {\n\t\treturn capitalization;\n\t}\n\n\tpublic void setCapitalization(Boolean capitalization) {\n\t\tthis.capitalization = capitalization;\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/TimePeriod.java",
    "content": "package com.piggymetrics.statistics.domain;\n\nimport java.math.BigDecimal;\n\npublic enum TimePeriod {\n\n\tYEAR(365.2425), QUARTER(91.3106), MONTH(30.4368), DAY(1), HOUR(0.0416);\n\n\tprivate double baseRatio;\n\n\tTimePeriod(double baseRatio) {\n\t\tthis.baseRatio = baseRatio;\n\t}\n\n\tpublic BigDecimal getBaseRatio() {\n\t\treturn new BigDecimal(baseRatio);\n\t}\n\n\tpublic static TimePeriod getBase() {\n\t\treturn DAY;\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/timeseries/DataPoint.java",
    "content": "package com.piggymetrics.statistics.domain.timeseries;\n\nimport com.piggymetrics.statistics.domain.Currency;\nimport org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\n\nimport java.math.BigDecimal;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Represents daily time series data point containing\n * current account state\n */\n@Document(collection = \"datapoints\")\npublic class DataPoint {\n\n\t@Id\n\tprivate DataPointId id;\n\n\tprivate Set<ItemMetric> incomes;\n\n\tprivate Set<ItemMetric> expenses;\n\n\tprivate Map<StatisticMetric, BigDecimal> statistics;\n\n\tprivate Map<Currency, BigDecimal> rates;\n\n\tpublic DataPointId getId() {\n\t\treturn id;\n\t}\n\n\tpublic void setId(DataPointId id) {\n\t\tthis.id = id;\n\t}\n\n\tpublic Set<ItemMetric> getIncomes() {\n\t\treturn incomes;\n\t}\n\n\tpublic void setIncomes(Set<ItemMetric> incomes) {\n\t\tthis.incomes = incomes;\n\t}\n\n\tpublic Set<ItemMetric> getExpenses() {\n\t\treturn expenses;\n\t}\n\n\tpublic void setExpenses(Set<ItemMetric> expenses) {\n\t\tthis.expenses = expenses;\n\t}\n\n\tpublic Map<StatisticMetric, BigDecimal> getStatistics() {\n\t\treturn statistics;\n\t}\n\n\tpublic void setStatistics(Map<StatisticMetric, BigDecimal> statistics) {\n\t\tthis.statistics = statistics;\n\t}\n\n\tpublic Map<Currency, BigDecimal> getRates() {\n\t\treturn rates;\n\t}\n\n\tpublic void setRates(Map<Currency, BigDecimal> rates) {\n\t\tthis.rates = rates;\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/timeseries/DataPointId.java",
    "content": "package com.piggymetrics.statistics.domain.timeseries;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class DataPointId implements Serializable {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\tprivate String account;\n\n\tprivate Date date;\n\n\tpublic DataPointId(String account, Date date) {\n\t\tthis.account = account;\n\t\tthis.date = date;\n\t}\n\n\tpublic String getAccount() {\n\t\treturn account;\n\t}\n\n\tpublic Date getDate() {\n\t\treturn date;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"DataPointId{\" +\n\t\t\t\t\"account='\" + account + '\\'' +\n\t\t\t\t\", date=\" + date +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/timeseries/ItemMetric.java",
    "content": "package com.piggymetrics.statistics.domain.timeseries;\n\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.TimePeriod;\n\nimport java.math.BigDecimal;\n\n/**\n * Represents normalized {@link com.piggymetrics.statistics.domain.Item} object\n * with {@link Currency#getBase()} currency and {@link TimePeriod#getBase()} time period\n */\npublic class ItemMetric {\n\n\tprivate String title;\n\n\tprivate BigDecimal amount;\n\n\tpublic ItemMetric(String title, BigDecimal amount) {\n\t\tthis.title = title;\n\t\tthis.amount = amount;\n\t}\n\n\tpublic String getTitle() {\n\t\treturn title;\n\t}\n\n\tpublic BigDecimal getAmount() {\n\t\treturn amount;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tItemMetric that = (ItemMetric) o;\n\n\t\treturn title.equalsIgnoreCase(that.title);\n\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn title.hashCode();\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/domain/timeseries/StatisticMetric.java",
    "content": "package com.piggymetrics.statistics.domain.timeseries;\n\npublic enum StatisticMetric {\n\n\tINCOMES_AMOUNT, EXPENSES_AMOUNT, SAVING_AMOUNT\n\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/repository/DataPointRepository.java",
    "content": "package com.piggymetrics.statistics.repository;\n\nimport com.piggymetrics.statistics.domain.timeseries.DataPoint;\nimport com.piggymetrics.statistics.domain.timeseries.DataPointId;\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.List;\n\n@Repository\npublic interface DataPointRepository extends CrudRepository<DataPoint, DataPointId> {\n\n\tList<DataPoint> findByIdAccount(String account);\n\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/repository/converter/DataPointIdReaderConverter.java",
    "content": "package com.piggymetrics.statistics.repository.converter;\n\nimport com.mongodb.DBObject;\nimport com.piggymetrics.statistics.domain.timeseries.DataPointId;\nimport org.springframework.core.convert.converter.Converter;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Date;\n\n@Component\npublic class DataPointIdReaderConverter implements Converter<DBObject, DataPointId> {\n\n\t@Override\n\tpublic DataPointId convert(DBObject object) {\n\n\t\tDate date = (Date) object.get(\"date\");\n\t\tString account = (String) object.get(\"account\");\n\n\t\treturn new DataPointId(account, date);\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/repository/converter/DataPointIdWriterConverter.java",
    "content": "package com.piggymetrics.statistics.repository.converter;\n\nimport com.mongodb.BasicDBObject;\nimport com.mongodb.DBObject;\nimport com.piggymetrics.statistics.domain.timeseries.DataPointId;\nimport org.springframework.core.convert.converter.Converter;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class DataPointIdWriterConverter implements Converter<DataPointId, DBObject> {\n\n\tprivate static final int FIELDS = 2;\n\n\t@Override\n\tpublic DBObject convert(DataPointId id) {\n\n\t\tDBObject object = new BasicDBObject(FIELDS);\n\n\t\tobject.put(\"date\", id.getDate());\n\t\tobject.put(\"account\", id.getAccount());\n\n\t\treturn object;\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/service/ExchangeRatesService.java",
    "content": "package com.piggymetrics.statistics.service;\n\nimport com.piggymetrics.statistics.domain.Currency;\n\nimport java.math.BigDecimal;\nimport java.util.Map;\n\npublic interface ExchangeRatesService {\n\n\t/**\n\t * Requests today's foreign exchange rates from a provider\n\t * or reuses values from the last request (if they are still relevant)\n\t *\n\t * @return current date rates\n\t */\n\tMap<Currency, BigDecimal> getCurrentRates();\n\n\t/**\n\t * Converts given amount to specified currency\n\t *\n\t * @param from {@link Currency}\n\t * @param to {@link Currency}\n\t * @param amount to be converted\n\t * @return converted amount\n\t */\n\tBigDecimal convert(Currency from, Currency to, BigDecimal amount);\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/service/ExchangeRatesServiceImpl.java",
    "content": "package com.piggymetrics.statistics.service;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.piggymetrics.statistics.client.ExchangeRatesClient;\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.ExchangeRatesContainer;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.Assert;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.time.LocalDate;\nimport java.util.Map;\n\n@Service\npublic class ExchangeRatesServiceImpl implements ExchangeRatesService {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(ExchangeRatesServiceImpl.class);\n\n\tprivate ExchangeRatesContainer container;\n\n\t@Autowired\n\tprivate ExchangeRatesClient client;\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic Map<Currency, BigDecimal> getCurrentRates() {\n\n\t\tif (container == null || !container.getDate().equals(LocalDate.now())) {\n\t\t\tcontainer = client.getRates(Currency.getBase());\n\t\t\tlog.info(\"exchange rates has been updated: {}\", container);\n\t\t}\n\n\t\treturn ImmutableMap.of(\n\t\t\t\tCurrency.EUR, container.getRates().get(Currency.EUR.name()),\n\t\t\t\tCurrency.RUB, container.getRates().get(Currency.RUB.name()),\n\t\t\t\tCurrency.USD, BigDecimal.ONE\n\t\t);\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic BigDecimal convert(Currency from, Currency to, BigDecimal amount) {\n\n\t\tAssert.notNull(amount);\n\n\t\tMap<Currency, BigDecimal> rates = getCurrentRates();\n\t\tBigDecimal ratio = rates.get(to).divide(rates.get(from), 4, RoundingMode.HALF_UP);\n\n\t\treturn amount.multiply(ratio);\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/service/StatisticsService.java",
    "content": "package com.piggymetrics.statistics.service;\n\nimport com.piggymetrics.statistics.domain.Account;\nimport com.piggymetrics.statistics.domain.timeseries.DataPoint;\n\nimport java.util.List;\n\npublic interface StatisticsService {\n\n\t/**\n\t * Finds account by given name\n\t *\n\t * @param accountName\n\t * @return found account\n\t */\n\tList<DataPoint> findByAccountName(String accountName);\n\n\t/**\n\t * Converts given {@link Account} object to {@link DataPoint} with\n\t * a set of significant statistic metrics.\n\t *\n\t * Compound {@link DataPoint#id} forces to rewrite the object\n\t * for each account within a day.\n\t *\n\t * @param accountName\n\t * @param account\n\t */\n\tDataPoint save(String accountName, Account account);\n\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/service/StatisticsServiceImpl.java",
    "content": "package com.piggymetrics.statistics.service;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.piggymetrics.statistics.domain.*;\nimport com.piggymetrics.statistics.domain.timeseries.DataPoint;\nimport com.piggymetrics.statistics.domain.timeseries.DataPointId;\nimport com.piggymetrics.statistics.domain.timeseries.ItemMetric;\nimport com.piggymetrics.statistics.domain.timeseries.StatisticMetric;\nimport com.piggymetrics.statistics.repository.DataPointRepository;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.Assert;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.time.Instant;\nimport java.time.LocalDate;\nimport java.time.ZoneId;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Service\npublic class StatisticsServiceImpl implements StatisticsService {\n\n\tprivate final Logger log = LoggerFactory.getLogger(getClass());\n\n\t@Autowired\n\tprivate DataPointRepository repository;\n\n\t@Autowired\n\tprivate ExchangeRatesService ratesService;\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic List<DataPoint> findByAccountName(String accountName) {\n\t\tAssert.hasLength(accountName);\n\t\treturn repository.findByIdAccount(accountName);\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic DataPoint save(String accountName, Account account) {\n\n\t\tInstant instant = LocalDate.now().atStartOfDay()\n\t\t\t\t.atZone(ZoneId.systemDefault()).toInstant();\n\n\t\tDataPointId pointId = new DataPointId(accountName, Date.from(instant));\n\n\t\tSet<ItemMetric> incomes = account.getIncomes().stream()\n\t\t\t\t.map(this::createItemMetric)\n\t\t\t\t.collect(Collectors.toSet());\n\n\t\tSet<ItemMetric> expenses = account.getExpenses().stream()\n\t\t\t\t.map(this::createItemMetric)\n\t\t\t\t.collect(Collectors.toSet());\n\n\t\tMap<StatisticMetric, BigDecimal> statistics = createStatisticMetrics(incomes, expenses, account.getSaving());\n\n\t\tDataPoint dataPoint = new DataPoint();\n\t\tdataPoint.setId(pointId);\n\t\tdataPoint.setIncomes(incomes);\n\t\tdataPoint.setExpenses(expenses);\n\t\tdataPoint.setStatistics(statistics);\n\t\tdataPoint.setRates(ratesService.getCurrentRates());\n\n\t\tlog.debug(\"new datapoint has been created: {}\", pointId);\n\n\t\treturn repository.save(dataPoint);\n\t}\n\n\tprivate Map<StatisticMetric, BigDecimal> createStatisticMetrics(Set<ItemMetric> incomes, Set<ItemMetric> expenses, Saving saving) {\n\n\t\tBigDecimal savingAmount = ratesService.convert(saving.getCurrency(), Currency.getBase(), saving.getAmount());\n\n\t\tBigDecimal expensesAmount = expenses.stream()\n\t\t\t\t.map(ItemMetric::getAmount)\n\t\t\t\t.reduce(BigDecimal.ZERO, BigDecimal::add);\n\n\t\tBigDecimal incomesAmount = incomes.stream()\n\t\t\t\t.map(ItemMetric::getAmount)\n\t\t\t\t.reduce(BigDecimal.ZERO, BigDecimal::add);\n\n\t\treturn ImmutableMap.of(\n\t\t\t\tStatisticMetric.EXPENSES_AMOUNT, expensesAmount,\n\t\t\t\tStatisticMetric.INCOMES_AMOUNT, incomesAmount,\n\t\t\t\tStatisticMetric.SAVING_AMOUNT, savingAmount\n\t\t);\n\t}\n\n\t/**\n\t * Normalizes given item amount to {@link Currency#getBase()} currency with\n\t * {@link TimePeriod#getBase()} time period\n\t */\n\tprivate ItemMetric createItemMetric(Item item) {\n\n\t\tBigDecimal amount = ratesService\n\t\t\t\t.convert(item.getCurrency(), Currency.getBase(), item.getAmount())\n\t\t\t\t.divide(item.getPeriod().getBaseRatio(), 4, RoundingMode.HALF_UP);\n\n\t\treturn new ItemMetric(item.getTitle(), amount);\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/java/com/piggymetrics/statistics/service/security/CustomUserInfoTokenServices.java",
    "content": "package com.piggymetrics.statistics.service.security;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.FixedAuthoritiesExtractor;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.oauth2.client.OAuth2RestOperations;\nimport org.springframework.security.oauth2.client.OAuth2RestTemplate;\nimport org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;\nimport org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;\nimport org.springframework.security.oauth2.common.OAuth2AccessToken;\nimport org.springframework.security.oauth2.common.exceptions.InvalidTokenException;\nimport org.springframework.security.oauth2.provider.OAuth2Authentication;\nimport org.springframework.security.oauth2.provider.OAuth2Request;\nimport org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;\n\nimport java.util.*;\n\n/**\n * Extended implementation of {@link org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices}\n *\n * By default, it designed to return only user details. This class provides {@link #getRequest(Map)} method, which\n * returns clientId and scope of calling service. This information used in controller's security checks.\n */\n\npublic class CustomUserInfoTokenServices implements ResourceServerTokenServices {\n\n\tprotected final Log logger = LogFactory.getLog(getClass());\n\n\tprivate static final String[] PRINCIPAL_KEYS = new String[] { \"user\", \"username\",\n\t\t\t\"userid\", \"user_id\", \"login\", \"id\", \"name\" };\n\n\tprivate final String userInfoEndpointUrl;\n\n\tprivate final String clientId;\n\n\tprivate OAuth2RestOperations restTemplate;\n\n\tprivate String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;\n\n\tprivate AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();\n\n\tpublic CustomUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {\n\t\tthis.userInfoEndpointUrl = userInfoEndpointUrl;\n\t\tthis.clientId = clientId;\n\t}\n\n\tpublic void setTokenType(String tokenType) {\n\t\tthis.tokenType = tokenType;\n\t}\n\n\tpublic void setRestTemplate(OAuth2RestOperations restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tpublic void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {\n\t\tthis.authoritiesExtractor = authoritiesExtractor;\n\t}\n\n\t@Override\n\tpublic OAuth2Authentication loadAuthentication(String accessToken)\n\t\t\tthrows AuthenticationException, InvalidTokenException {\n\t\tMap<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);\n\t\tif (map.containsKey(\"error\")) {\n\t\t\tthis.logger.debug(\"userinfo returned error: \" + map.get(\"error\"));\n\t\t\tthrow new InvalidTokenException(accessToken);\n\t\t}\n\t\treturn extractAuthentication(map);\n\t}\n\n\tprivate OAuth2Authentication extractAuthentication(Map<String, Object> map) {\n\t\tObject principal = getPrincipal(map);\n\t\tOAuth2Request request = getRequest(map);\n\t\tList<GrantedAuthority> authorities = this.authoritiesExtractor\n\t\t\t\t.extractAuthorities(map);\n\t\tUsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(\n\t\t\t\tprincipal, \"N/A\", authorities);\n\t\ttoken.setDetails(map);\n\t\treturn new OAuth2Authentication(request, token);\n\t}\n\n\tprivate Object getPrincipal(Map<String, Object> map) {\n\t\tfor (String key : PRINCIPAL_KEYS) {\n\t\t\tif (map.containsKey(key)) {\n\t\t\t\treturn map.get(key);\n\t\t\t}\n\t\t}\n\t\treturn \"unknown\";\n\t}\n\n\t@SuppressWarnings({ \"unchecked\" })\n\tprivate OAuth2Request getRequest(Map<String, Object> map) {\n\t\tMap<String, Object> request = (Map<String, Object>) map.get(\"oauth2Request\");\n\n\t\tString clientId = (String) request.get(\"clientId\");\n\t\tSet<String> scope = new LinkedHashSet<>(request.containsKey(\"scope\") ?\n\t\t\t\t(Collection<String>) request.get(\"scope\") : Collections.<String>emptySet());\n\n\t\treturn new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),\n\t\t\t\tnull, null, null, null);\n\t}\n\n\t@Override\n\tpublic OAuth2AccessToken readAccessToken(String accessToken) {\n\t\tthrow new UnsupportedOperationException(\"Not supported: read access token\");\n\t}\n\n\t@SuppressWarnings({ \"unchecked\" })\n\tprivate Map<String, Object> getMap(String path, String accessToken) {\n\t\tthis.logger.debug(\"Getting user info from: \" + path);\n\t\ttry {\n\t\t\tOAuth2RestOperations restTemplate = this.restTemplate;\n\t\t\tif (restTemplate == null) {\n\t\t\t\tBaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();\n\t\t\t\tresource.setClientId(this.clientId);\n\t\t\t\trestTemplate = new OAuth2RestTemplate(resource);\n\t\t\t}\n\t\t\tOAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()\n\t\t\t\t\t.getAccessToken();\n\t\t\tif (existingToken == null || !accessToken.equals(existingToken.getValue())) {\n\t\t\t\tDefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(\n\t\t\t\t\t\taccessToken);\n\t\t\t\ttoken.setTokenType(this.tokenType);\n\t\t\t\trestTemplate.getOAuth2ClientContext().setAccessToken(token);\n\t\t\t}\n\t\t\treturn restTemplate.getForEntity(path, Map.class).getBody();\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthis.logger.info(\"Could not fetch user details: \" + ex.getClass() + \", \"\n\t\t\t\t\t+ ex.getMessage());\n\t\t\treturn Collections.<String, Object>singletonMap(\"error\",\n\t\t\t\t\t\"Could not fetch user details\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: statistics-service\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user"
  },
  {
    "path": "statistics-service/src/test/java/com/piggymetrics/statistics/StatisticsServiceApplicationTests.java",
    "content": "package com.piggymetrics.statistics;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class StatisticsServiceApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "statistics-service/src/test/java/com/piggymetrics/statistics/client/ExchangeRatesClientTest.java",
    "content": "package com.piggymetrics.statistics.client;\n\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.ExchangeRatesContainer;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.time.LocalDate;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class ExchangeRatesClientTest {\n\n\t@Autowired\n\tprivate ExchangeRatesClient client;\n\n\t@Test\n\tpublic void shouldRetrieveExchangeRates() {\n\n\t\tExchangeRatesContainer container = client.getRates(Currency.getBase());\n\n\t\tassertEquals(container.getDate(), LocalDate.now());\n\t\tassertEquals(container.getBase(), Currency.getBase());\n\n\t\tassertNotNull(container.getRates());\n\t\tassertNotNull(container.getRates().get(Currency.USD.name()));\n\t\tassertNotNull(container.getRates().get(Currency.EUR.name()));\n\t\tassertNotNull(container.getRates().get(Currency.RUB.name()));\n\t}\n\n\t@Test\n\tpublic void shouldRetrieveExchangeRatesForSpecifiedCurrency() {\n\n\t\tCurrency requestedCurrency = Currency.EUR;\n\t\tExchangeRatesContainer container = client.getRates(Currency.getBase());\n\n\t\tassertEquals(container.getDate(), LocalDate.now());\n\t\tassertEquals(container.getBase(), Currency.getBase());\n\n\t\tassertNotNull(container.getRates());\n\t\tassertNotNull(container.getRates().get(requestedCurrency.name()));\n\t}\n}"
  },
  {
    "path": "statistics-service/src/test/java/com/piggymetrics/statistics/controller/StatisticsControllerTest.java",
    "content": "package com.piggymetrics.statistics.controller;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.collect.ImmutableList;\nimport com.piggymetrics.statistics.domain.Account;\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.Item;\nimport com.piggymetrics.statistics.domain.Saving;\nimport com.piggymetrics.statistics.domain.TimePeriod;\nimport com.piggymetrics.statistics.domain.timeseries.DataPoint;\nimport com.piggymetrics.statistics.domain.timeseries.DataPointId;\nimport com.piggymetrics.statistics.service.StatisticsService;\nimport com.sun.security.auth.UserPrincipal;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\n\nimport java.math.BigDecimal;\nimport java.util.Date;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\nimport static org.mockito.internal.verification.VerificationModeFactory.times;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class StatisticsControllerTest {\n\n\tprivate static final ObjectMapper mapper = new ObjectMapper();\n\n\t@InjectMocks\n\tprivate StatisticsController statisticsController;\n\n\t@Mock\n\tprivate StatisticsService statisticsService;\n\n\tprivate MockMvc mockMvc;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t\tthis.mockMvc = MockMvcBuilders.standaloneSetup(statisticsController).build();\n\t}\n\n\t@Test\n\tpublic void shouldGetStatisticsByAccountName() throws Exception {\n\n\t\tfinal DataPoint dataPoint = new DataPoint();\n\t\tdataPoint.setId(new DataPointId(\"test\", new Date()));\n\n\t\twhen(statisticsService.findByAccountName(dataPoint.getId().getAccount()))\n\t\t\t\t.thenReturn(ImmutableList.of(dataPoint));\n\n\t\tmockMvc.perform(get(\"/test\").principal(new UserPrincipal(dataPoint.getId().getAccount())))\n\t\t\t\t.andExpect(jsonPath(\"$[0].id.account\").value(dataPoint.getId().getAccount()))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldGetCurrentAccountStatistics() throws Exception {\n\n\t\tfinal DataPoint dataPoint = new DataPoint();\n\t\tdataPoint.setId(new DataPointId(\"test\", new Date()));\n\n\t\twhen(statisticsService.findByAccountName(dataPoint.getId().getAccount()))\n\t\t\t\t.thenReturn(ImmutableList.of(dataPoint));\n\n\t\tmockMvc.perform(get(\"/current\").principal(new UserPrincipal(dataPoint.getId().getAccount())))\n\t\t\t\t.andExpect(jsonPath(\"$[0].id.account\").value(dataPoint.getId().getAccount()))\n\t\t\t\t.andExpect(status().isOk());\n\t}\n\n\t@Test\n\tpublic void shouldSaveAccountStatistics() throws Exception {\n\n\t\tSaving saving = new Saving();\n\t\tsaving.setAmount(new BigDecimal(1500));\n\t\tsaving.setCurrency(Currency.USD);\n\t\tsaving.setInterest(new BigDecimal(\"3.32\"));\n\t\tsaving.setDeposit(true);\n\t\tsaving.setCapitalization(false);\n\n\t\tItem grocery = new Item();\n\t\tgrocery.setTitle(\"Grocery\");\n\t\tgrocery.setAmount(new BigDecimal(10));\n\t\tgrocery.setCurrency(Currency.USD);\n\t\tgrocery.setPeriod(TimePeriod.DAY);\n\n\t\tItem salary = new Item();\n\t\tsalary.setTitle(\"Salary\");\n\t\tsalary.setAmount(new BigDecimal(9100));\n\t\tsalary.setCurrency(Currency.USD);\n\t\tsalary.setPeriod(TimePeriod.MONTH);\n\n\t\tfinal Account account = new Account();\n\t\taccount.setSaving(saving);\n\t\taccount.setExpenses(ImmutableList.of(grocery));\n\t\taccount.setIncomes(ImmutableList.of(salary));\n\n\t\tString json = mapper.writeValueAsString(account);\n\n\t\tmockMvc.perform(put(\"/test\").contentType(MediaType.APPLICATION_JSON).content(json))\n\t\t\t\t.andExpect(status().isOk());\n\n\t\tverify(statisticsService, times(1)).save(anyString(), any(Account.class));\n\t}\n}"
  },
  {
    "path": "statistics-service/src/test/java/com/piggymetrics/statistics/repository/DataPointRepositoryTest.java",
    "content": "package com.piggymetrics.statistics.repository;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Sets;\nimport com.piggymetrics.statistics.domain.timeseries.DataPoint;\nimport com.piggymetrics.statistics.domain.timeseries.DataPointId;\nimport com.piggymetrics.statistics.domain.timeseries.ItemMetric;\nimport com.piggymetrics.statistics.domain.timeseries.StatisticMetric;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.math.BigDecimal;\nimport java.util.Date;\nimport java.util.List;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(SpringRunner.class)\n@DataMongoTest\npublic class DataPointRepositoryTest {\n\n\t@Autowired\n\tprivate DataPointRepository repository;\n\n\t@Test\n\tpublic void shouldSaveDataPoint() {\n\n\t\tItemMetric salary = new ItemMetric(\"salary\", new BigDecimal(20_000));\n\n\t\tItemMetric grocery = new ItemMetric(\"grocery\", new BigDecimal(1_000));\n\t\tItemMetric vacation = new ItemMetric(\"vacation\", new BigDecimal(2_000));\n\n\t\tDataPointId pointId = new DataPointId(\"test-account\", new Date(0));\n\n\t\tDataPoint point = new DataPoint();\n\t\tpoint.setId(pointId);\n\t\tpoint.setIncomes(Sets.newHashSet(salary));\n\t\tpoint.setExpenses(Sets.newHashSet(grocery, vacation));\n\t\tpoint.setStatistics(ImmutableMap.of(\n\t\t\t\tStatisticMetric.SAVING_AMOUNT, new BigDecimal(400_000),\n\t\t\t\tStatisticMetric.INCOMES_AMOUNT, new BigDecimal(20_000),\n\t\t\t\tStatisticMetric.EXPENSES_AMOUNT, new BigDecimal(3_000)\n\t\t));\n\n\t\trepository.save(point);\n\n\t\tList<DataPoint> points = repository.findByIdAccount(pointId.getAccount());\n\t\tassertEquals(1, points.size());\n\t\tassertEquals(pointId.getDate(), points.get(0).getId().getDate());\n\t\tassertEquals(point.getStatistics().size(), points.get(0).getStatistics().size());\n\t\tassertEquals(point.getIncomes().size(), points.get(0).getIncomes().size());\n\t\tassertEquals(point.getExpenses().size(), points.get(0).getExpenses().size());\n\t}\n\n\t@Test\n\tpublic void shouldRewriteDataPointWithinADay() {\n\n\t\tfinal BigDecimal earlyAmount = new BigDecimal(100);\n\t\tfinal BigDecimal lateAmount = new BigDecimal(200);\n\n\t\tDataPointId pointId = new DataPointId(\"test-account\", new Date(0));\n\n\t\tDataPoint earlier = new DataPoint();\n\t\tearlier.setId(pointId);\n\t\tearlier.setStatistics(ImmutableMap.of(\n\t\t\t\tStatisticMetric.SAVING_AMOUNT, earlyAmount\n\t\t));\n\n\t\trepository.save(earlier);\n\n\t\tDataPoint later = new DataPoint();\n\t\tlater.setId(pointId);\n\t\tlater.setStatistics(ImmutableMap.of(\n\t\t\t\tStatisticMetric.SAVING_AMOUNT, lateAmount\n\t\t));\n\n\t\trepository.save(later);\n\n\t\tList<DataPoint> points = repository.findByIdAccount(pointId.getAccount());\n\n\t\tassertEquals(1, points.size());\n\t\tassertEquals(lateAmount, points.get(0).getStatistics().get(StatisticMetric.SAVING_AMOUNT));\n\t}\n}\n"
  },
  {
    "path": "statistics-service/src/test/java/com/piggymetrics/statistics/service/ExchangeRatesServiceImplTest.java",
    "content": "package com.piggymetrics.statistics.service;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.piggymetrics.statistics.client.ExchangeRatesClient;\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.ExchangeRatesContainer;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.math.BigDecimal;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.*;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class ExchangeRatesServiceImplTest {\n\n\t@InjectMocks\n\tprivate ExchangeRatesServiceImpl ratesService;\n\n\t@Mock\n\tprivate ExchangeRatesClient client;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t}\n\n\t@Test\n\tpublic void shouldReturnCurrentRatesWhenContainerIsEmptySoFar() {\n\n\t\tExchangeRatesContainer container = new ExchangeRatesContainer();\n\t\tcontainer.setRates(ImmutableMap.of(\n\t\t\t\tCurrency.EUR.name(), new BigDecimal(\"0.8\"),\n\t\t\t\tCurrency.RUB.name(), new BigDecimal(\"80\")\n\t\t));\n\n\t\twhen(client.getRates(Currency.getBase())).thenReturn(container);\n\n\t\tMap<Currency, BigDecimal> result = ratesService.getCurrentRates();\n\t\tverify(client, times(1)).getRates(Currency.getBase());\n\n\t\tassertEquals(container.getRates().get(Currency.EUR.name()), result.get(Currency.EUR));\n\t\tassertEquals(container.getRates().get(Currency.RUB.name()), result.get(Currency.RUB));\n\t\tassertEquals(BigDecimal.ONE, result.get(Currency.USD));\n\t}\n\n\t@Test\n\tpublic void shouldNotRequestRatesWhenTodaysContainerAlreadyExists() {\n\n\t\tExchangeRatesContainer container = new ExchangeRatesContainer();\n\t\tcontainer.setRates(ImmutableMap.of(\n\t\t\t\tCurrency.EUR.name(), new BigDecimal(\"0.8\"),\n\t\t\t\tCurrency.RUB.name(), new BigDecimal(\"80\")\n\t\t));\n\n\t\twhen(client.getRates(Currency.getBase())).thenReturn(container);\n\n\t\t// initialize container\n\t\tratesService.getCurrentRates();\n\n\t\t// use existing container\n\t\tratesService.getCurrentRates();\n\n\t\tverify(client, times(1)).getRates(Currency.getBase());\n\t}\n\n\t@Test\n\tpublic void shouldConvertCurrency() {\n\n\t\tExchangeRatesContainer container = new ExchangeRatesContainer();\n\t\tcontainer.setRates(ImmutableMap.of(\n\t\t\t\tCurrency.EUR.name(), new BigDecimal(\"0.8\"),\n\t\t\t\tCurrency.RUB.name(), new BigDecimal(\"80\")\n\t\t));\n\n\t\twhen(client.getRates(Currency.getBase())).thenReturn(container);\n\n\t\tfinal BigDecimal amount = new BigDecimal(100);\n\t\tfinal BigDecimal expectedConvertionResult = new BigDecimal(\"1.25\");\n\n\t\tBigDecimal result = ratesService.convert(Currency.RUB, Currency.USD, amount);\n\n\t\tassertTrue(expectedConvertionResult.compareTo(result) == 0);\n\t}\n\n\t@Test(expected = IllegalArgumentException.class)\n\tpublic void shouldFailToConvertWhenAmountIsNull() {\n\t\tratesService.convert(Currency.EUR, Currency.RUB, null);\n\t}\n}"
  },
  {
    "path": "statistics-service/src/test/java/com/piggymetrics/statistics/service/StatisticsServiceImplTest.java",
    "content": "package com.piggymetrics.statistics.service;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.piggymetrics.statistics.domain.Account;\nimport com.piggymetrics.statistics.domain.Currency;\nimport com.piggymetrics.statistics.domain.Item;\nimport com.piggymetrics.statistics.domain.Saving;\nimport com.piggymetrics.statistics.domain.TimePeriod;\nimport com.piggymetrics.statistics.domain.timeseries.DataPoint;\nimport com.piggymetrics.statistics.domain.timeseries.ItemMetric;\nimport com.piggymetrics.statistics.domain.timeseries.StatisticMetric;\nimport com.piggymetrics.statistics.repository.DataPointRepository;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.time.LocalDate;\nimport java.time.ZoneId;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.AdditionalAnswers.returnsFirstArg;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class StatisticsServiceImplTest {\n\n\t@InjectMocks\n\tprivate StatisticsServiceImpl statisticsService;\n\n\t@Mock\n\tprivate ExchangeRatesServiceImpl ratesService;\n\n\t@Mock\n\tprivate DataPointRepository repository;\n\n\t@Before\n\tpublic void setup() {\n\t\tinitMocks(this);\n\t}\n\n\t@Test\n\tpublic void shouldFindDataPointListByAccountName() {\n\t\tfinal List<DataPoint> list = ImmutableList.of(new DataPoint());\n\t\twhen(repository.findByIdAccount(\"test\")).thenReturn(list);\n\n\t\tList<DataPoint> result = statisticsService.findByAccountName(\"test\");\n\t\tassertEquals(list, result);\n\t}\n\n\t@Test(expected = IllegalArgumentException.class)\n\tpublic void shouldFailToFindDataPointWhenAccountNameIsNull() {\n\t\tstatisticsService.findByAccountName(null);\n\t}\n\n\t@Test(expected = IllegalArgumentException.class)\n\tpublic void shouldFailToFindDataPointWhenAccountNameIsEmpty() {\n\t\tstatisticsService.findByAccountName(\"\");\n\t}\n\n\t@Test\n\tpublic void shouldSaveDataPoint() {\n\n\t\t/**\n\t\t * Given\n\t\t */\n\n\t\tItem salary = new Item();\n\t\tsalary.setTitle(\"Salary\");\n\t\tsalary.setAmount(new BigDecimal(9100));\n\t\tsalary.setCurrency(Currency.USD);\n\t\tsalary.setPeriod(TimePeriod.MONTH);\n\n\t\tItem grocery = new Item();\n\t\tgrocery.setTitle(\"Grocery\");\n\t\tgrocery.setAmount(new BigDecimal(500));\n\t\tgrocery.setCurrency(Currency.RUB);\n\t\tgrocery.setPeriod(TimePeriod.DAY);\n\n\t\tItem vacation = new Item();\n\t\tvacation.setTitle(\"Vacation\");\n\t\tvacation.setAmount(new BigDecimal(3400));\n\t\tvacation.setCurrency(Currency.EUR);\n\t\tvacation.setPeriod(TimePeriod.YEAR);\n\n\t\tSaving saving = new Saving();\n\t\tsaving.setAmount(new BigDecimal(1000));\n\t\tsaving.setCurrency(Currency.EUR);\n\t\tsaving.setInterest(new BigDecimal(3.2));\n\t\tsaving.setDeposit(true);\n\t\tsaving.setCapitalization(false);\n\n\t\tAccount account = new Account();\n\t\taccount.setIncomes(ImmutableList.of(salary));\n\t\taccount.setExpenses(ImmutableList.of(grocery, vacation));\n\t\taccount.setSaving(saving);\n\n\t\tfinal Map<Currency, BigDecimal> rates = ImmutableMap.of(\n\t\t\t\tCurrency.EUR, new BigDecimal(\"0.8\"),\n\t\t\t\tCurrency.RUB, new BigDecimal(\"80\"),\n\t\t\t\tCurrency.USD, BigDecimal.ONE\n\t\t);\n\n\t\t/**\n\t\t * When\n\t\t */\n\n\t\twhen(ratesService.convert(any(Currency.class),any(Currency.class),any(BigDecimal.class)))\n\t\t\t\t.then(i -> ((BigDecimal)i.getArgument(2))\n\t\t\t\t\t\t.divide(rates.get(i.getArgument(0)), 4, RoundingMode.HALF_UP));\n\n\t\twhen(ratesService.getCurrentRates()).thenReturn(rates);\n\n\t\twhen(repository.save(any(DataPoint.class))).then(returnsFirstArg());\n\n\t\tDataPoint dataPoint = statisticsService.save(\"test\", account);\n\n\t\t/**\n\t\t * Then\n\t\t */\n\n\t\tfinal BigDecimal expectedExpensesAmount = new BigDecimal(\"17.8861\");\n\t\tfinal BigDecimal expectedIncomesAmount = new BigDecimal(\"298.9802\");\n\t\tfinal BigDecimal expectedSavingAmount = new BigDecimal(\"1250\");\n\n\t\tfinal BigDecimal expectedNormalizedSalaryAmount = new BigDecimal(\"298.9802\");\n\t\tfinal BigDecimal expectedNormalizedVacationAmount = new BigDecimal(\"11.6361\");\n\t\tfinal BigDecimal expectedNormalizedGroceryAmount = new BigDecimal(\"6.25\");\n\n\t\tassertEquals(dataPoint.getId().getAccount(), \"test\");\n\t\tassertEquals(dataPoint.getId().getDate(), Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));\n\n\t\tassertTrue(expectedExpensesAmount.compareTo(dataPoint.getStatistics().get(StatisticMetric.EXPENSES_AMOUNT)) == 0);\n\t\tassertTrue(expectedIncomesAmount.compareTo(dataPoint.getStatistics().get(StatisticMetric.INCOMES_AMOUNT)) == 0);\n\t\tassertTrue(expectedSavingAmount.compareTo(dataPoint.getStatistics().get(StatisticMetric.SAVING_AMOUNT)) == 0);\n\n\t\tItemMetric salaryItemMetric = dataPoint.getIncomes().stream()\n\t\t\t\t.filter(i -> i.getTitle().equals(salary.getTitle()))\n\t\t\t\t.findFirst().get();\n\n\t\tItemMetric vacationItemMetric = dataPoint.getExpenses().stream()\n\t\t\t\t.filter(i -> i.getTitle().equals(vacation.getTitle()))\n\t\t\t\t.findFirst().get();\n\n\t\tItemMetric groceryItemMetric = dataPoint.getExpenses().stream()\n\t\t\t\t.filter(i -> i.getTitle().equals(grocery.getTitle()))\n\t\t\t\t.findFirst().get();\n\n\t\tassertTrue(expectedNormalizedSalaryAmount.compareTo(salaryItemMetric.getAmount()) == 0);\n\t\tassertTrue(expectedNormalizedVacationAmount.compareTo(vacationItemMetric.getAmount()) == 0);\n\t\tassertTrue(expectedNormalizedGroceryAmount.compareTo(groceryItemMetric.getAmount()) == 0);\n\n\t\tassertEquals(rates, dataPoint.getRates());\n\n\t\tverify(repository, times(1)).save(dataPoint);\n\t}\n}"
  },
  {
    "path": "statistics-service/src/test/resources/application.yml",
    "content": "hystrix:\n  command:\n    default:\n      execution:\n        isolation:\n          thread:\n            timeoutInMilliseconds: 20000\n\nspring:\n  data:\n    mongodb:\n      database: piggymetrics\n      port: 0\n\nrates:\n  url: https://api.exchangeratesapi.io"
  },
  {
    "path": "statistics-service/src/test/resources/bootstrap.yml",
    "content": "eureka:\n  client:\n    enabled: false"
  },
  {
    "path": "turbine-stream-service/Dockerfile",
    "content": "FROM java:8-jre\nMAINTAINER Chi Dov <d.chiproeng@gmail.com>\n\nADD ./target/turbine-stream-service.jar /app/\nCMD [\"java\", \"-Xmx200m\", \"-jar\", \"/app/turbine-stream-service.jar\"]\n\nEXPOSE 8989"
  },
  {
    "path": "turbine-stream-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>turbine-stream-service</artifactId>\n\t<version>0.0.1-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>turbine-stream-service</name>\n\t<description>Turbine Stream Service</description>\n\n\t<parent>\n\t\t<groupId>com.piggymetrics</groupId>\n\t\t<artifactId>piggymetrics</artifactId>\n\t\t<version>1.0-SNAPSHOT</version>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-config</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-netflix-turbine-stream</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-starter-stream-rabbit</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>${project.name}</finalName>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "turbine-stream-service/src/main/java/com/piggymetrics/turbine/TurbineStreamServiceApplication.java",
    "content": "package com.piggymetrics.turbine;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.cloud.netflix.turbine.stream.EnableTurbineStream;\n\n@SpringBootApplication\n@EnableTurbineStream\n@EnableDiscoveryClient\npublic class TurbineStreamServiceApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(TurbineStreamServiceApplication.class, args);\n\t}\n}\n"
  },
  {
    "path": "turbine-stream-service/src/main/resources/bootstrap.yml",
    "content": "spring:\n  application:\n    name: turbine-stream-service\n  cloud:\n    config:\n      uri: http://config:8888\n      fail-fast: true\n      password: ${CONFIG_SERVICE_PASSWORD}\n      username: user"
  },
  {
    "path": "turbine-stream-service/src/test/java/com/piggymetrics/turbine/TurbineStreamServiceApplicationTests.java",
    "content": "package com.piggymetrics.turbine;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class TurbineStreamServiceApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "turbine-stream-service/src/test/resources/bootstrap.yml",
    "content": "eureka:\n  client:\n    enabled: false"
  }
]