This commit is contained in:
Ben Greenier 2018-12-06 10:08:20 -08:00 ΠΊΠΎΠΌΠΌΠΈΡ‚ ΠΏΡ€ΠΎΠΈΠ·Π²Ρ‘Π» GitHub
Π ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒ 57155bad74
ΠšΠΎΠΌΠΌΠΈΡ‚ 1639cfa3ee
НС Π½Π°ΠΉΠ΄Π΅Π½ ΠΊΠ»ΡŽΡ‡, ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ Π΄Π°Π½Π½ΠΎΠΉ подписи
Π˜Π΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΊΠ»ΡŽΡ‡Π° GPG: 4AEE18F83AFDEB23
115 ΠΈΠ·ΠΌΠ΅Π½Ρ‘Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ²: 25107 Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΉ ΠΈ 30 ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΉ

1
.gitattributes поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1 @@
*.sh eol=lf

132
.gitignore поставляСмый
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -1,23 +1,115 @@
# Compiled class file
### Custom entries ###
*.tsv
*.env
*.prefs
*.java2
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.classpath
*.properties
*.lst
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
*.original
*.project
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Created by https://www.gitignore.io/api/maven
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
# End of https://www.gitignore.io/api/maven
# Created by https://www.gitignore.io/api/intellij
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/sonarlint
.idea/misc.xml
# End of https://www.gitignore.io/api/intellij

241
README.md
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -1,14 +1,235 @@
# SpringDAL
# Contributing
A RESTful DAL (Database Abstraction Layer) reference implementation written using Spring.
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
# Introduction
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project provides a reference implementation for of Java-based microservices with REST APIs that read and write data stored in Azure Cosmos DB. The services are hosted in containers running in Azure App Service for Containers, (FUTURE: with Azure Redis providing caching). HA/DR is provided by hosting the microservices in multiple regions, as well as CosmosDB's native geo-redundancy. Traffic Manager is used to route traffic based on geo-proximity, and Application Gateway provides path-based routing, service authentication and DDoS protection.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
Cosmos DB is configured to use the NoSQL MongoDB API.
In order to demonstrate Cosmos DB performance with large amounts of data, the project imports historial movie data from [IMDb](https://www.imdb.com/interfaces/). See (https://datasets.imdbws.com/.) The datasets include 8.9 million peple, 5.3 million movies and 30 million relationships between them.
## Architecture
This solution provides a robust foundation on which enterprise engineering (EE) teams may build and deploy production-ready microservices solutions.
We built the solution to provide a common enterprise-ready foundation for Azure-based applications with the following architecture:
- Java-based microservices
- Data stored in Cosmos DB
- Redis-based caching
- High Availability & Disaster Recovery (HA/DR)
- A full CI/CD pipeline
- Robust but simple codebase that follows common enterprise-engineering best practices
- Load and failure simulators to validate scale, resiliency and failover
### Why We Chose App Services
We decided to host our application using Azure App Services instead of using Azure Kuberenetes Cluster. We made this decision because Azure App Services gave us better control over scaling the app accross regions. It also required less configuration with our traffic manager and load balancer architecture. Furthermore, Azure App Services has an easy-to-use, built-in load testing service that we utilize to test the container scaling of our app. Out of the box, Azure App Services offers auto-scaling, authentication, and deployment slots. In the future, because Azure App Services is a PaaS provider, we can implement Platform Chaos to initiate chaos testing services too. While this approach does not provide as much control of the server itself, the deployed docker container will keep the JVM consistent across deployments.
If you'd like to learn more you can read these articles:
- [Container? Why not App Services?](https://blogs.msdn.microsoft.com/premier_developer/2018/06/15/container-why-not-app-services/)
- [Azure Deployment Models](https://stackify.com/azure-deployment-models/)
## Key Benefits
Key technologies and concepts demonstrated:
| Benefit | Supporting Solution
|---|---
| Common, standard technologies | <li>Java programming language<li>Spring Boot Framework, one of the most widely used EE frameworks for Java<li>MongoDB NoSQL API (via Azure Cosmos DB)<li>Redis Cache
| Production-ready codebase | High quality codebase that is easily enhanced, well-documented and meets typical enterprise code quality standards
| Well-designed RESTful API | Solution follows RESTful design best-practices
| Enhanced productivity via Docker| Micros-services implemented in Docker containers, which are hosted by the Azure App Service for Containers PaaS service. Developer productivity enhance due to service isolation and easy service updates
| Example of well-designed CI/CD pipeline | Full continuous integration/continuous delivery (CI/CD) is implemented using Azure DevOps with a pipeline of environments that support dev, testing and production
| Automated infrastructure deployment | <li>Azure ARM templates<li>App Service for Containers<li>Azure container registry
| High Availability/Disaster Recovery (HA/DR) | Full geo-replication of microservices and data, with automatic failover in the event of an issue in any region:<br><br><li>Cosmos DB deployed to multiple regions with active-active read/write<li>Session consistency to assure that user experience is consistent across failover<li>Stateless microservices deployed to multiple regions<li>Health monitoring to detect errors that require failover<li>Azure Traffic Manager redirects traffic to healthy region
| Demonstrates insfrastructure best practices | <li>Application auto-scaling<li>Minimize network latency through geo-based DNS routing<li>API authentication<li>Distributed denial of service (DDoS) protection & mitigation
| Load and performance testing | The solution includes an integrated traffic simulator to demonstrate that the solution auto-scales properly, maintaining application performance as scale increases
| Proves application resiliency through chaos testing | A Chaos Monkey-style solution to shut down different portions of the architecture in order to validate that resilience measures keep everything runing in the event of any single failure
## Getting Started With Azure
Follow these instructions to begin using the solution
### Pre-Requisites
- Clone the reference solution to your computer:
```
git clone https://<your alias>@dev.azure.com/csebostoncrew/ProjectJackson/_git/ProjectJackson
```
- Install [Java 8 (version 1.8)](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
- Install [the latest Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)
- Install MongoDB:
- Windows: [Install MongoDB Community Edition on Windows](https://docs.mongodb.com/v3.2/tutorial/install-mongodb-on-windows/)
- MacOS: From a command line, run `brew install mongodb`
- Linux: From command line, run `apt-get install mongodb`
Temporary instructions before we have automated deployments running:
### Create Azure Resources
- [Create a resource group](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-portal#manage-resource-groups)
- [Add a Cosmos DB instance](https://docs.microsoft.com/en-us/azure/cosmos-db/create-mongodb-java#create-a-database-account) to the resource group
*OPTIONAL: Enable the MongoDB Aggregation Pipeline*
The aggregation pipeline must be enabled in order to support aggregation queries like `count()`:
- From the Cosmos DB resource view, click the "Preview Features" option (just below "Connection String")
- Click the "Enable" button next to "MongoDB Aggregation Pipeline"
### Set up your environment variables
- Open the Cosmos Connection String blade
- Open the `data/importdata.sh` file.
- Make sure the Cosmos DB resource is already created as mentioned above, for the next steps to be successful.
- From Bash command line, run `load_env.sh`. It will will write/load any needed variables to the `vars.env` file.
- `RESOURCE_GROUP` - the Azure resource group name
- `COSMOSDB_NAME` - the CosmosDB collection name (which is case sensitive)
- `COSMOSDB_PASSWORD` - the CosmosDB's password (needed for when you load the data into Cosmos)
- Load `vars.env` into your environment or VM where the app is being built locally.
- `source vars.env`
- or in your chosen IDE, set your environment variables within your project.
- NB: there will also be a DB_NAME and DB_CONNSTR for the Spring application (see the database section below in Application Configuration)
### Prepare the command line
- Switch into the project `data` directory: `cd data`
- Log into Azure: `az login`
- If you have multiple subscriptions, confirm that the project subscription is active:
``` Bash
az account show
az account set --subscription <subscription name/ID>
```
### Import the sample IMDb data to Cosmos DB
- Open a Bash command line
- Download and prepare the required IMDb data files:
``` Bash
data/getdata.sh
```
- Before starting to import data make sure the step `Set up your environment variables` is completed.
- Import the data into Cosmos collections
``` Bash
data/importdata.sh
```
### TIP: Explore the data from the MongoDB command-line
- Copy the Cosmos DB connection string from the "Connection String" blade
- Start the MongoDB CLI with this command: `mongo <connection string>`
- Begin executing MongoDB commands, such as:
``` Mongo
use moviesdb
show collections
db.titles.count()
db.titles.find ({primaryTitle: "Casablanca"})
```
## Application Configuration
We use [Profiles](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html) for configuration.
Currently `development` and `production` are possible values for the `spring.profiles.active` property.
By default, `development` is assumed. Note: `default` is technically it's own profile, that is the same as `development`.
### Authentication
> Note: If you're running with the `development` profile, this is __optional__.
To configure authentication, you'll need to specify your authentication provider's `jwt` or `jwk` key uri. For more information see [the spring docs](https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2-resource-server).
The `OAUTH_KEYSET_URI` environment variable must be set to that uri. For Microsoft Azure, that value can always be `https://login.microsoftonline.com/common/discovery/keys` - this is because a common key set is used for all Azure Active Directory applications.
The `OAUTH_RES_ID` environment variable should (but optionally may not be) set to the application id from the oauth2 provider. If this is omitted the authentication layer will validate whether the token is created by the given provider, but not that it is issued for your specific application.
Learn more about how to configure an Azure Active Directory application [here](./docs/azureActiveDirectory.md).
### Database
> Note: If you're running with the `development` profile, this is __optional__.
To configure communications with a database, the following environment variables are used:
+ `DB_CONNSTR` - a mongo [database connection string](https://docs.mongodb.com/manual/reference/connection-string/) (ex: `mongodb://db.com/myDb`)
+ `DB_NAME` - a mongo database name (ex: `myDb`)
+ `EXCLUDE_FILTER` - [optional] a (regex capable) list of classes to exclude from loading (ex: `TitleRepository,PersonRepository`)
### Mock Data
By default, when running with the `development` profile, test data is auto-loaded into the embedded mongo instance.
However, __if you set the above environment variables, that configuration will take precedence__.
This mock data contains about 8 entries from each collection, and can be found in the `src/main/resources/testdata` folder. There are related entries across each collection to prove out the custom API routes.
### Logging
Spring uses [Commons Logging](https://commons.apache.org/logging) under the hood, more details can be found
[here](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-logging.html).
To configure logging, the following environment variables can be used:
> Note: These values should be a [valid log level](a valid [log level](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-logging.html#boot-features-custom-log-levels))
+ `logging.level.root` - Configures the logging level for the whole application, including frameworks
+ `logging.level.com.microsoft.cse.*` - Configures the logging level for our application, excluding frameworks
+ `logging.level.org.zalando.logbook` - Configures the logging of HTTP requests/responses to the console. *Set to TRACE*
+ `logging.level.org.springframework.data.mongodb.core.MongoTemplate` - Configures the logging of a MongoDB query. *Set to DEBUG* to see how any constructed query gets data from MongoDB
To configure [application insights](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-overview) logging, the following environment variable must be set:
+ `APPLICATION_INSIGHTS_IKEY` - an [application insights telemetry key](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-java-get-started#1-get-an-application-insights-instrumentation-key)
## Building
> Note: Before running, please make sure everything is [configured](#configuration) to your liking!
To build, use `mvn compile`. To run, use `mvn spring-boot:run`. To run in production, first set the `spring.profiles.active` environment variable to `production` (as per [the above section](#configuration)).
### API Routes
We're using three kinds of models: `Person`, `Title`, and `Principal`. The `Person` model represents a person who participates in media, either in front of the camera or behind the scenes. The `Title` represents what it sounds like - the title of the piece of media, be it a movie, a TV series, or some other kind of similar media. Finally, the `Principal` model and its derivative child class `PrincipalWithName` represent the intersection of Person and Title, ie. what a particular person does or plays in a specific title.
To meaninfully access this IMDb dataset and these models, there are a few routes one can access on the API.
+ `/people`
+ `POST` - Creates a person, and returns information and ID of new person
+ `GET` - Returns a small number of people entries
+ `/people/{nconst}` > nconst is the unique identifier
+ `GET` - Gets the person associated with ID, and returns information about the person
+ `PUT` - Updates a person for a given ID, and returns information about updated person
+ `DELETE` - Deletes a person with a given ID, and returns the success/failure code
+ `/people/{nconst}/titles` > nconst is the unique identifier
+ `GET` - Gets the titles in the dataset associated with the person with specified ID and returns them in an array
+ `/titles`
+ `POST` - Creates a title, and returns the information and ID of the new titles
+ `GET` - returns a small number of title entries
+ `/titles/{tconst}` > tconst is the unique identifier
+ `GET` - Gets the title of piece given the ID, and returns information about that title
+ `PUT` - Updates the title of a piece given the ID, and returns that updated information based on ID
+ `DELETE` - Deletes the piece of media given the ID, and returns the success/failure code
+ `/titles/{tconst}/people` > tconst is the unique identifier
+ `GET` - Gets the people in the dataset associated with the given title, and returns that list
+ `/titles/{tconst}/cast` > tconst is the unique identifier
+ `GET` - Gets the people in the dataset associated with the given title who act, and returns that list
+ `/titles/{tconst}/crew` > tconst is the unique identifier
+ `GET` - Gets the people in the dataset associated with the given title who participate behind the scenes, and returns that list
For more details, check out the [Swagger documentation](https://dev.azure.com/csebostoncrew/_git/ProjectJackson?path=%2Fswagger.yml).
TODO: Any `upcoming feature` endpoints.
## Testing
To run the tests, use `mvn test`. This project strives to unit test each behavior, and integration test end to end scenarios.
## Contribute
TODO: Explain how other users and developers can contribute to make your code better.

16
api/.idea/compiler.xml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="SpringDAL" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="SpringDAL" target="1.8" />
</bytecodeTargetLevel>
</component>
</project>

6
api/.idea/encodings.xml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$" charset="UTF-8" />
</component>
</project>

19
api/.idea/misc.xml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="2">
<item index="0" class="java.lang.String" itemvalue="org.springframework.context.event.EventListener" />
<item index="1" class="java.lang.String" itemvalue="org.springframework.data.rest.core.annotation.RepositoryRestResource" />
</list>
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
api/.idea/modules.xml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/SpringDAL.iml" filepath="$PROJECT_DIR$/SpringDAL.iml" />
</modules>
</component>
</project>

6
api/.idea/vcs.xml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

17
api/Dockerfile Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,17 @@
# Start with a base image containing Java runtime
FROM openjdk:8-jdk-alpine
# Add a volume pointing to /tmp
VOLUME /tmp
# Make port 8080 available to the world outside this container
EXPOSE 8080
# The application's jar file
ARG JAR_FILE=target/spring-dal-0.0.1-SNAPSHOT.jar
# Add the application's jar to the container
ADD ${JAR_FILE} spring-dal.jar
# Run the jar file
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/spring-dal.jar"]

132
api/SpringDAL.iml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.1.2-SNAPSHOT" level="project" />
<orderEntry type="library" name="Maven: de.flapdoodle.embed:de.flapdoodle.embed.process:2.0.5" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.1.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.0.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.1.1" level="project" />
<orderEntry type="library" name="Maven: com.microsoft.azure:applicationinsights-web:2.2.0" level="project" />
<orderEntry type="library" name="Maven: com.microsoft.azure:applicationinsights-core:2.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-rest:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.10.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.10.0" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
<orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.19" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.7" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.7" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-rest-webmvc:3.0.11.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-rest-core:3.0.11.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.hateoas:spring-hateoas:0.25.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.atteo:evo-inflector:1.2.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.34" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.34" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.34" level="project" />
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.13.Final" level="project" />
<orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.3.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.0.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.0.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.0.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.0.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.0.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.0.10.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.0.6.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.0.6.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.0.6.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />
<orderEntry type="library" name="Maven: net.minidev:json-smart:2.3" level="project" />
<orderEntry type="library" name="Maven: net.minidev:accessors-smart:1.2" level="project" />
<orderEntry type="library" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.9.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:2.15.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.7.11" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.7.11" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.0.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.0.10.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.0.10.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.5.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-mongodb:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.mongodb:mongodb-driver:3.6.4" level="project" />
<orderEntry type="library" name="Maven: org.mongodb:bson:3.6.4" level="project" />
<orderEntry type="library" name="Maven: org.mongodb:mongodb-driver-core:3.6.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-mongodb:2.0.11.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.0.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.0.11.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-oauth2-resource-server:2.1.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-config:5.0.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.0.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-oauth2-resource-server:5.1.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-oauth2-core:5.0.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.0.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-oauth2-jose:5.0.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.nimbusds:nimbus-jose-jwt:5.14" level="project" />
<orderEntry type="library" name="Maven: com.github.stephenc.jcip:jcip-annotations:1.0-1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.7" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.7" level="project" />
<orderEntry type="library" name="Maven: com.sun.xml.bind:jaxb-core:2.3.0.1" level="project" />
<orderEntry type="library" name="Maven: com.sun.xml.bind:jaxb-impl:2.3.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.0.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security.oauth:spring-security-oauth2:2.3.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.11" level="project" />
<orderEntry type="library" name="Maven: org.codehaus.jackson:jackson-mapper-asl:1.9.13" level="project" />
<orderEntry type="library" name="Maven: org.codehaus.jackson:jackson-core-asl:1.9.13" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-jwt:1.0.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.bouncycastle:bcpkix-jdk15on:1.56" level="project" />
<orderEntry type="library" name="Maven: org.bouncycastle:bcprov-jdk15on:1.56" level="project" />
<orderEntry type="library" name="Maven: com.microsoft.azure:applicationinsights-web:2.2.1" level="project" />
<orderEntry type="library" name="Maven: com.microsoft.azure:applicationinsights-core:2.2.1" level="project" />
<orderEntry type="library" name="Maven: org.zalando:logbook-spring-boot-starter:1.11.1" level="project" />
<orderEntry type="library" name="Maven: org.zalando:logbook-core:1.11.1" level="project" />
<orderEntry type="library" name="Maven: org.zalando:logbook-api:1.11.1" level="project" />
<orderEntry type="library" name="Maven: org.zalando:logbook-httpclient:1.11.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.6" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.10" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpasyncclient:4.1.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore-nio:4.4.10" level="project" />
<orderEntry type="library" name="Maven: org.zalando:logbook-servlet:1.11.1" level="project" />
<orderEntry type="library" name="Maven: org.apiguardian:apiguardian-api:1.0.0" level="project" />
<orderEntry type="library" name="Maven: org.zalando:faux-pas:0.8.0" level="project" />
<orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:3.0.2" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
<orderEntry type="library" name="Maven: de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.0.0" level="project" />
<orderEntry type="library" name="Maven: de.flapdoodle.embed:de.flapdoodle.embed.process:2.0.1" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
<orderEntry type="library" name="Maven: net.java.dev.jna:jna:4.5.2" level="project" />
<orderEntry type="library" name="Maven: net.java.dev.jna:jna-platform:4.5.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.10" level="project" />
</component>
</module>

33
api/azure-pipelines.yml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,33 @@
# Maven
# Build your Java project and run tests with Apache Maven.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/java
trigger:
branches:
include:
- master
paths:
include:
- api/*
pool:
vmImage: 'Ubuntu 16.04'
steps:
- script: |
echo Starting the build
cd api/
mvn test
mvn package
displayName: 'Maven test and build'
- script: |
cd api/
docker build -t $ACR_SERVER/$ACR_CONTAINER_TAG .
displayName: 'Docker Build'
- script: |
docker login $(ACR_SERVER) -u $(ACR_USERNAME) -p $(ACR_PASSWORD)
displayName: 'Docker Login'
- script: |
docker push $ACR_SERVER/$ACR_CONTAINER_TAG
displayName: 'Docker Push'

109
api/pom.xml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.cse</groupId>
<artifactId>spring-dal</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringDAL</name>
<description>Spring DAL RESTful reference</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>applicationinsights-web</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>1.11.1</version>
</dependency>
<!-- from oss-sonartype repository -->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>oss-sonartype</id>
<name>sonartype</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project>

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,22 @@
package com.microsoft.cse.reference.spring.dal;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
@SpringBootApplication
@EnableMongoRepositories
@EnableAutoConfiguration(exclude = {EmbeddedMongoAutoConfiguration.class})
@EnableResourceServer
public class SpringDAL {
/**
* Application entry point. Scans for spring beans and automatically loads them
* @param args passed arguments
*/
public static void main(String[] args) {
SpringApplication.run(SpringDAL.class, args);
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,41 @@
package com.microsoft.cse.reference.spring.dal.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CORSConfig {
@Autowired
Environment env;
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin(env.getProperty(Constants.ENV_ALLOWED_ORIGIN));
config.addAllowedHeader("*");
config.addAllowedMethod(HttpMethod.GET);
config.addAllowedMethod(HttpMethod.HEAD);
config.addAllowedMethod(HttpMethod.POST);
config.addAllowedMethod(HttpMethod.PUT);
config.addAllowedMethod(HttpMethod.PATCH);
config.addAllowedMethod(HttpMethod.DELETE);
config.addAllowedMethod(HttpMethod.OPTIONS);
config.addAllowedMethod(HttpMethod.TRACE);
source.registerCorsConfiguration("/**", config);
final FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,101 @@
package com.microsoft.cse.reference.spring.dal.config;
public class Constants {
/**
* The constant that represents our application name
*/
public static final String APP_NAME = "SpringDAL";
/**
* The constant environment variable that we look to for the oauth2 resource id
* For more info, see: https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2-resource-server
*/
public static final String ENV_OAUTH_RES_ID = "OAUTH_RES_ID";
/**
* The constant environment variable that we look to for the oauth2 keyset uri
* For more info, see: https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2-resource-server
*/
public static final String ENV_OAUTH_KEYSET_URI = "OAUTH_KEYSET_URI";
/**
* The constant environment variable that we look to for production db connection string
*/
public static final String ENV_DB_CONNSTR = "DB_CONNSTR";
/**
* The constant environment variable that we look to for production db name
*/
public static final String ENV_DB_NAME = "DB_NAME";
/**
* The constant environment variable that we look to for exclusion filters
* Note: this value may be a comma separated list (ie: ClassA,ClassB)
* Note: regex is supported in each entry
*/
public static final String ENV_EXCLUDE_FILTER = "EXCLUDE_FILTER";
/**
* The constant environment variable that we look to for app insights telemetry key
* for more information, see the following:
* https://docs.microsoft.com/en-us/azure/application-insights/app-insights-java-get-started#1-get-an-application-insights-instrumentation-key
*/
public static final String ENV_APPINSIGHTS_KEY = "APPLICATION_INSIGHTS_IKEY";
/**
* The constant environment variable that we look to for the allowed origins strings for CORS
*/
public static final String ENV_ALLOWED_ORIGIN = "ALLOWED_ORIGIN";
/**
* Error message used to indicate we couldn't read database configuration
*/
public static final String ERR_DB_CONF = "Failed to read database information from configuration";
/**
* Error message used to indicate we couldn't read test data
*/
public static final String ERR_TEST_DATA_FAIL = "Failed to read test data";
/**
* Error message used to indicate we couldn't properly parse test data
*/
public static final String ERR_TEST_DATA_FORMAT = "Json structure must be a top-level array";
/**
* Status message that is used to display our database connection information
* Note: should String.format({0}=database info)
*/
public static final String STATUS_DB_CONN_INFO = "Successfully read database configuration %s";
/**
* Status message that is used to indicate we've loaded test data
*/
public static final String STATUS_TEST_DATA_USED = "Successfully loaded test data";
/**
* Status message that is used to indicate we've configured appInsights
*/
public static final String STATUS_APPINSIGHTS_SUCCESS = "Successfully configured appInsights telemetry key";
/**
* Status message that is used to indicate we've failed to configure appInsights:
* Note: this isn't an ERR, as appInsights is optional in all cases today
*/
public static final String STATUS_APPINSIGHTS_FAILURE = "Unable to configure appInsights telemetry key";
/**
* The collection from which we pull Person objects
*/
public static final String DB_PERSON_COLLECTION = "names";
/**
* The collection from which we pull Principal objects
*/
public static final String DB_PRINCIPAL_COLLECTION = "principals_mapping";
/**
* The collection from which we pull Title objects
*/
public static final String DB_TITLE_COLLECTION = "titles";
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,40 @@
package com.microsoft.cse.reference.spring.dal.config;
/**
* Represents a read-only database information object used for connection
*/
public class DatabaseInformation {
private String connStr;
private String name;
/**
* Create an instance of database information with the given connStr and name
* @param connStr the connection string
* @param name the name
*/
public DatabaseInformation(String connStr, String name) {
this.connStr = connStr;
this.name = name;
}
/**
* The connection string with which to connect to the database
* @return connection string
*/
public String getConnectionString() {
return this.connStr;
}
/**
* The name with which to connect to the database
* @return name
*/
public String getName() {
return this.name;
}
@Override
public String toString() {
return '[' + this.getConnectionString() + "]/" + this.getName();
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,78 @@
package com.microsoft.cse.reference.spring.dal.config;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import java.io.IOException;
@Configuration
@Profile({"development", "default"})
public class DevelopmentConfig extends WebSecurityConfigurerAdapter implements IApplicationConfig {
@Autowired
Environment env;
Logger logger = LoggerFactory.getLogger(DevelopmentConfig.class);
Net embeddedBindInformation;
MongodExecutable embeddedMongoInstance;
@Override
public DatabaseInformation getDatabaseInformation() throws Exception {
String connStr = env.getProperty(Constants.ENV_DB_CONNSTR);
String name;
if (connStr == null) {
if (this.embeddedMongoInstance == null) {
// we need to try to startup embedded mongo
this.embeddedBindInformation = new Net();
this.embeddedMongoInstance = this.setupMongoEmbed(this.embeddedBindInformation);
}
connStr = "mongodb://localhost:" + this.embeddedBindInformation.getPort();
name = "test";
} else {
name = env.getRequiredProperty(Constants.ENV_DB_NAME);
}
return new DatabaseInformation(connStr, name);
}
@Override
public void configure(WebSecurity webSecurity) throws Exception {
// In development mode, we ignore all webSecurity features
// effectively disabling oauth2 token requirements
webSecurity.ignoring().antMatchers("/**");
}
/**
* Attempts to start a mongo instance, using embedMongo
* @param bind the net info to bind to
* @return the instance
* @throws IOException indicates a failure
*/
private MongodExecutable setupMongoEmbed(Net bind) throws IOException {
MongodStarter starter;
starter = MongodStarter.getDefaultInstance();
IMongodConfig mongodConfig = new MongodConfigBuilder()
.version(Version.Main.DEVELOPMENT)
.net(bind)
.build();
MongodExecutable mongodExecutable = starter.prepare(mongodConfig);
MongodProcess mongod = mongodExecutable.start();
return mongodExecutable;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,94 @@
package com.microsoft.cse.reference.spring.dal.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Responsible for populating test data in development deployments
*/
@Component
@Profile({"default", "development"})
public class DevelopmentEmbeddedData {
@Autowired
MongoTemplate mongoInterface;
Logger logger = LoggerFactory.getLogger(MongoConfig.class);
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
try {
// load all the testdata sets into their given collections
mongoInterface.insert(parseTestData("testdata/titles.testdata.json"), Constants.DB_TITLE_COLLECTION);
mongoInterface.insert(parseTestData("testdata/names.testdata.json"), Constants.DB_PERSON_COLLECTION);
mongoInterface.insert(parseTestData("testdata/principals_mapping.testdata.json"), Constants.DB_PRINCIPAL_COLLECTION);
logger.info(Constants.STATUS_TEST_DATA_USED);
} catch (IOException e) {
logger.error(Constants.ERR_TEST_DATA_FAIL, e);
}
}
/**
* Parse test data from a resources file
* @param resourcePath
* @return
* @throws IOException
*/
private List<Document> parseTestData(String resourcePath) throws IOException {
ClassPathResource resource = new ClassPathResource(resourcePath);
InputStream inputStream = resource.getInputStream();
// first we read the data
StringBuilder textBuilder = new StringBuilder();
try (Reader reader = new BufferedReader(new InputStreamReader
(inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
int c = 0;
while ((c = reader.read()) != -1) {
textBuilder.append((char) c);
}
}
String jsonData = textBuilder.toString();
// then we convert that data to a json node
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonData);
// we ensure it is an array of documents to insert
if (!jsonNode.isArray()) {
throw new InvalidObjectException(Constants.ERR_TEST_DATA_FORMAT);
}
// we parse and store the elements of the array
Iterator<JsonNode> it = jsonNode.elements();
ArrayList results = new ArrayList<Document>();
while (it.hasNext()) {
JsonNode node = it.next();
Document parsed = Document.parse(node.toString());
results.add(parsed);
}
// return those elements
return results;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,10 @@
package com.microsoft.cse.reference.spring.dal.config;
public interface IApplicationConfig {
/**
* Get the database information object that describes the database we'll connect to
* @return Database information object
* @throws Exception Thrown when we cannot get this information (usually due to misconfiguration)
*/
DatabaseInformation getDatabaseInformation() throws Exception;
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,63 @@
package com.microsoft.cse.reference.spring.dal.config;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import java.util.List;
/**
* Configures the mongo driver based on our application configuration
*/
@Configuration
@EnableMongoRepositories
public class MongoConfig extends AbstractMongoConfiguration {
@Autowired
IApplicationConfig appConfig;
@Autowired
List<Converter> converters;
Logger logger = LoggerFactory.getLogger(MongoConfig.class);
@Override
public MongoClient mongoClient() {
DatabaseInformation info = null;
try {
info = this.appConfig.getDatabaseInformation();
logger.info(String.format(Constants.STATUS_DB_CONN_INFO, info));
} catch (Exception e) {
logger.error(Constants.ERR_DB_CONF, e);
}
return new MongoClient(new MongoClientURI(info.getConnectionString()));
}
@Override
protected String getDatabaseName() {
DatabaseInformation info = null;
try {
info = this.appConfig.getDatabaseInformation();
logger.info(String.format(Constants.STATUS_DB_CONN_INFO, info));
} catch (Exception e) {
logger.error(Constants.ERR_DB_CONF, e);
}
return info.getName();
}
@Override
public MongoCustomConversions customConversions() {
return new MongoCustomConversions(converters);
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,29 @@
package com.microsoft.cse.reference.spring.dal.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore;
/**
* Configure oauth2 resource server
*/
@Configuration
@ConditionalOnProperty(name = {Constants.ENV_OAUTH_KEYSET_URI, Constants.ENV_OAUTH_RES_ID})
public class OauthConfig extends ResourceServerConfigurerAdapter {
@Autowired
Environment env;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// setup the resource id
resources.resourceId(this.env.getProperty(Constants.ENV_OAUTH_RES_ID));
// setup the token store
resources.tokenStore(new JwkTokenStore(this.env.getProperty(Constants.ENV_OAUTH_KEYSET_URI)));
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,51 @@
package com.microsoft.cse.reference.spring.dal.config;
import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.web.internal.WebRequestTrackingFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
@Configuration
@Profile("production")
public class ProductionConfig implements IApplicationConfig {
@Autowired
Environment env;
Logger logger = LoggerFactory.getLogger(ProductionConfig.class);
@Override
public DatabaseInformation getDatabaseInformation() {
return new DatabaseInformation(env.getRequiredProperty(Constants.ENV_DB_CONNSTR),
env.getRequiredProperty(Constants.ENV_DB_NAME));
}
/**
* Gets the appInsights telemetry configuration information
* @return telemetry key
*/
@Bean
public String telemetryConfig() {
// note: this is optional, if it isn't set we won't use appInsights
String telemetryKey = env.getProperty(Constants.ENV_APPINSIGHTS_KEY);
if (telemetryKey != null) {
TelemetryConfiguration.getActive().setInstrumentationKey(telemetryKey);
logger.info(Constants.STATUS_APPINSIGHTS_SUCCESS);
} else {
logger.info(Constants.STATUS_APPINSIGHTS_FAILURE);
}
return telemetryKey;
}
/**
* Creates bean of type WebRequestTrackingFilter for request tracking to appInsights
* @return {@link Bean} of type {@link WebRequestTrackingFilter}
*/
@Bean
public WebRequestTrackingFilter appInsightFilter() {
return new WebRequestTrackingFilter(Constants.APP_NAME);
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,52 @@
package com.microsoft.cse.reference.spring.dal.config;
import com.microsoft.cse.reference.spring.dal.models.Person;
import com.microsoft.cse.reference.spring.dal.models.Title;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import java.util.regex.Pattern;
@Configuration
public class RepositoryConfig extends RepositoryRestConfigurerAdapter {
@Autowired
Environment env;
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
// expose the ids for the given model types
config.exposeIdsFor(Person.class);
config.exposeIdsFor(Title.class);
// configure how we find and load repositories to let us disable them at runtime with an environment variable
config.setRepositoryDetectionStrategy(metadata -> {
// if it's not exported, exclude it
if (!metadata.getRepositoryInterface().getAnnotation(RepositoryRestResource.class).exported()) {
return false;
}
String className = metadata.getRepositoryInterface().getName();
String exclusionList = env.getProperty(Constants.ENV_EXCLUDE_FILTER);
if (exclusionList != null && !exclusionList.isEmpty()) {
for (String exclude : exclusionList.split(",")) {
// see if we get any hits, treating the exclusion list entry as a regex pattern
// note: this allows us to hit 'ClassA' even if it's really 'com.package.ClassA'
if (Pattern.compile(exclude).matcher(className).find()) {
// exclude if we match
return false;
}
}
}
// default to allowing the repository
return true;
});
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,223 @@
package com.microsoft.cse.reference.spring.dal.controllers;
import com.microsoft.cse.reference.spring.dal.converters.IntegerToBoolean;
import com.microsoft.cse.reference.spring.dal.converters.EmptyStringToNull;
import com.microsoft.cse.reference.spring.dal.models.PrincipalWithName;
import com.microsoft.cse.reference.spring.dal.models.Title;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import static java.util.Arrays.asList;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
/**
*
* Create a custom controller so that when a user hits a URL that's not formatted like the search endpoint
*
*/
@RestController
public class CustomEndpointController {
private IntegerToBoolean integerToBoolean = new IntegerToBoolean();
private EmptyStringToNull emptyStringToNull = new EmptyStringToNull();
private MongoTemplate mongoTemplate;
public CustomEndpointController(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
/**
* Query the Principals collection via aggregation.
*
* Since the Mongo/Spring doesn't do a great job with the relational IMDb data we have
* and the SDK doesn't have a solution to access the data we want, we have to do some
* data transforming.
*
* example Mongo query that gets us close:
* db.principals_mapping.aggregate([
* {$match: {"nconst":"nm0000428"}},
* {$lookup: {from:"titles", localField:"tconst", foreignField:"tconst", as: "title_info"}},
* {$project: {"title_info":{"$arrayElemAt": ["$title_info",0]}, "_id":0}}
* ])
*
* Unfortunately, it seems you can't suppress a field in a projection in Spring, so we get back
* data that doesn't properly map into a Title object like we'd want.
*
* Ideally: if this data was formatted in a more "MongoDB" way, then there would likely be
* a Title object embedded into the requested Principal object so no cross-collection,
* JOIN-like behavior would be needed. However, the data is not ideal in this way, so we have
* to come up with a way to return the needed information.
*
* @param nconst
* @return List of Titles
*/
@RequestMapping(method = RequestMethod.GET, value = "/people/{nconst}/titles")
public List<Title> getAllTitles(@PathVariable String nconst) {
MatchOperation filterByNconst = match(Criteria.where("nconst").is(nconst));
LookupOperation titleLookup = LookupOperation.newLookup()
.from("titles")
.localField("tconst")
.foreignField("tconst")
.as("title_info");
Aggregation aggregation = Aggregation.newAggregation(
filterByNconst,
titleLookup,
project("title_info")
);
AggregationResults<Document> aggregationResults = mongoTemplate.aggregate(aggregation, "principals_mapping", Document.class);
return documentToTitleList(aggregationResults);
}
/**
* Generates a list from the Principal data set based on the tconst
* and has a Person-like object instead of the id.
*
* @param tconst
* @return List of PrincipalWithNames
*/
@RequestMapping(method = RequestMethod.GET, value = "/titles/{tconst}/people")
public List<PrincipalWithName> getAllPeople(@PathVariable String tconst) {
MatchOperation filterByNconst = match(Criteria.where("tconst").is(tconst));
LookupOperation nameLookup = LookupOperation.newLookup()
.from("names")
.localField("nconst")
.foreignField("nconst")
.as("person");
Aggregation aggregation = Aggregation.newAggregation(
filterByNconst,
nameLookup
);
AggregationResults<PrincipalWithName> results = mongoTemplate.aggregate(aggregation, "principals_mapping", PrincipalWithName.class);
// removes a clunky "_id" field that is generated when searching MongoDb
List<PrincipalWithName> mappedResults = results.getMappedResults();
for (PrincipalWithName p: mappedResults) {
p.person.remove("_id");
}
return mappedResults;
}
/**
* Generates a list from the Principal data set based on the tconst
* and has a Person-like object instead of the id.
*
* @param tconst
* @return List of PrincipalWithNames
*/
@RequestMapping(method = RequestMethod.GET, value = "/titles/{tconst}/crew")
public List<PrincipalWithName> getAllCrew(@PathVariable String tconst) {
MatchOperation filterByNconst = match(Criteria.where("tconst").is(tconst));
MatchOperation excludeCast = match(Criteria.where("category").ne("actress").andOperator(Criteria.where("category").ne("actor")));
LookupOperation nameLookup = LookupOperation.newLookup()
.from("names")
.localField("nconst")
.foreignField("nconst")
.as("person");
Aggregation aggregation = Aggregation.newAggregation(
filterByNconst,
excludeCast,
nameLookup
);
AggregationResults<PrincipalWithName> results = mongoTemplate.aggregate(aggregation, "principals_mapping", PrincipalWithName.class);
// removes a clunky "_id" field that is generated when searching MongoDb
List<PrincipalWithName> mappedResults = results.getMappedResults();
for (PrincipalWithName p: mappedResults) {
p.person.remove("_id");
}
return mappedResults;
}
/**
* Generates a list from the Principal data set based on the tconst
* and has a Person-like object instead of the id.
*
* @param tconst
* @return List of PrincipalWithNames
*/
@RequestMapping(method = RequestMethod.GET, value = "/titles/{tconst}/cast")
public List<PrincipalWithName> getAllCast(@PathVariable String tconst) {
MatchOperation filterByNconst = match(Criteria.where("tconst").is(tconst));
MatchOperation includeCast = match(new Criteria().orOperator(Criteria.where("category").is("actor"), Criteria.where("category").is("actress")));
LookupOperation nameLookup = LookupOperation.newLookup()
.from("names")
.localField("nconst")
.foreignField("nconst")
.as("person");
Aggregation aggregation = Aggregation.newAggregation(
filterByNconst,
includeCast,
nameLookup
);
AggregationResults<PrincipalWithName> results = mongoTemplate.aggregate(aggregation, "principals_mapping", PrincipalWithName.class);
// removes a clunky "_id" field that is generated when searching MongoDb
List<PrincipalWithName> mappedResults = results.getMappedResults();
for (PrincipalWithName p: mappedResults) {
p.person.remove("_id");
}
return mappedResults;
}
/**
* Reusable block of code to change the MongoDB document into being
* a Title for easier code reading
*
* @param input
* @return List of Title
*/
private List<Title> documentToTitleList(AggregationResults<Document> input) {
List<Title> titleList = new ArrayList<>();
Iterator<Document> iter = input.iterator();
// Declares reused variables
Title t;
Document nextDoc, resultDoc;
ArrayList<Document> titleInfo;
while (iter.hasNext()){
nextDoc = iter.next();
titleInfo = (ArrayList<Document>) nextDoc.get("title_info");
resultDoc = !titleInfo.isEmpty() ? titleInfo.get(0) : null;
if (resultDoc != null) {
t = new Title();
t.tconst = (String) resultDoc.get("tconst");
t.titleType = (String) resultDoc.get("titleType");
t.primaryTitle = (String) resultDoc.get("primaryTitle");
t.originalTitle = (String) resultDoc.get("originalTitle");
t.isAdult = integerToBoolean.convert(((Integer) resultDoc.get("isAdult")));
t.startYear = (Integer) resultDoc.get("startYear");
String endYear = emptyStringToNull.convert((String) resultDoc.get("endYear"));
t.endYear = endYear == null ? null : Integer.parseInt(endYear);
t.runtimeMinutes = (Integer) resultDoc.get("runtimeMinutes");
String genres = (String) resultDoc.get("genres");
t.genres = genres != null ? asList((genres).split(",")) : null;
titleList.add(t);
}
}
return titleList;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,24 @@
package com.microsoft.cse.reference.spring.dal.controllers;
import com.microsoft.cse.reference.spring.dal.models.Person;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Create a repository of Person models (mounted at /people) to facilitate route generation
* for model CRUD operations as well as custom queries
*/
@Repository
@RepositoryRestResource(path="people")
public interface PersonRepository extends MongoRepository<Person, String> {
/**
* Create a custom query for searching by primaryName
* @param primaryName the person primary name
* @return the person(s)
*/
List<Person> findByPrimaryName(@Param("primaryName") String primaryName);
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,31 @@
package com.microsoft.cse.reference.spring.dal.controllers;
import com.microsoft.cse.reference.spring.dal.models.Principal;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Create a repository of Principal models (not externally mounted) to facilitate route generation
* for model CRUD operations as well as custom queries
*/
@Repository
@RepositoryRestResource(exported = false)
public interface PrincipalRepository extends MongoRepository<Principal, String> {
/**
* Create a custom query for searching by tconst
* @param tconst the tconst value
* @return the principal(s)
*/
List<Principal> findByTconst(@Param("tconst") String tconst);
/**
* Create a custom query for searching by nconst
* @param nconst the nconst value
* @return the principal(s)
*/
List<Principal> findByNconst(@Param("nconst") String nconst);
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,24 @@
package com.microsoft.cse.reference.spring.dal.controllers;
import com.microsoft.cse.reference.spring.dal.models.Title;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Create a repository of Title models (mounted at /titles) to facilitate route generation
* for model CRUD operations as well as custom queries
*/
@Repository
@RepositoryRestResource(path = "titles")
public interface TitleRepository extends MongoRepository<Title, String> {
/**
* Create a custom query for searching by primaryTitle
* @param primaryTitle the title primary title
* @return the title(s)
*/
List<Title> findByPrimaryTitle(@Param("primaryTitle") String primaryTitle);
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,14 @@
package com.microsoft.cse.reference.spring.dal.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.stereotype.Component;
@Component
@WritingConverter
public class BooleanToInteger implements Converter<Boolean,Integer> {
@Override
public Integer convert(Boolean bool) {
return bool == true ? 1 : 0;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,14 @@
package com.microsoft.cse.reference.spring.dal.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.stereotype.Component;
@Component
@ReadingConverter
public class EmptyStringToNull implements Converter<String,String> {
@Override
public String convert(String str) {
return str == null || str.isEmpty() ? null : str;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,14 @@
package com.microsoft.cse.reference.spring.dal.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.stereotype.Component;
@Component
@ReadingConverter
public class IntegerToBoolean implements Converter<Integer,Boolean> {
@Override
public Boolean convert(Integer integer) {
return !(integer == null || integer == 0);
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,55 @@
package com.microsoft.cse.reference.spring.dal.converters;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Parses json arrays and csv literals to string lists
*
* Note: we don't convert back, so this is also a data cleaning component - that is,
* any new writes will have a normalized schema using an array, not a string literal
*
* If we didn't want this behavior, we'd need a more complicated @WritingConverter to match
*/
@Component
@ReadingConverter
public class JsonArrayToStringList implements Converter<String, List<String>> {
@Override
public List<String> convert(String str) {
try {
str = str.isEmpty() || str == null ? "[]" : str;
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(str);
if (!jsonNode.isArray()) {
throw new IOException(Constants.ERR_TEST_DATA_FORMAT);
} else {
ArrayList<String> results = new ArrayList<>();
Iterator<JsonNode> it = jsonNode.elements();
while (it.hasNext()) {
results.add(it.next().asText());
}
return results;
}
} catch (IOException e) {
if (str.contains(",")) {
return Arrays.asList(str.split(","));
} else {
return Arrays.asList(str);
}
}
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,14 @@
package com.microsoft.cse.reference.spring.dal.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.stereotype.Component;
@Component
@WritingConverter
public class NullToEmptyString implements Converter<String,String> {
@Override
public String convert(String str) {
return str == null ? "" : str;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,20 @@
package com.microsoft.cse.reference.spring.dal.models;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Document(collection = Constants.DB_PERSON_COLLECTION)
public class Person {
private ObjectId id;
@Id
public String nconst;
public String primaryName;
public Integer birthYear;
public Integer deathYear;
public List<String> primaryProfession;
public List<String> knownForTitles;
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,20 @@
package com.microsoft.cse.reference.spring.dal.models;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Document(collection = Constants.DB_PRINCIPAL_COLLECTION)
public class Principal {
@Id
private ObjectId id;
public String tconst;
public Integer ordering;
public String nconst;
public String category;
public String job;
public List<String> characters;
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,18 @@
package com.microsoft.cse.reference.spring.dal.models;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import java.util.LinkedHashMap;
import java.util.List;
public class PrincipalWithName {
@Id
private ObjectId id;
public String tconst;
public Integer ordering;
public LinkedHashMap<?,?> person;
public String category;
public String job;
public List<String> characters;
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,23 @@
package com.microsoft.cse.reference.spring.dal.models;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Document(collection = Constants.DB_TITLE_COLLECTION)
public class Title {
private ObjectId id;
@Id
public String tconst;
public String titleType;
public String primaryTitle;
public String originalTitle;
public Boolean isAdult;
public Integer startYear;
public Integer endYear;
public Integer runtimeMinutes;
public List<String> genres;
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings" schemaVersion="2014-05-30">
<!-- HTTP request component (not required for bare API) -->
<TelemetryModules>
<Add type="com.microsoft.applicationinsights.web.extensibility.modules.WebRequestTrackingTelemetryModule"/>
<Add type="com.microsoft.applicationinsights.web.extensibility.modules.WebSessionTrackingTelemetryModule"/>
<Add type="com.microsoft.applicationinsights.web.extensibility.modules.WebUserTrackingTelemetryModule"/>
</TelemetryModules>
<!-- Events correlation (not required for bare API) -->
<!-- These initializers add context data to each event -->
<TelemetryInitializers>
<Add type="com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationIdTelemetryInitializer"/>
<Add type="com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationNameTelemetryInitializer"/>
<Add type="com.microsoft.applicationinsights.web.extensibility.initializers.WebSessionTelemetryInitializer"/>
<Add type="com.microsoft.applicationinsights.web.extensibility.initializers.WebUserTelemetryInitializer"/>
<Add type="com.microsoft.applicationinsights.web.extensibility.initializers.WebUserAgentTelemetryInitializer"/>
</TelemetryInitializers>
</ApplicationInsights>

67
api/src/main/resources/testdata/names.testdata.json поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,67 @@
[
{
"nconst" : "nm0000496",
"primaryName" : "Juliette Lewis",
"birthYear" : 1973,
"deathYear" : "",
"primaryProfession" : "actress,soundtrack,director",
"knownForTitles" : "tt0116367,tt1322269,tt0110632,tt0101540"
},
{
"nconst" : "nm0000342",
"primaryName" : "James Cromwell",
"birthYear" : 1940,
"deathYear" : "",
"primaryProfession" : "actor,producer,soundtrack",
"knownForTitles" : "tt0112431,tt0120689,tt2245084,tt0119488"
},
{
"nconst" : "nm0000230",
"primaryName" : "Sylvester Stallone",
"birthYear" : 1946,
"deathYear" : "",
"primaryProfession" : "actor,writer,producer",
"knownForTitles" : "tt3076658,tt0089927,tt0084602,tt0075148"
},
{
"nconst" : "nm0001500",
"primaryName" : "Karl Malden",
"birthYear" : 1912,
"deathYear" : 2009,
"primaryProfession" : "actor,soundtrack,director",
"knownForTitles" : "tt0047296,tt0066206,tt0048973,tt0044081"
},
{
"nconst" : "nm0000548",
"primaryName" : "Elizabeth Montgomery",
"birthYear" : 1933,
"deathYear" : 1995,
"primaryProfession" : "actress,soundtrack,miscellaneous",
"knownForTitles" : "tt0076981,tt0088713,tt0057733,tt0073273"
},
{
"nconst":"nm0000428",
"primaryName": "D.W. Griffith",
"birthYear": 1875,
"deathYear": 1948,
"primaryProfession": "director,writer,producer",
"knownForTitles": "tt0006864,tt0010484,tt0004972,tt0012532"
},
{
"nconst":"nm0492757",
"primaryName": "Florence Lawrence",
"birthYear": 1886,
"deathYear": 1938,
"primaryProfession": "actress",
"knownForTitles": "tt0143358,tt0200577,tt0000770,tt0200909"
},
{
"nconst":"nm0555522",
"primaryName": "Arthur Marvin",
"birthYear": 1859,
"deathYear": 1911,
"primaryProfession": "cinematographer,director,camera_department",
"knownForTitles": "tt0291476,tt0000412,tt0233612,tt0300052"
}
]

63
api/src/main/resources/testdata/principals_mapping.testdata.json поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,63 @@
[
{
"tconst" : "tt0000843",
"ordering" : 6,
"nconst" : "nm0878494",
"category" : "writer",
"job" : "story",
"characters" : ""
},
{
"tconst" : "tt0000854",
"ordering" : 5,
"nconst" : "nm0000428",
"category" : "director",
"job" : "",
"characters" : ""
},
{
"tconst" : "tt0000442",
"ordering" : 1,
"nconst" : "nm0622273",
"category" : "actress",
"job" : "",
"characters" : "[\"Barnemordersken\"]"
},
{
"tconst" : "tt0001008",
"ordering" : 1,
"nconst" : "nm0819384",
"category" : "actress",
"job" : "",
"characters" : "[\"The Prince\",\"Tom Canty\"]"
},
{
"tconst" : "tt0000698",
"ordering" : 3,
"nconst" : "nm0000428",
"category" : "actor",
"job" : "",
"characters" : "[\"Footman\"]"
},
{
"tconst" : "tt0092377",
"ordering" : 4,
"nconst" : "nm0000496",
"category" : "actress",
"characters" : "[\"Kate Farrell\"]"
},
{
"tconst" : "tt0000698",
"ordering" : 1,
"nconst" : "nm0492757",
"category" : "actress",
"characters" : "[\"O'Yama\"]"
},
{
"tconst" : "tt0000698",
"ordering" : 6,
"nconst" : "nm0555522",
"category" : "cinematographer",
"characters" : ""
}
]

68
api/src/main/resources/testdata/titles.testdata.json поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,68 @@
[
{
"tconst" : "tt0000003",
"titleType" : "short",
"primaryTitle" : "Pauvre Pierrot",
"originalTitle" : "Pauvre Pierrot",
"isAdult" : 0,
"startYear" : 1892,
"endYear" : "",
"runtimeMinutes" : 4,
"genres" : "Animation,Comedy,Romance"
},
{
"tconst" : "tt0000192",
"titleType" : "short",
"primaryTitle" : "Ella Lola, a la Trilby",
"originalTitle" : "Ella Lola, a la Trilby",
"isAdult" : 0,
"startYear" : 1898,
"endYear" : "",
"runtimeMinutes" : "",
"genres" : "Short"
},
{
"tconst" : "tt0001022",
"titleType" : "short",
"primaryTitle" : "A Rose of the Tenderloin",
"originalTitle" : "A Rose of the Tenderloin",
"isAdult" : 0,
"startYear" : 1909,
"endYear" : "",
"runtimeMinutes" : "",
"genres" : "Drama,Short"
},
{
"tconst" : "tt0001008",
"titleType" : "short",
"primaryTitle" : "The Prince and the Pauper",
"originalTitle" : "The Prince and the Pauper",
"isAdult" : 0,
"startYear" : 1909,
"endYear" : "",
"runtimeMinutes" : "",
"genres" : "Short"
},
{
"tconst" : "tt0075472",
"titleType" : "tvSeries",
"primaryTitle" : "All Creatures Great and Small",
"originalTitle" : "All Creatures Great and Small",
"isAdult" : 0,
"startYear" : 1978,
"endYear" : 1990,
"runtimeMinutes" : 50,
"genres" : "Comedy,Drama"
},
{
"tconst":"tt0000698",
"titleType": "short",
"primaryTitle": "The Heart of O Yama",
"originalTitle": "The Heart of O Yama",
"isAdult": 0,
"startYear": 1908,
"endYear": "",
"runtimeMinutes": 15,
"genres": "Drama,Romance,Short"
}
]

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,40 @@
package com.microsoft.cse.reference.spring.dal.integration;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = BasicExclusionTests.Config.class)
public class BasicExclusionTests {
@Autowired
TestRestTemplate rest;
@LocalServerPort
Integer httpPort;
@Test
public void ExcludeTitleRepository() {
ResponseEntity<String> res = this.rest.getForEntity("http://localhost:" + httpPort + "/titles", String.class);
Assert.assertTrue(res.getStatusCode().is4xxClientError());
}
/**
* A configuration instance for these tests
*/
public static class Config extends PropertyMockingApplicationContextInitializer {
@Override
protected String[] getExcludeList() {
// we wish to disable the TitleRepository for the tests above, so we exclude them
return new String[] { "TitleRepository" };
}
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,371 @@
package com.microsoft.cse.reference.spring.dal.integration;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Note: we do integration tests for the endpoints, as unit tests aren't feasible
* https://stackoverflow.com/questions/23435937/how-to-test-spring-data-repositories
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = BasicRouteTests.Config.class)
public class BasicRouteTests {
@Autowired
TestRestTemplate rest;
@LocalServerPort
Integer httpPort;
@Test
public void ValidateTitlesEndpoint() throws JSONException {
String obj = this.rest.getForObject("http://localhost:" + httpPort + "/titles", String.class);
String raw = "{\n" +
" \"_embedded\" : {\n" +
" \"titles\" : [ {\n" +
" \"tconst\" : \"tt0000003\",\n" +
" \"titleType\" : \"short\",\n" +
" \"primaryTitle\" : \"Pauvre Pierrot\",\n" +
" \"originalTitle\" : \"Pauvre Pierrot\",\n" +
" \"isAdult\" : false,\n" +
" \"startYear\" : 1892,\n" +
" \"endYear\" : null,\n" +
" \"runtimeMinutes\" : 4,\n" +
" \"genres\" : [ \"Animation\", \"Comedy\", \"Romance\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0000003\"\n" +
" },\n" +
" \"title\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0000003\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"tconst\" : \"tt0000192\",\n" +
" \"titleType\" : \"short\",\n" +
" \"primaryTitle\" : \"Ella Lola, a la Trilby\",\n" +
" \"originalTitle\" : \"Ella Lola, a la Trilby\",\n" +
" \"isAdult\" : false,\n" +
" \"startYear\" : 1898,\n" +
" \"endYear\" : null,\n" +
" \"runtimeMinutes\" : null,\n" +
" \"genres\" : [ \"Short\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0000192\"\n" +
" },\n" +
" \"title\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0000192\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"tconst\" : \"tt0001022\",\n" +
" \"titleType\" : \"short\",\n" +
" \"primaryTitle\" : \"A Rose of the Tenderloin\",\n" +
" \"originalTitle\" : \"A Rose of the Tenderloin\",\n" +
" \"isAdult\" : false,\n" +
" \"startYear\" : 1909,\n" +
" \"endYear\" : null,\n" +
" \"runtimeMinutes\" : null,\n" +
" \"genres\" : [ \"Drama\", \"Short\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0001022\"\n" +
" },\n" +
" \"title\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0001022\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"tconst\" : \"tt0001008\",\n" +
" \"titleType\" : \"short\",\n" +
" \"primaryTitle\" : \"The Prince and the Pauper\",\n" +
" \"originalTitle\" : \"The Prince and the Pauper\",\n" +
" \"isAdult\" : false,\n" +
" \"startYear\" : 1909,\n" +
" \"endYear\" : null,\n" +
" \"runtimeMinutes\" : null,\n" +
" \"genres\" : [ \"Short\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0001008\"\n" +
" },\n" +
" \"title\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0001008\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"tconst\" : \"tt0075472\",\n" +
" \"titleType\" : \"tvSeries\",\n" +
" \"primaryTitle\" : \"All Creatures Great and Small\",\n" +
" \"originalTitle\" : \"All Creatures Great and Small\",\n" +
" \"isAdult\" : false,\n" +
" \"startYear\" : 1978,\n" +
" \"endYear\" : 1990,\n" +
" \"runtimeMinutes\" : 50,\n" +
" \"genres\" : [ \"Comedy\", \"Drama\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0075472\"\n" +
" },\n" +
" \"title\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/tt0075472\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"titleType\": \"short\",\n" +
" \"primaryTitle\": \"The Heart of O Yama\",\n" +
" \"originalTitle\": \"The Heart of O Yama\",\n" +
" \"isAdult\": false,\n" +
" \"startYear\": 1908,\n" +
" \"endYear\": null,\n" +
" \"runtimeMinutes\": 15,\n" +
" \"genres\": [ \"Drama\", \"Romance\", \"Short\" ],\n" +
" \"_links\": {\n" +
" \"self\": {\n" +
" \"href\": \"http://localhost:8080/titles/tt0000698\"\n" +
" },\n" +
" \"title\": {\n" +
" \"href\": \"http://localhost:8080/titles/tt0000698\"\n" +
" }\n" +
" }\n" +
" } ]" +
" }, " +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/titles{?page,size,sort}\",\n" +
" \"templated\" : true\n" +
" },\n" +
" \"profile\" : {\n" +
" \"href\" : \"http://localhost:8080/profile/titles\"\n" +
" },\n" +
" \"search\" : {\n" +
" \"href\" : \"http://localhost:8080/titles/search\"\n" +
" }\n" +
" },\n" +
" \"page\" : {\n" +
" \"size\" : 20,\n" +
" \"totalElements\" : 6,\n" +
" \"totalPages\" : 1,\n" +
" \"number\" : 0\n" +
" }\n" +
"}";
raw = raw.replace("localhost:8080/", "localhost:" + httpPort + "/");
JSONObject expected = new JSONObject(raw);
JSONAssert.assertEquals(expected, new JSONObject(obj), true);
}
@Test
public void ValidatePeopleEndpoint() throws JSONException {
String obj = this.rest.getForObject("http://localhost:" + httpPort + "/people", String.class);
String raw = "{\n" +
" \"_embedded\" : {\n" +
" \"persons\" : [ {\n" +
" \"nconst\" : \"nm0000496\",\n" +
" \"primaryName\" : \"Juliette Lewis\",\n" +
" \"birthYear\" : 1973,\n" +
" \"deathYear\" : null,\n" +
" \"primaryProfession\" : [ \"actress\", \"soundtrack\", \"director\" ],\n" +
" \"knownForTitles\" : [ \"tt0116367\", \"tt1322269\", \"tt0110632\", \"tt0101540\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000496\"\n" +
" },\n" +
" \"person\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000496\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"nconst\" : \"nm0000342\",\n" +
" \"primaryName\" : \"James Cromwell\",\n" +
" \"birthYear\" : 1940,\n" +
" \"deathYear\" : null,\n" +
" \"primaryProfession\" : [ \"actor\", \"producer\", \"soundtrack\" ],\n" +
" \"knownForTitles\" : [ \"tt0112431\", \"tt0120689\", \"tt2245084\", \"tt0119488\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000342\"\n" +
" },\n" +
" \"person\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000342\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"nconst\" : \"nm0000230\",\n" +
" \"primaryName\" : \"Sylvester Stallone\",\n" +
" \"birthYear\" : 1946,\n" +
" \"deathYear\" : null,\n" +
" \"primaryProfession\" : [ \"actor\", \"writer\", \"producer\" ],\n" +
" \"knownForTitles\" : [ \"tt3076658\", \"tt0089927\", \"tt0084602\", \"tt0075148\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000230\"\n" +
" },\n" +
" \"person\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000230\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"nconst\" : \"nm0001500\",\n" +
" \"primaryName\" : \"Karl Malden\",\n" +
" \"birthYear\" : 1912,\n" +
" \"deathYear\" : 2009,\n" +
" \"primaryProfession\" : [ \"actor\", \"soundtrack\", \"director\" ],\n" +
" \"knownForTitles\" : [ \"tt0047296\", \"tt0066206\", \"tt0048973\", \"tt0044081\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0001500\"\n" +
" },\n" +
" \"person\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0001500\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"nconst\" : \"nm0000548\",\n" +
" \"primaryName\" : \"Elizabeth Montgomery\",\n" +
" \"birthYear\" : 1933,\n" +
" \"deathYear\" : 1995,\n" +
" \"primaryProfession\" : [ \"actress\", \"soundtrack\", \"miscellaneous\" ],\n" +
" \"knownForTitles\" : [ \"tt0076981\", \"tt0088713\", \"tt0057733\", \"tt0073273\" ],\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000548\"\n" +
" },\n" +
" \"person\" : {\n" +
" \"href\" : \"http://localhost:8080/people/nm0000548\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"nconst\": \"nm0000428\",\n" +
" \"primaryName\": \"D.W. Griffith\",\n" +
" \"birthYear\": 1875,\n" +
" \"deathYear\": 1948,\n" +
" \"primaryProfession\": [ \"director\", \"writer\", \"producer\" ],\n" +
" \"knownForTitles\": [ \"tt0006864\", \"tt0010484\", \"tt0004972\", \"tt0012532\" ],\n" +
" \"_links\": {\n" +
" \"self\": {\n" +
" \"href\": \"http://localhost:8080/people/nm0000428\"\n" +
" },\n" +
" \"person\": {\n" +
" \"href\": \"http://localhost:8080/people/nm0000428\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"nconst\": \"nm0492757\",\n" +
" \"primaryName\": \"Florence Lawrence\",\n" +
" \"birthYear\": 1886,\n" +
" \"deathYear\": 1938,\n" +
" \"primaryProfession\": [\n \"actress\" ],\n" +
" \"knownForTitles\": [ \"tt0143358\", \"tt0200577\", \"tt0000770\", \"tt0200909\" ],\n" +
" \"_links\": {\n" +
" \"self\": {\n" +
" \"href\": \"http://localhost:8080/people/nm0492757\"\n" +
" },\n" +
" \"person\": {\n" +
" \"href\": \"http://localhost:8080/people/nm0492757\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"nconst\": \"nm0555522\",\n" +
" \"primaryName\": \"Arthur Marvin\",\n" +
" \"birthYear\": 1859,\n" +
" \"deathYear\": 1911,\n" +
" \"primaryProfession\": [ \"cinematographer\", \"director\", \"camera_department\" ],\n" +
" \"knownForTitles\": [ \"tt0291476\", \"tt0000412\", \"tt0233612\", \"tt0300052\" ],\n" +
" \"_links\": {\n" +
" \"self\": {\n" +
" \"href\": \"http://localhost:8080/people/nm0555522\"\n" +
" },\n" +
" \"person\": {\n" +
" \"href\": \"http://localhost:8080/people/nm0555522\"\n" +
" }\n" +
" }\n" +
" }" +
" ]\n" +
"},\n" +
" \"_links\" : {\n" +
" \"self\" : {\n" +
" \"href\" : \"http://localhost:8080/people{?page,size,sort}\",\n" +
" \"templated\" : true\n" +
" },\n" +
" \"profile\" : {\n" +
" \"href\" : \"http://localhost:8080/profile/people\"\n" +
" },\n" +
" \"search\" : {\n" +
" \"href\" : \"http://localhost:8080/people/search\"\n" +
" }\n" +
" },\n" +
" \"page\" : {\n" +
" \"size\" : 20,\n" +
" \"totalElements\" : 8,\n" +
" \"totalPages\" : 1,\n" +
" \"number\" : 0\n" +
" }\n" +
"}";
raw = raw.replace("localhost:8080/", "localhost:" + httpPort + "/");
JSONObject expected = new JSONObject(raw);
JSONAssert.assertEquals(expected, new JSONObject(obj), true);
}
@Test
public void CORS_Success_TitlesEndpointResponse() throws URISyntaxException {
ResponseEntity<String> resTitles = this.rest.exchange(RequestEntity
.get(new URI("http://localhost:" + httpPort + "/titles"))
.header(HttpHeaders.ORIGIN, "http://test.com")
.build(),
String.class
);
Assert.assertTrue(resTitles.getStatusCode().is2xxSuccessful());
Assert.assertEquals(resTitles.getHeaders().getAccessControlAllowOrigin(), "*");
}
@Test
public void CORS_Success_PeopleEndpointResponse() throws URISyntaxException {
ResponseEntity<String> resPeople = this.rest.exchange(RequestEntity
.get(new URI("http://localhost:" + httpPort + "/people"))
.header(HttpHeaders.ORIGIN, "http://test.com")
.build(),
String.class
);
Assert.assertTrue(resPeople.getStatusCode().is2xxSuccessful());
Assert.assertEquals(resPeople.getHeaders().getAccessControlAllowOrigin(), "*");
}
/**
* A configuration instance for these tests
*/
public static class Config extends PropertyMockingApplicationContextInitializer {
@Override
protected String getAllowedOrigin() {
// we wish to allow * with CORS for the tests above
return "*";
}
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,192 @@
package com.microsoft.cse.reference.spring.dal.integration;
import org.json.JSONArray;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = CustomRouteTests.Config.class)
public class CustomRouteTests {
@Autowired
TestRestTemplate rest;
@LocalServerPort
Integer httpPort;
@Test
public void ValidatePeopleFromTitlesEndpoint() throws JSONException {
String obj = this.rest.getForObject("http://localhost:" + httpPort + "/titles/tt0000698/people", String.class);
String raw = "[\n" +
" {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"ordering\": 3,\n" +
" \"person\": {\n" +
" \"nconst\": \"nm0000428\",\n" +
" \"primaryName\": \"D.W. Griffith\",\n" +
" \"birthYear\": 1875,\n" +
" \"deathYear\": 1948,\n" +
" \"primaryProfession\": \"director,writer,producer\",\n" +
" \"knownForTitles\": \"tt0006864,tt0010484,tt0004972,tt0012532\"\n" +
" },\n" +
" \"category\": \"actor\",\n" +
" \"job\": null,\n" +
" \"characters\": [\n" +
" \"Footman\"\n" +
" ]\n" +
" },\n" +
" {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"ordering\": 1,\n" +
" \"person\": {\n" +
" \"nconst\": \"nm0492757\",\n" +
" \"primaryName\": \"Florence Lawrence\",\n" +
" \"birthYear\": 1886,\n" +
" \"deathYear\": 1938,\n" +
" \"primaryProfession\": \"actress\",\n" +
" \"knownForTitles\": \"tt0143358,tt0200577,tt0000770,tt0200909\"\n" +
" },\n" +
" \"category\": \"actress\",\n" +
" \"job\": null,\n" +
" \"characters\": [\n" +
" \"O'Yama\"\n" +
" ]\n" +
" },\n" +
" {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"ordering\": 6,\n" +
" \"person\": {\n" +
" \"nconst\": \"nm0555522\",\n" +
" \"primaryName\": \"Arthur Marvin\",\n" +
" \"birthYear\": 1859,\n" +
" \"deathYear\": 1911,\n" +
" \"primaryProfession\": \"cinematographer,director,camera_department\",\n" +
" \"knownForTitles\": \"tt0291476,tt0000412,tt0233612,tt0300052\"\n" +
" },\n" +
" \"category\": \"cinematographer\",\n" +
" \"job\": null,\n" +
" \"characters\": []\n" +
" }\n" +
"]";
JSONArray expected = new JSONArray(raw);
JSONAssert.assertEquals(expected, new JSONArray(obj), true);
}
@Test
public void ValidateCastFromTitlesEndpoint() throws JSONException {
String obj = this.rest.getForObject("http://localhost:" + httpPort + "/titles/tt0000698/cast", String.class);
String raw = "[\n" +
" {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"ordering\": 3,\n" +
" \"person\": {\n" +
" \"nconst\": \"nm0000428\",\n" +
" \"primaryName\": \"D.W. Griffith\",\n" +
" \"birthYear\": 1875,\n" +
" \"deathYear\": 1948,\n" +
" \"primaryProfession\": \"director,writer,producer\",\n" +
" \"knownForTitles\": \"tt0006864,tt0010484,tt0004972,tt0012532\"\n" +
" },\n" +
" \"category\": \"actor\",\n" +
" \"job\": null,\n" +
" \"characters\": [\n" +
" \"Footman\"\n" +
" ]\n" +
" },\n" +
" {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"ordering\": 1,\n" +
" \"person\": {\n" +
" \"nconst\": \"nm0492757\",\n" +
" \"primaryName\": \"Florence Lawrence\",\n" +
" \"birthYear\": 1886,\n" +
" \"deathYear\": 1938,\n" +
" \"primaryProfession\": \"actress\",\n" +
" \"knownForTitles\": \"tt0143358,tt0200577,tt0000770,tt0200909\"\n" +
" },\n" +
" \"category\": \"actress\",\n" +
" \"job\": null,\n" +
" \"characters\": [\n" +
" \"O'Yama\"\n" +
" ]\n" +
" }\n" +
"]";
JSONArray expected = new JSONArray(raw);
JSONAssert.assertEquals(expected, new JSONArray(obj), true);
}
@Test
public void ValidateCrewFromTitlesEndpoint() throws JSONException {
String obj = this.rest.getForObject("http://localhost:" + httpPort + "/titles/tt0000698/crew", String.class);
String raw = "[\n" +
" {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"ordering\": 6,\n" +
" \"person\": {\n" +
" \"nconst\": \"nm0555522\",\n" +
" \"primaryName\": \"Arthur Marvin\",\n" +
" \"birthYear\": 1859,\n" +
" \"deathYear\": 1911,\n" +
" \"primaryProfession\": \"cinematographer,director,camera_department\",\n" +
" \"knownForTitles\": \"tt0291476,tt0000412,tt0233612,tt0300052\"\n" +
" },\n" +
" \"category\": \"cinematographer\",\n" +
" \"job\": null,\n" +
" \"characters\": []\n" +
" }\n" +
"]";
JSONArray expected = new JSONArray(raw);
JSONAssert.assertEquals(expected, new JSONArray(obj), true);
}
@Test
public void ValidateTitlesFromPeopleEndpoint() throws JSONException {
String obj = this.rest.getForObject("http://localhost:" + httpPort + "/people/nm0000428/titles", String.class);
String raw = "[\n" +
" {\n" +
" \"tconst\": \"tt0000698\",\n" +
" \"titleType\": \"short\",\n" +
" \"primaryTitle\": \"The Heart of O Yama\",\n" +
" \"originalTitle\": \"The Heart of O Yama\",\n" +
" \"isAdult\": false,\n" +
" \"startYear\": 1908,\n" +
" \"endYear\": null,\n" +
" \"runtimeMinutes\": 15,\n" +
" \"genres\": [\n" +
" \"Drama\",\n" +
" \"Romance\",\n" +
" \"Short\"\n" +
" ]\n" +
" }\n" +
"]";
JSONArray expected = new JSONArray(raw);
JSONAssert.assertEquals(expected, new JSONArray(obj), true);
}
/**
* A configuration instance for these tests
*/
public static class Config extends PropertyMockingApplicationContextInitializer {
@Override
protected String getAllowedOrigin() {
// we wish to allow * with CORS for the tests above
return "*";
}
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,35 @@
package com.microsoft.cse.reference.spring.dal.integration;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import java.io.IOException;
/**
* Integration test utilities
*/
public class Helpers {
/**
* Setup a mongo instance
* @param net the net instance to bind to
* @return the mongo instance
* @throws IOException thrown when unable to bind
*/
static MongodExecutable SetupMongo(Net net) throws IOException {
MongodStarter starter = MongodStarter.getDefaultInstance();
IMongodConfig mongodConfig = new MongodConfigBuilder()
.version(Version.Main.DEVELOPMENT)
.net(net)
.build();
MongodExecutable mongoProc = starter.prepare(mongodConfig);
mongoProc.start();
return mongoProc;
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,97 @@
package com.microsoft.cse.reference.spring.dal.integration;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.config.Net;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.mock.env.MockPropertySource;
import java.io.IOException;
/**
* Allows mocking of application context properties
*/
public abstract class PropertyMockingApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
/**
* Get the mongo data repository exclusion list
* @apiNote see Constants.ENV_EXCLUDE_FILTER for more info
* @return the mongo data repository exclusion list
*/
protected String[] getExcludeList() {
return new String[0];
}
/**
* Get the CORS allowed origin value
* @return the CORS allowed origin value
*/
protected String getAllowedOrigin() { return ""; }
/**
* Get the net binding information for the embedded mongo server
* @return net binding information
*/
protected Net getMongoNet() {
try {
return new Net();
} catch (IOException e) {
return null;
}
}
/**
* Get the database name
* @return the database name
*/
protected String getDbName() {
return "test";
}
/**
* Get the embedded mongo server instance
* @param bind the net binding information to bind to
* @return the embedded mongo server instance
*/
protected MongodExecutable getMongo(Net bind) {
try {
return Helpers.SetupMongo(bind);
} catch (IOException e) {
return null;
}
}
/**
* Get the rest template builder with which to test rest endpoints
* @return the rest template builder
*/
protected RestTemplateBuilder getRestTemplateBuilder() {
return new RestTemplateBuilder().setConnectTimeout(1000).setReadTimeout(1000);
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// configure a net binding instance
Net mongoNet = this.getMongoNet();
// register some autowire-able dependencies, to make leveraging the configured instances in a test possible
applicationContext.getBeanFactory().registerResolvableDependency(RestTemplateBuilder.class, this.getRestTemplateBuilder());
applicationContext.getBeanFactory().registerResolvableDependency(Net.class, mongoNet);
applicationContext.getBeanFactory().registerResolvableDependency(MongodExecutable.class, this.getMongo(mongoNet));
// configure the property sources that will be used by the application
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
MockPropertySource mockEnvVars = new MockPropertySource()
.withProperty(Constants.ENV_DB_NAME, this.getDbName())
.withProperty(Constants.ENV_DB_CONNSTR, "mongodb://localhost:" + mongoNet.getPort())
.withProperty(Constants.ENV_ALLOWED_ORIGIN, this.getAllowedOrigin())
.withProperty(Constants.ENV_EXCLUDE_FILTER, String.join(",", this.getExcludeList()));
// inject the property sources into the application as environment variables
propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,160 @@
package com.microsoft.cse.reference.spring.dal.unit;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import com.microsoft.cse.reference.spring.dal.config.DevelopmentConfig;
import com.microsoft.cse.reference.spring.dal.config.MongoConfig;
import com.microsoft.cse.reference.spring.dal.controllers.PersonRepository;
import com.microsoft.cse.reference.spring.dal.converters.BooleanToInteger;
import com.microsoft.cse.reference.spring.dal.converters.IntegerToBoolean;
import com.microsoft.cse.reference.spring.dal.models.Person;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Define the tests using the built-in DataMongoTest attribute
* However, since the builtin doesn't load other beans, we need to load
* the converters, and the config that loads the converters - we do that with @Import
*/
@RunWith(SpringRunner.class)
@DataMongoTest
@EnableWebSecurity
@EnableResourceServer
@Import({IntegerToBoolean.class,
BooleanToInteger.class,
MongoConfig.class,
DevelopmentConfig.class})
public class PersonDataTests {
@Autowired
public MongoTemplate mongoTemplate;
@Autowired
public PersonRepository repo;
@Before
public void setUp() {
this.mongoTemplate.dropCollection(Constants.DB_PERSON_COLLECTION);
this.mongoTemplate.insert(Document.parse("{\n" +
" \"nconst\" : \"nm0001500\",\n" +
" \"primaryName\" : \"Karl Malden\",\n" +
" \"birthYear\" : 1912,\n" +
" \"deathYear\" : 2009,\n" +
" \"primaryProfession\" : \"actor,soundtrack,director\",\n" +
" \"knownForTitles\" : \"tt0047296,tt0066206,tt0048973,tt0044081\"\n" +
" },"), Constants.DB_PERSON_COLLECTION);
}
@Test
public void findById_Success() {
Person actual = this.repo.findById("nm0001500").get();
assertThat(actual.nconst, is("nm0001500"));
assertThat(actual.primaryName, is("Karl Malden"));
assertThat(actual.birthYear, is(1912));
assertThat(actual.deathYear, is(2009));
assertThat(actual.primaryProfession, is(Arrays.asList("actor", "soundtrack", "director")));
assertThat(actual.knownForTitles, is(Arrays.asList("tt0047296", "tt0066206", "tt0048973", "tt0044081")));
}
@Test
public void findById_Failure() {
Optional<Person> actual = this.repo.findById("not-real");
assertThat(actual.isPresent(), is(false));
}
@Test
public void findByPrimaryName_Success() {
List<Person> actuals = this.repo.findByPrimaryName("Karl Malden");
assertThat(actuals.size(), is(1));
Person actual = actuals.get(0);
assertThat(actual.nconst, is("nm0001500"));
assertThat(actual.primaryName, is("Karl Malden"));
assertThat(actual.birthYear, is(1912));
assertThat(actual.deathYear, is(2009));
assertThat(actual.primaryProfession, is(Arrays.asList("actor", "soundtrack", "director")));
assertThat(actual.knownForTitles, is(Arrays.asList("tt0047296", "tt0066206", "tt0048973", "tt0044081")));
}
@Test
public void findByPrimaryName_Failure() {
List<Person> actuals = this.repo.findByPrimaryName("not real");
assertThat(actuals.size(), is(0));
}
@Test
public void deleteAll_Success() {
this.repo.deleteAll();
assertThat(this.repo.count(), is(0L));
}
@Test
public void deleteById_Success() {
this.repo.deleteById("nm0001500");
assertThat(this.repo.count(), is(0L));
}
@Test
public void insert_Success() {
String id = "nm0000001";
Person newPerson = new Person();
newPerson.nconst = id;
newPerson.birthYear = 2020;
newPerson.primaryName = "Tim Tam";
newPerson.primaryProfession = Arrays.asList("Test", "Dancer");
assertThat(this.repo.insert(newPerson), is(newPerson));
assertThat(this.repo.count(), is(2L));
Person actual = this.repo.findById(id).get();
assertThat(actual.nconst, is(id));
assertThat(actual.primaryName, is("Tim Tam"));
assertThat(actual.birthYear, is(2020));
assertThat(actual.deathYear, is(nullValue()));
assertThat(actual.primaryProfession, is(Arrays.asList("Test", "Dancer")));
assertThat(actual.knownForTitles, is(nullValue()));
}
@Test
public void update_Success() {
Person update = this.repo.findById("nm0001500").get();
update.deathYear = 2010;
update.primaryProfession.add("Test");
assertThat(this.repo.save(update), is(update));
Person actual = this.repo.findById("nm0001500").get();
assertThat(actual.nconst, is("nm0001500"));
assertThat(actual.primaryName, is("Karl Malden"));
assertThat(actual.birthYear, is(1912));
assertThat(actual.deathYear, is(2010));
assertThat(actual.primaryProfession, is(Arrays.asList("actor", "soundtrack", "director", "Test")));
assertThat(actual.knownForTitles, is(Arrays.asList("tt0047296", "tt0066206", "tt0048973", "tt0044081")));
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,175 @@
package com.microsoft.cse.reference.spring.dal.unit;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import com.microsoft.cse.reference.spring.dal.config.DevelopmentConfig;
import com.microsoft.cse.reference.spring.dal.config.MongoConfig;
import com.microsoft.cse.reference.spring.dal.controllers.PrincipalRepository;
import com.microsoft.cse.reference.spring.dal.converters.*;
import com.microsoft.cse.reference.spring.dal.models.Principal;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Define the tests using the built-in DataMongoTest attribute
* However, since the builtin doesn't load other beans, we need to load
* the converters, and the config that loads the converters - we do that with @Import
*/
@RunWith(SpringRunner.class)
@DataMongoTest
@EnableWebSecurity
@EnableResourceServer
@Import({EmptyStringToNull.class,
NullToEmptyString.class,
JsonArrayToStringList.class,
MongoConfig.class,
DevelopmentConfig.class})
public class PrincipalDataTests {
@Autowired
public MongoTemplate mongoTemplate;
@Autowired
public PrincipalRepository repo;
@Before
public void setUp() {
this.mongoTemplate.dropCollection(Constants.DB_PRINCIPAL_COLLECTION);
this.mongoTemplate.insert(Document.parse("{\n" +
" \"tconst\" : \"tt0000442\",\n" +
" \"ordering\" : 1,\n" +
" \"nconst\" : \"nm0622273\",\n" +
" \"category\" : \"actress\",\n" +
" \"job\" : \"\",\n" +
" \"characters\" : \"[\\\"Barnemordersken\\\"]\"\n" +
" },"), Constants.DB_PRINCIPAL_COLLECTION);
}
@Test
public void storageOperation_Success() {
// we aren't really testing findAll here, we're testing that
// our model inflates as expected - hence the name storageOperation
Principal actual = this.repo.findAll().get(0);
assertThat(actual.tconst, is("tt0000442"));
assertThat(actual.ordering, is(1));
assertThat(actual.nconst, is("nm0622273"));
assertThat(actual.category, is("actress"));
assertThat(actual.job, is(nullValue()));
assertThat(actual.characters, is(Arrays.asList("Barnemordersken")));
}
@Test
public void findById_Failure() {
Optional<Principal> actual = this.repo.findById("not-real");
assertThat(actual.isPresent(), is(false));
}
@Test
public void findByNconst_Success() {
List<Principal> actuals = this.repo.findByNconst("nm0622273");
assertThat(actuals.size(), is(1));
Principal actual = actuals.get(0);
assertThat(actual.tconst, is("tt0000442"));
assertThat(actual.ordering, is(1));
assertThat(actual.nconst, is("nm0622273"));
assertThat(actual.category, is("actress"));
assertThat(actual.job, is(nullValue()));
assertThat(actual.characters, is(Arrays.asList("Barnemordersken")));
}
@Test
public void findByNconst_Failure() {
List<Principal> actuals = this.repo.findByNconst("not real");
assertThat(actuals.size(), is(0));
}
@Test
public void findByTconst_Success() {
List<Principal> actuals = this.repo.findByTconst("tt0000442");
assertThat(actuals.size(), is(1));
Principal actual = actuals.get(0);
assertThat(actual.tconst, is("tt0000442"));
assertThat(actual.ordering, is(1));
assertThat(actual.nconst, is("nm0622273"));
assertThat(actual.category, is("actress"));
assertThat(actual.job, is(nullValue()));
assertThat(actual.characters, is(Arrays.asList("Barnemordersken")));
}
@Test
public void findByTconst_Failure() {
List<Principal> actuals = this.repo.findByTconst("not real");
assertThat(actuals.size(), is(0));
}
@Test
public void deleteAll_Success() {
this.repo.deleteAll();
assertThat(this.repo.count(), is(0L));
}
@Test
public void insert_Success() {
String id = "tt0000001";
Principal newPrincipal = new Principal();
newPrincipal.tconst = id;
newPrincipal.nconst = "nm0000001";
assertThat(this.repo.insert(newPrincipal), is(newPrincipal));
assertThat(this.repo.count(), is(2L));
Principal actual = this.repo.findAll().get(1);
assertThat(actual.tconst, is("tt0000001"));
assertThat(actual.ordering, is(nullValue()));
assertThat(actual.nconst, is("nm0000001"));
assertThat(actual.category, is(nullValue()));
assertThat(actual.job, is(nullValue()));
assertThat(actual.characters, is(nullValue()));
}
@Test
public void update_Success() {
Principal update = this.repo.findAll().get(0);
update.job = "Test";
update.characters.add("Test");
assertThat(this.repo.save(update), is(update));
Principal actual = this.repo.findAll().get(0);
assertThat(actual.tconst, is("tt0000442"));
assertThat(actual.ordering, is(1));
assertThat(actual.nconst, is("nm0622273"));
assertThat(actual.category, is("actress"));
assertThat(actual.job, is("Test"));
assertThat(actual.characters, is(Arrays.asList("Barnemordersken", "Test")));
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,174 @@
package com.microsoft.cse.reference.spring.dal.unit;
import com.microsoft.cse.reference.spring.dal.config.Constants;
import com.microsoft.cse.reference.spring.dal.config.DevelopmentConfig;
import com.microsoft.cse.reference.spring.dal.config.MongoConfig;
import com.microsoft.cse.reference.spring.dal.controllers.TitleRepository;
import com.microsoft.cse.reference.spring.dal.converters.BooleanToInteger;
import com.microsoft.cse.reference.spring.dal.converters.IntegerToBoolean;
import com.microsoft.cse.reference.spring.dal.models.Title;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Define the tests using the built-in DataMongoTest attribute
* However, since the builtin doesn't load other beans, we need to load
* the converters, and the config that loads the converters - we do that with @Import
*/
@RunWith(SpringRunner.class)
@DataMongoTest
@EnableWebSecurity
@EnableResourceServer
@Import({IntegerToBoolean.class,
BooleanToInteger.class,
MongoConfig.class,
DevelopmentConfig.class})
public class TitleDataTests {
@Autowired
public MongoTemplate mongoTemplate;
@Autowired
public TitleRepository repo;
@Before
public void setUp() {
this.mongoTemplate.dropCollection(Constants.DB_TITLE_COLLECTION);
this.mongoTemplate.insert(Document.parse("{\n" +
" \"tconst\" : \"tt0075472\",\n" +
" \"titleType\" : \"tvSeries\",\n" +
" \"primaryTitle\" : \"All Creatures Great and Small\",\n" +
" \"originalTitle\" : \"All Creatures Great and Small\",\n" +
" \"isAdult\" : 0,\n" +
" \"startYear\" : 1978,\n" +
" \"endYear\" : 1990,\n" +
" \"runtimeMinutes\" : 50,\n" +
" \"genres\" : \"Comedy,Drama\"\n" +
" }"), Constants.DB_TITLE_COLLECTION);
}
@Test
public void findById_Success() {
Title actual = this.repo.findById("tt0075472").get();
assertThat(actual.tconst, is("tt0075472"));
assertThat(actual.titleType, is("tvSeries"));
assertThat(actual.primaryTitle, is("All Creatures Great and Small"));
assertThat(actual.originalTitle, is("All Creatures Great and Small"));
assertThat(actual.isAdult, is(false));
assertThat(actual.startYear, is(1978));
assertThat(actual.endYear, is(1990));
assertThat(actual.runtimeMinutes, is(50));
assertThat(actual.genres, is(Arrays.asList("Comedy", "Drama")));
}
@Test
public void findById_Failure() {
Optional<Title> actual = this.repo.findById("not-real");
assertThat(actual.isPresent(), is(false));
}
@Test
public void findByPrimaryTitle_Success() {
List<Title> actuals = this.repo.findByPrimaryTitle("All Creatures Great and Small");
assertThat(actuals.size(), is(1));
Title actual = actuals.get(0);
assertThat(actual.tconst, is("tt0075472"));
assertThat(actual.titleType, is("tvSeries"));
assertThat(actual.primaryTitle, is("All Creatures Great and Small"));
assertThat(actual.originalTitle, is("All Creatures Great and Small"));
assertThat(actual.isAdult, is(false));
assertThat(actual.startYear, is(1978));
assertThat(actual.endYear, is(1990));
assertThat(actual.runtimeMinutes, is(50));
assertThat(actual.genres, is(Arrays.asList("Comedy", "Drama")));
}
@Test
public void findByPrimaryTitle_Failure() {
List<Title> actuals = this.repo.findByPrimaryTitle("not real");
assertThat(actuals.size(), is(0));
}
@Test
public void deleteAll_Success() {
this.repo.deleteAll();
assertThat(this.repo.count(), is(0L));
}
@Test
public void deleteById_Success() {
this.repo.deleteById("tt0075472");
assertThat(this.repo.count(), is(0L));
}
@Test
public void insert_Success() {
String id = "tt0000001";
Title newTitle = new Title();
newTitle.tconst = id;
newTitle.originalTitle = "Test Movie";
newTitle.isAdult = true;
newTitle.genres = Arrays.asList("Comedy", "Horror");
assertThat(this.repo.insert(newTitle), is(newTitle));
assertThat(this.repo.count(), is(2L));
Title actual = this.repo.findById(id).get();
assertThat(actual.tconst, is(id));
assertThat(actual.titleType, is(nullValue()));
assertThat(actual.primaryTitle, is(nullValue()));
assertThat(actual.originalTitle, is("Test Movie"));
assertThat(actual.isAdult, is(true));
assertThat(actual.startYear, is(nullValue()));
assertThat(actual.endYear, is(nullValue()));
assertThat(actual.runtimeMinutes, is(nullValue()));
assertThat(actual.genres, is(Arrays.asList("Comedy", "Horror")));
}
@Test
public void update_Success() {
Title update = this.repo.findById("tt0075472").get();
update.isAdult = true;
update.genres.add("Test");
assertThat(this.repo.save(update), is(update));
Title actual = this.repo.findById("tt0075472").get();
assertThat(actual.tconst, is("tt0075472"));
assertThat(actual.titleType, is("tvSeries"));
assertThat(actual.primaryTitle, is("All Creatures Great and Small"));
assertThat(actual.originalTitle, is("All Creatures Great and Small"));
assertThat(actual.isAdult, is(true));
assertThat(actual.startYear, is(1978));
assertThat(actual.endYear, is(1990));
assertThat(actual.runtimeMinutes, is(50));
assertThat(actual.genres, is(Arrays.asList("Comedy", "Drama", "Test")));
}
}

478
api/swagger.yml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,478 @@
swagger: "2.0"
info:
description: "The ProjectJackson API. Azure-based services."
version: "1.0.0"
title: "Project Jackson API"
schemes:
- "https"
paths:
/people:
post:
tags:
- people
- active
summary: "Creates a person"
description: "Returns information and ID of new person"
produces:
- "application/json"
parameters:
- in: body
name: person
required: true
schema:
$ref: "#/definitions/Person"
responses:
201:
description: Successful person creation
400:
description: Incorrect/incomplete person supplied
401:
description: Unauthorized
get:
tags:
- people
- active
summary: "An endpoint that returns some people objects"
description: "Returns a small number of people entries"
responses:
200:
description: Successful person creation
schema:
$ref: "#/definitions/Person"
401:
description: Unauthorized
"/people/{nconst}":
get:
tags:
- people
- active
summary: "Gets the person associated with ID"
description: "Returns information about person"
produces:
- "application/json"
parameters:
- in: path
name: nconst
description: "ID (nconst) of the person"
required: true
type: string
responses:
200:
description: Successful get of person
schema:
$ref: "#/definitions/Person"
401:
description: Unauthorized
404:
description: Person not found
put:
tags:
- people
- active
summary: "Updates a person for a given ID"
description: "Returns information about updated person"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: path
name: nconst
description: "ID (nconst) of the person"
required: true
type: string
- in: body
name: person
required: true
schema:
$ref: "#/definitions/Person"
responses:
200:
description: Successful update
schema:
$ref: "#/definitions/Person"
401:
description: Unauthorized
404:
description: Person not found
delete:
tags:
- people
- active
summary: "Deletes a person"
description: "Returns success/failure of deletion"
produces:
- "application/json"
parameters:
- in: path
name: nconst
description: "ID (nconst) of the person"
required: true
type: string
responses:
200:
description: Successful deletion
401:
description: Unauthorized
404:
description: Person not found
"/people/{nconst}/titles":
get:
tags:
- people
- active
summary: "Gets the titles associated with the person associated with specified ID"
description: "Returns array of titles"
produces:
- "application/json"
parameters:
- in: path
name: nconst
description: "ID (nconst) of the person"
required: true
type: string
responses:
200:
description: Successful return of titles
schema:
$ref: "#/definitions/Title"
401:
description: Unauthorized
404:
description: No valid ID Supplied
"/people/search":
get:
tags:
- people
- upcoming feature
summary: "Search people by specified terms"
description: "Returns collection of titles, where 'Type' may be like Role/Name/etc and 'Value' is the filtering search criterion"
produces:
- "application/json"
parameters:
- in: query
name: filterType
description: "Filtering type, eg Role, Name, etc."
required: true
type: string
- in: query
name: filterValue
description: "Filtering value"
required: true
type: string
responses:
200:
description: Successful return of people
schema:
$ref: "#/definitions/Person"
401:
description: Unauthorized
404:
description: No people found with matching criteria
/titles:
post:
tags:
- titles
- active
summary: "Creates a title"
description: "Returns information and ID of new title"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: body
name: title
description: "the details for a title"
required: true
schema:
$ref: "#/definitions/Title"
responses:
201:
description: Successful title creation
401:
description: Unauthorized
get:
tags:
- titles
- active
summary: "An endpoint that returns some title objects"
description: "Returns a small number of title entries"
responses:
200:
description: Successful return of some titles
schema:
$ref: "#/definitions/Title"
401:
description: Unauthorized
"/titles/{tconst}":
get:
tags:
- titles
- active
summary: "Gets the title of a piece given an ID"
description: "Returns information about title"
produces:
- "application/json"
parameters:
- in: path
name: tconst
description: "tconst of title to return"
required: true
type: string
responses:
200:
description: Successful get
schema:
$ref: "#/definitions/Title"
401:
description: Unauthorized
404:
description: Title not found
put:
tags:
- titles
- active
summary: "Updates the title of a piece given an ID"
description: "Returns information about title"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: path
name: tconst
description: "tconst of title to update and return"
required: true
type: string
responses:
201:
description: Successful update of Title
schema:
$ref: "#/definitions/Title"
401:
description: Unauthorized
404:
description: Title not found
delete:
tags:
- titles
- active
summary: "Deletes the piece of media given an ID"
description: "Returns success/failure message"
produces:
- "application/json"
parameters:
- in: path
name: tconst
description: "ID of title to delete"
required: true
type: string
responses:
200:
description: Successful deletion
401:
description: Unauthorized
404:
description: Title not found
"/titles/{tconst}/people":
get:
tags:
- titles
- active
- principalwithname
summary: "Gets the people popularly associated with the given title"
description: "Returns list of PrincipalWithName objects"
produces:
- "application/json"
parameters:
- in: path
name: tconst
description: "tconst of title to get"
required: true
type: string
responses:
200:
description: Successful get
schema:
$ref: "#/definitions/PrincipalWithName"
401:
description: Unauthorized
404:
description: Title not supplied/found
"/titles/{tconst}/cast":
get:
tags:
- titles
- active
- principalwithname
summary: "Gets the actors associated with the given title"
description: "Returns list of PrincipalWithName objects whose role is actor"
produces:
- "application/json"
parameters:
- in: path
name: tconst
description: "tconst of title to get"
required: true
type: string
responses:
200:
description: Successful get
schema:
$ref: "#/definitions/PrincipalWithName"
401:
description: Unauthorized
404:
description: Title not supplied/found
"/titles/{tconst}/crew":
get:
tags:
- titles
- active
- principalwithname
summary: "Gets the crew members associated with the given title"
description: "Returns list of PrincipalWithName objects whose roles are not actor"
produces:
- "application/json"
parameters:
- in: path
name: tconst
description: "tconst of title to get"
required: true
type: string
responses:
200:
description: Successful get
schema:
$ref: "#/definitions/PrincipalWithName"
401:
description: Unauthorized
404:
description: Title not supplied/found
"/titles/search":
get:
tags:
- titles
- upcoming feature
summary: "Search titles by specified terms"
description: "Returns collection of titles based on search criteria"
produces:
- "application/json"
parameters:
- in: query
name: filterType
description: "Filtering type"
required: true
type: string
- in: query
name: filterValue
description: "Filtering value"
required: true
type: string
responses:
200:
description: Successful return of titles
schema:
$ref: "#/definitions/Title"
401:
description: Unauthorized
404:
description: No people found with matching criteria
/search:
get:
tags:
- upcoming feature
summary: "Upcoming Feature: Performs an aggregate search"
description: "Upcoming Feature: Any title or person who has a field that matches the search term(s)"
produces:
- "application/json"
responses:
200:
description: Successful get
401:
description: Unauthorized
definitions:
Title:
properties:
tconst:
description: "A string of a sequential number prepended with 'tt'"
type: string
titleType:
description: "A string describing the type of content, eg. tvSeries, movie, tvMiniSeries"
type: string
primaryTitle:
description: "A string of the title of the piece of media"
type: string
originalTitle:
description: "A string of the original title of the piece of media, typically distinct from primaryTitle if film name is originally in a foreign language"
type: string
isAdult:
description: "An integer representation of a boolean (0 or 1) whether the content is adult, ie. typically of pornographic nature"
type: integer
format: int32
startYear:
description: "An integer of the year; for non-serialized content it's the release year"
type: integer
format: int32
endYear:
description: "An integer of the year; for serialized content it's the year media completes"
type: integer
format: int32
runtimeMinutes:
description: "An integer of the number of minutes long the content lasts"
type: integer
format: int32
genres:
description: "A array of strings for genres associated with the piece"
type: array
items:
type: string
Person:
properties:
nconst:
description: "A string of a sequential number prepended with 'nm'"
type: string
primaryName:
description: "A string representing a person's name"
type: string
birthYear:
description: "An integer representing the person's birth year"
type: integer
format: int32
deathYear:
description: "An integer representing the person's year of death"
type: integer
format: int32
primaryProfession:
description: "A string of a comma-separated list of professions"
type: string
knownForTitles:
description: "An array of strings of tconsts (from the titles)"
type: array
items:
type: string
PrincipalWithName:
properties:
tconst:
description: "A string representing a title's IMDb ID, one half of the title-person join"
type: string
ordering:
description: "An integer representing the billing order when searching on IMDb for the piece of media"
type: integer
format: int32
person:
description: "An LinkedHashMap object that maps to the fields found in an a Person object"
type: object
category:
description: "A string that contains what kind of role a Person has in a piece of media"
type: integer
format: int32
job:
description: "A string that has a person's job in the piece of media"
type: string
characters:
description: "An array of strings that optionally contain what characters a Person plays in the piece of media"
type: array
items:
type: string

14
data/getdata.sh Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,14 @@
#!/bin/bash
echo
echo "Getting IMDb data files (updated daily)..."
curl --remote-name-all https://datasets.imdbws.com/{name.basics.tsv.gz,title.basics.tsv.gz,title.principals.tsv.gz}
echo
echo "Unzipping the data files (overwrites any existing .tsv files)..."
gunzip -vf *.gz
echo
echo "Removing IMDb '\N' values..."
sed --in-place 's/\\N//g' *.tsv
echo
echo "IMDb data files ready:"
ls *.tsv
echo

93
data/importdata.sh Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,93 @@
#!/bin/bash
################################################################################
# Variables
################################################################################
resourceGroup=$RESOURCE_GROUP
cosmosName=$COSMOSDB_NAME
password=$COSMOSDB_PASSWORD
databaseName=IMDb
files=("title.basics.tsv" "name.basics.tsv" "title.principals.tsv")
collections=(titles names principals_mapping)
keys=("tconst" "nconst" "tconst")
len=${#collections[@]}
################################################################################
# Helpers
################################################################################
create_database() {
az cosmosdb database create -g $resourceGroup -n $cosmosName --db-name $databaseName > /dev/null
}
create_collections() {
for ((i=0; i<len; i++)); do
step=$((i + 1))
echo "($step of $len) Creating collection '${collections[i]}'"
partition="/'\$v'/${keys[$i]}/'\$v'"
az cosmosdb collection create -g $resourceGroup -n $cosmosName --db-name $databaseName --collection-name ${collections[$i]} \
--partition-key-path $partition --throughput 100000 > /dev/null
done
}
delete_tsv_files() {
for ((i=0; i<len; i++)); do
rm -v ${files[$i]}
done
}
import_data() {
for ((i=0; i<len; i++)); do
step=$((i + 1))
echo
echo "($step of $len) Importing collection ${collections[$i]}..."
hostName="${cosmosName}.documents.azure.com:10255"
user=$cosmosName
mongoimport --host $hostName -u $user -p $password --ssl --sslAllowInvalidCertificates --type tsv --headerline \
--db $databaseName --collection ${collections[$i]} --numInsertionWorkers 40 --file ${files[$i]}
echo
echo "${collections[$i]} import is complete. Reducing RUs to 1,000 to reduce cost."
done
}
set_throughput() {
collection=$1
RUs=$2
echo
echo "Setting ${collection} throughput to ${RUs}..."
az cosmosdb collection update -g $resourceGroup -n $cosmosName --db-name $databaseName --collection-name $collection --throughput $RUs
}
################################################################################
# Main script
################################################################################
set -e
echo
echo "Creating Cosmos DB database..."
create_database
echo
echo "Creating Cosmos DB collections..."
create_collections
echo
echo "Importing IMDb data to Cosmos DB..."
import_data
echo
echo "Finished importing data. Cleaning up..."
delete_tsv_files
echo
echo "Reducing throughput on Azure..."
set_throughput
echo
echo "Complete!"
echo

Π Π°Π·Π½ΠΈΡ†Π° ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ Π½Π΅ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° ΠΈΠ·-Π·Π° своСго большого Ρ€Π°Π·ΠΌΠ΅Ρ€Π° Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ Ρ€Π°Π·Π½ΠΈΡ†Ρƒ

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,645 @@
tconst titleType primaryTitle originalTitle isAdult startYear endYear runtimeMinutes genres
tt0120737 movie The Lord of the Rings: The Fellowship of the Ring The Lord of the Rings: The Fellowship of the Ring 0 2001 \N 178 Adventure,Drama,Fantasy
tt0133093 movie The Matrix The Matrix 0 1999 \N 136 Action,Sci-Fi
tt0166924 movie Mulholland Dr. Mulholland Dr. 0 2001 \N 147 Drama,Mystery,Thriller
tt0167260 movie The Lord of the Rings: The Return of the King The Lord of the Rings: The Return of the King 0 2003 \N 201 Action,Adventure,Drama
tt0167261 movie The Lord of the Rings: The Two Towers The Lord of the Rings: The Two Towers 0 2002 \N 179 Adventure,Drama,Fantasy
tt0168629 movie Dancer in the Dark Dancer in the Dark 0 2000 \N 140 Crime,Drama,Musical
tt0169102 movie Lagaan: Once Upon a Time in India Lagaan: Once Upon a Time in India 0 2001 \N 224 Adventure,Drama,Musical
tt0172495 movie Gladiator Gladiator 0 2000 \N 155 Action,Adventure,Drama
tt0180093 movie Requiem for a Dream Requiem for a Dream 0 2000 \N 102 Drama
tt0198781 movie Monsters, Inc. Monsters, Inc. 0 2001 \N 92 Adventure,Animation,Comedy
tt0208092 movie Snatch Snatch 0 2000 \N 104 Comedy,Crime
tt0209144 movie Memento Memento 0 2000 \N 113 Mystery,Thriller
tt0209180 movie Sky Hook Nebeska udica 0 2000 \N 95 Drama,War
tt0222012 movie Hey Ram Hey Ram 0 2000 \N 186 Crime,Drama,History
tt0242256 movie Alai Payuthey Alai Payuthey 0 2000 \N 156 Drama,Musical,Romance
tt0242519 movie Chicanery Hera Pheri 0 2000 \N 156 Action,Comedy,Crime
tt0246578 movie Donnie Darko Donnie Darko 0 2001 \N 113 Drama,Sci-Fi,Thriller
tt0253474 movie The Pianist The Pianist 0 2002 \N 150 Biography,Drama,Music
tt0264464 movie Catch Me If You Can Catch Me If You Can 0 2002 \N 141 Biography,Crime,Drama
tt0264476 movie Children Underground Children Underground 0 2001 \N 104 Documentary
tt0266543 movie Finding Nemo Finding Nemo 0 2003 \N 100 Adventure,Animation,Comedy
tt0266697 movie Kill Bill: Vol. 1 Kill Bill: Vol. 1 0 2003 \N 111 Action,Crime,Thriller
tt0268978 movie A Beautiful Mind A Beautiful Mind 0 2001 \N 135 Biography,Drama
tt0270053 movie Vizontele Vizontele 0 2001 \N 110 Comedy,Drama
tt0276919 movie Dogville Dogville 0 2003 \N 178 Crime,Drama
tt0278736 movie Stanley Kubrick: A Life in Pictures Stanley Kubrick: A Life in Pictures 0 2001 \N 142 Biography,Documentary
tt0282864 movie Promises Promises 0 2001 \N 106 Documentary
tt0283509 movie No Man's Land No Man's Land 0 2001 \N 98 Drama,War
tt0287467 movie Talk to Her Hable con ella 0 2002 \N 112 Drama,Mystery,Romance
tt0296574 movie Company Company 0 2002 \N 155 Action,Crime,Drama
tt0307385 movie Rivers and Tides: Andy Goldsworthy Working with Time Rivers and Tides 0 2001 \N 90 Documentary
tt0309061 movie War Photographer War Photographer 0 2001 \N 96 Documentary,War
tt0310793 movie Bowling for Columbine Bowling for Columbine 0 2002 \N 120 Crime,Documentary,Drama
tt0312859 movie A Peck on the Cheek Kannathil Muthamittal 0 2002 \N 123 Drama,War
tt0314067 movie Philanthropy Filantropica 0 2002 \N 110 Comedy,Drama
tt0317705 movie The Incredibles The Incredibles 0 2004 \N 115 Action,Adventure,Animation
tt0317910 movie The Fog of War: Eleven Lessons from the Life of Robert S. McNamara The Fog of War: Eleven Lessons from the Life of Robert S. McNamara 0 2003 \N 107 Biography,Documentary,History
tt0319061 movie Big Fish Big Fish 0 2003 \N 125 Adventure,Drama,Fantasy
tt0319736 movie The Legend of Bhagat Singh The Legend of Bhagat Singh 0 2002 \N 155 Biography,Drama,History
tt0325980 movie Pirates of the Caribbean: The Curse of the Black Pearl Pirates of the Caribbean: The Curse of the Black Pearl 0 2003 \N 143 Action,Adventure,Fantasy
tt0327056 movie Mystic River Mystic River 0 2003 \N 138 Crime,Drama,Mystery
tt0329393 movie Mr. and Mrs. Iyer Mr. and Mrs. Iyer 0 2002 \N 120 Drama
tt0338013 movie Eternal Sunshine of the Spotless Mind Eternal Sunshine of the Spotless Mind 0 2004 \N 108 Drama,Romance,Sci-Fi
tt0342804 movie My Flesh and Blood My Flesh and Blood 0 2003 \N 83 Documentary
tt0343121 movie Tupac: Resurrection Tupac: Resurrection 0 2003 \N 112 Biography,Documentary,Music
tt0347048 movie Head-On Gegen die Wand 0 2004 \N 121 Drama,Romance
tt0347149 movie Howl's Moving Castle Hauru no ugoku shiro 0 2004 \N 119 Adventure,Animation,Family
tt0347779 movie Pinjar Pinjar: Beyond Boundaries... 0 2003 \N 188 Drama
tt0352248 movie Cinderella Man Cinderella Man 0 2005 \N 144 Biography,Drama,Sport
tt0358456 movie Earthlings Earthlings 0 2005 \N 95 Documentary,Horror
tt0361748 movie Inglourious Basterds Inglourious Basterds 0 2009 \N 153 Adventure,Drama,War
tt0363163 movie Downfall Der Untergang 0 2004 \N 156 Biography,Drama,History
tt0363510 movie The Revolution Will Not Be Televised Chavez: Inside the Coup 0 2003 \N 74 Documentary
tt0364569 movie Oldboy Oldeuboi 0 2003 \N 120 Action,Drama,Mystery
tt0364647 movie Virumandi Virumandi 0 2004 \N 175 Action,Drama,Romance
tt0366840 movie The One Okkadu 0 2003 \N 170 Action,Romance
tt0367110 movie Swades Swades: We, the People 0 2004 \N 210 Drama
tt0367495 movie Anbe Sivam Anbe Sivam 0 2003 \N 160 Adventure,Comedy,Drama
tt0368711 movie End of the Century End of the Century 0 2003 \N 110 Biography,Documentary,Music
tt0369702 movie The Sea Inside Mar adentro 0 2004 \N 125 Biography,Drama,Romance
tt0371392 movie Watermark Watermark 0 2003 \N 76 Crime,Drama,Mystery
tt0372784 movie Batman Begins Batman Begins 0 2005 \N 140 Action,Adventure,Thriller
tt0375611 movie Black Black 0 2005 \N 122 Drama
tt0376127 movie The Stranger Anniyan 0 2005 \N 181 Action,Drama,Thriller
tt0378194 movie Kill Bill: Vol. 2 Kill Bill: Vol. 2 0 2004 \N 137 Action,Crime,Thriller
tt0378647 movie Ramana Ramana 0 2002 \N 140 Action,Drama
tt0379225 movie The Corporation The Corporation 0 2003 \N 145 Documentary,History
tt0379357 movie Los Angeles Plays Itself Los Angeles Plays Itself 0 2003 \N 169 Documentary,History
tt0379370 movie Maqbool Maqbool 0 2003 \N 132 Crime,Drama
tt0379557 movie Touching the Void Touching the Void 0 2003 \N 106 Adventure,Documentary,Drama
tt0379730 movie Charlie: The Life and Art of Charles Chaplin Charlie: The Life and Art of Charles Chaplin 0 2003 \N 132 Biography,Documentary
tt0381061 movie Casino Royale Casino Royale 0 2006 \N 144 Action,Adventure,Thriller
tt0381681 movie Before Sunset Before Sunset 0 2004 \N 80 Drama,Romance
tt0382932 movie Ratatouille Ratatouille 0 2007 \N 111 Adventure,Animation,Comedy
tt0383846 movie When I Grow Up, I'll Be a Kangaroo Kad porastem bicu Kengur 0 2004 \N 92 Comedy
tt0384116 movie G.O.R.A. G.O.R.A. 0 2004 \N 127 Adventure,Comedy,Sci-Fi
tt0385928 movie Panchatanthiram Panchatanthiram 0 2002 \N 150 Comedy
tt0386032 movie Sicko Sicko 0 2007 \N 123 Documentary,Drama
tt0386064 movie Tae Guk Gi: The Brotherhood of War Taegukgi hwinalrimyeo 0 2004 \N 140 Action,Drama,War
tt0393597 movie Earth Earth 0 2007 \N 90 Documentary
tt0393775 movie Simon Simon 0 2004 \N 102 Comedy,Drama
tt0395169 movie Hotel Rwanda Hotel Rwanda 0 2004 \N 121 Biography,Drama,History
tt0396962 movie Shwaas Shwaas 0 2004 \N 107 Drama
tt0399040 movie My Girl Fan chan 0 2003 \N 110 Comedy,Romance
tt0400234 movie Black Friday Black Friday 0 2004 \N 143 Action,Crime,Drama
tt0401085 movie C.R.A.Z.Y. C.R.A.Z.Y. 0 2005 \N 127 Comedy,Drama
tt0401383 movie The Diving Bell and the Butterfly Le scaphandre et le papillon 0 2007 \N 112 Biography,Drama
tt0401792 movie Sin City Sin City 0 2005 \N 124 Crime,Thriller
tt0405094 movie The Lives of Others Das Leben der Anderen 0 2006 \N 137 Drama,Thriller
tt0405159 movie Million Dollar Baby Million Dollar Baby 0 2004 \N 132 Drama,Sport
tt0405508 movie Rang De Basanti Rang De Basanti 0 2006 \N 157 Comedy,Drama
tt0407887 movie The Departed The Departed 0 2006 \N 151 Crime,Drama,Thriller
tt0408664 movie Nobody Knows Dare mo shiranai 0 2004 \N 141 Drama
tt0410407 movie Orwell Rolls in His Grave Orwell Rolls in His Grave 0 2003 \N 84 Documentary
tt0411469 movie Hazaaron Khwaishein Aisi Hazaaron Khwaishein Aisi 0 2003 \N 120 Drama
tt0412631 movie Death in Gaza Death in Gaza 0 2004 \N 80 Documentary
tt0413615 movie Unforgivable Blackness: The Rise and Fall of Jack Johnson Unforgivable Blackness: The Rise and Fall of Jack Johnson 0 2004 \N 214 Biography,Documentary,Sport
tt0416960 movie The Lizard Marmoulak 0 2004 \N 115 Comedy,Drama
tt0419781 movie Graves End Graves End 0 2005 \N 90 Mystery,Thriller
tt0423866 movie 3-Iron Bin-jip 0 2004 \N 88 Crime,Drama,Romance
tt0424227 movie Turtles Can Fly Lakposhtha parvaz mikonand 0 2004 \N 98 Drama,War
tt0425162 movie Kaazhcha Kaazhcha 0 2004 \N 137 Drama
tt0425333 movie Pardon Pardon 0 2005 \N 94 Comedy,Drama
tt0428870 movie A Moment to Remember Nae meorisokui jiwoogae 0 2004 \N 117 Drama,Romance
tt0434409 movie V for Vendetta V for Vendetta 0 2005 \N 132 Action,Drama,Sci-Fi
tt0435761 movie Toy Story 3 Toy Story 3 0 2010 \N 103 Adventure,Animation,Comedy
tt0436231 movie The Devil and Daniel Johnston The Devil and Daniel Johnston 0 2005 \N 110 Biography,Documentary,Music
tt0436971 movie Why We Fight Why We Fight 0 2005 \N 98 Documentary,History,War
tt0440963 movie The Bourne Ultimatum The Bourne Ultimatum 0 2007 \N 115 Action,Mystery,Thriller
tt0442268 movie Be with You Ima, ai ni yukimasu 0 2004 \N 119 Drama,Fantasy,Romance
tt0450259 movie Blood Diamond Blood Diamond 0 2006 \N 143 Adventure,Drama,Thriller
tt0453729 movie Iqbal Iqbal 0 2005 \N 132 Drama,Sport
tt0454921 movie The Pursuit of Happyness The Pursuit of Happyness 0 2006 \N 117 Biography,Drama
tt0455829 movie Vettaiyaadu Vilaiyaadu Vettaiyaadu Vilaiyaadu 0 2006 \N 174 Action,Crime,Thriller
tt0456144 movie Keep at It Munna Bhai Lage Raho Munna Bhai 0 2006 \N 144 Comedy,Drama,Fantasy
tt0456149 movie The Death of Mr. Lazarescu Moartea domnului LΓ£zΓ£rescu 0 2005 \N 153 Drama
tt0457430 movie Pan's Labyrinth El laberinto del fauno 0 2006 \N 118 Drama,Fantasy,War
tt0457496 movie Street Fight Street Fight 0 2005 \N 83 Documentary
tt0458050 movie Paruthiveeran Paruthiveeran 0 2007 \N 162 Action,Drama,Romance
tt0459516 movie Pudhu Pettai Pudhu Pettai 0 2006 \N 168 Action,Crime,Drama
tt0462441 movie Amazing Journey: The Story of The Who Amazing Journey: The Story of The Who 0 2007 \N 237 Documentary,Music
tt0466460 movie Khosla Ka Ghosla! Khosla Ka Ghosla! 0 2006 \N 135 Comedy,Crime,Drama
tt0468569 movie The Dark Knight The Dark Knight 0 2008 \N 152 Action,Crime,Drama
tt0469494 movie There Will Be Blood There Will Be Blood 0 2007 \N 158 Drama
tt0471571 movie Athadu Athadu 0 2005 \N 172 Action,Comedy,Musical
tt0473453 movie The Bondage Bondage 0 2006 \N 100 Biography,Drama
tt0473604 movie Anukokunda Oka Roju Anukokunda Oka Roju 0 2005 \N 145 Mystery,Thriller
tt0476735 movie My Father and My Son Babam ve Oglum 0 2005 \N 108 Drama,Family
tt0477348 movie No Country for Old Men No Country for Old Men 0 2007 \N 122 Crime,Drama,Thriller
tt0478209 movie Metal: A Headbanger's Journey Metal: A Headbanger's Journey 0 2005 \N 96 Documentary,Music
tt0478331 movie Workingman's Death Workingman's Death 0 2005 \N 122 Documentary
tt0480732 movie Shyamol Chhaya Shyamol Chhaya 0 2004 \N 110 Drama
tt0482571 movie The Prestige The Prestige 0 2006 \N 130 Drama,Mystery,Sci-Fi
tt0483180 movie Thanmathra Thanmathra 0 2005 \N 160 Drama
tt0488414 movie Omkara Omkara 0 2006 \N 155 Action,Crime,Drama
tt0493393 movie Ashes and Snow Ashes and Snow 0 2005 \N 63 Documentary,Drama
tt0494724 movie 1 Litre of Tears Ichi ritoru no namida 0 2005 \N 98 Drama
tt0758758 movie Into the Wild Into the Wild 0 2007 \N 148 Adventure,Biography,Drama
tt0759952 movie American Zeitgeist American Zeitgeist 0 2006 \N 150 Documentary
tt0770802 movie Samsara Samsara 0 2011 \N 102 Documentary,Music
tt0772153 movie America: Freedom to Fascism America: Freedom to Fascism 0 2006 \N 95 Documentary
tt0790636 movie Dallas Buyers Club Dallas Buyers Club 0 2013 \N 117 Biography,Drama
tt0796366 movie Star Trek Star Trek 0 2009 \N 127 Action,Adventure,Sci-Fi
tt0805188 movie Marilena from P7 Marilena de la P7 0 2006 \N 48 Drama,Fantasy
tt0807956 movie Occupation 101 Occupation 101 0 2006 \N 90 Documentary
tt0808417 movie Persepolis Persepolis 0 2007 \N 96 Animation,Biography,Drama
tt0814075 movie Deliver Us from Evil Deliver Us from Evil 0 2006 \N 101 Crime,Documentary
tt0816692 movie Interstellar Interstellar 0 2014 \N 169 Adventure,Drama,Sci-Fi
tt0824316 movie Dor Dor 0 2006 \N 147 Drama
tt0839967 movie The Rest is Silence Restul e tacere 0 2007 \N 114 Comedy,Drama
tt0841119 movie Lake of Fire Lake of Fire 0 2006 \N 152 Documentary
tt0843326 movie Bommarillu Bommarillu 0 2006 \N 170 Comedy,Drama,Romance
tt0848228 movie The Avengers The Avengers 0 2012 \N 143 Action,Adventure,Sci-Fi
tt0851577 movie The Island Ostrov 0 2006 \N 112 Drama
tt0855822 movie Huddersfield Hadersfild 0 2007 \N 95 Drama
tt0856008 movie Sharkwater Sharkwater 0 2006 \N 89 Documentary
tt0860906 movie Evangelion: 2.0 You Can (Not) Advance Evangerion shin gekijΓ΄ban: Ha 0 2009 \N 112 Action,Animation,Drama
tt0861739 movie Elite Squad Tropa de Elite 0 2007 \N 115 Action,Crime,Drama
tt0867145 movie Classmates Classmates 0 2006 \N 175 Drama,Mystery,Romance
tt0870112 movie Future by Design Future by Design 0 2006 \N 86 Documentary,Sci-Fi
tt0871510 movie Chak de! India Chak De! India 0 2007 \N 153 Drama,Family,Sport
tt0892375 movie U2 3D U2 3D 0 2007 \N 85 Documentary,Music
tt0892425 movie Before Flying Back to Earth Pries parskrendant i zeme 0 2005 \N 52 Documentary
tt0892769 movie How to Train Your Dragon How to Train Your Dragon 0 2010 \N 98 Action,Adventure,Animation
tt0910970 movie WALLΒ·E WALLΒ·E 0 2008 \N 98 Adventure,Animation,Family
tt0911010 movie White Light/Black Rain: The Destruction of Hiroshima and Nagasaki White Light/Black Rain: The Destruction of Hiroshima and Nagasaki 0 2007 \N 86 Documentary,History
tt0912593 movie No End in Sight No End in Sight 0 2007 \N 102 Documentary,War
tt0923752 movie The King of Kong: A Fistful of Quarters The King of Kong 0 2007 \N 79 Biography,Documentary,Sport
tt0925130 movie Suryam Suryam 0 2004 \N 150 Action,Romance
tt0925248 movie In the Shadow of the Moon In the Shadow of the Moon 0 2007 \N 100 Documentary,History
tt0929620 movie Political Rowdy Political Rowdy 0 2005 \N \N Action
tt0947798 movie Black Swan Black Swan 0 2010 \N 108 Drama,Thriller
tt0965382 movie Tom Petty and the Heartbreakers: Runnin' Down a Dream Tom Petty and the Heartbreakers: Runnin' Down a Dream 0 2007 \N 239 Documentary,Music
tt0978762 movie Mary and Max Mary and Max 0 2009 \N 92 Animation,Comedy,Drama
tt0986264 movie Like Stars on Earth Taare Zameen Par 0 2007 \N 165 Drama,Family
tt0986329 movie Mungaru Male Mungaru Male 0 2006 \N 143 Comedy,Music,Romance
tt0988108 movie The Class Klass 0 2007 \N 99 Drama
tt0993846 movie The Wolf of Wall Street The Wolf of Wall Street 0 2013 \N 180 Biography,Comedy,Crime
tt1010048 movie Slumdog Millionaire Slumdog Millionaire 0 2008 \N 120 Drama,Romance
tt1014762 movie Home Home 0 2009 \N 120 Documentary,Drama,Family
tt1015246 movie War Made Easy: How Presidents & Pundits Keep Spinning Us to Death War Made Easy: How Presidents & Pundits Keep Spinning Us to Death 0 2007 \N 73 Documentary,History,War
tt1028532 movie Hachi: A Dog's Tale Hachi: A Dog's Tale 0 2009 \N 93 Drama,Family
tt1029172 movie The War on Democracy The War on Democracy 0 2007 \N 96 Documentary
tt1034053 movie Mozhi Mozhi 0 2007 \N 155 Comedy,Musical,Romance
tt1039647 movie The Union: The Business Behind Getting High The Union: The Business Behind Getting High 0 2007 \N 104 Documentary
tt1049413 movie Up Up 0 2009 \N 96 Adventure,Animation,Comedy
tt1068956 movie In the Name of God Khuda Kay Liye 0 2007 \N 168 Drama
tt1069238 movie Departures Okuribito 0 2008 \N 130 Drama,Music
tt1087578 movie Still Walking Aruitemo aruitemo 0 2008 \N 115 Drama
tt1094594 movie Sigur RΓ³s: Heima Sigur RΓ³s: Heima 0 2007 \N 97 Documentary,Music
tt1097256 movie Please Vote for Me Please Vote for Me 0 2007 \N 58 Documentary
tt1113829 movie George Harrison: Living in the Material World George Harrison: Living in the Material World 0 2011 \N 208 Biography,Documentary,Music
tt1128075 movie Love Exposure Ai no mukidashi 0 2008 \N 237 Action,Comedy,Drama
tt1129435 movie The Beaches of Agnès Les plages d'Agnès 0 2008 \N 110 Biography,Documentary
tt1130884 movie Shutter Island Shutter Island 0 2010 \N 138 Mystery,Thriller
tt1152758 movie Dear Zachary: A Letter to a Son About His Father Dear Zachary: A Letter to a Son About His Father 0 2008 \N 95 Biography,Crime,Documentary
tt1157720 movie Stranded: I've Come from a Plane That Crashed on the Mountains Stranded: I've Come from a Plane That Crashed on the Mountains 0 2007 \N 130 Documentary
tt1171701 movie The Breath Nefes: Vatan Sagolsun 0 2009 \N 128 Action,Drama,Thriller
tt1178197 movie The World is Big and Salvation Lurks Around the Corner Svetat e golyam i spasenie debne otvsyakade 0 2008 \N 105 Drama
tt1180583 movie Vaaranam Aayiram Vaaranam Aayiram 0 2008 \N 169 Drama,Musical,Romance
tt1183919 movie Marley Marley 0 2012 \N 144 Biography,Documentary,Music
tt1185616 movie Waltz with Bashir Vals Im Bashir 0 2008 \N 90 Animation,Biography,Drama
tt1187043 movie 3 Idiots 3 Idiots 0 2009 \N 170 Comedy,Drama
tt1188996 movie My Name Is Khan My Name Is Khan 0 2010 \N 165 Drama
tt1194437 movie Rats and Cats Rats and Cats 0 2007 \N 87 Comedy,Drama
tt1194620 movie Silent Wedding Nunta muta 0 2008 \N 87 Comedy,Drama,Romance
tt1201607 movie Harry Potter and the Deathly Hallows: Part 2 Harry Potter and the Deathly Hallows: Part 2 0 2011 \N 130 Adventure,Drama,Fantasy
tt1205489 movie Gran Torino Gran Torino 0 2008 \N 116 Drama
tt1220719 movie Ip Man Yip Man 0 2008 \N 106 Action,Biography,Drama
tt1249171 movie Global Metal Global Metal 0 2008 \N 93 Documentary,Music
tt1255953 movie Incendies Incendies 0 2010 \N 131 Drama,Mystery,War
tt1260502 movie Ghost in the Shell 2.0 KΓ΄kaku kidΓ΄tai 2.0 0 2008 \N 83 Action,Animation,Crime
tt1261047 movie Gulaal Gulaal 0 2009 \N 140 Crime,Drama,Thriller
tt1277737 movie The Stoning of Soraya M. The Stoning of Soraya M. 0 2008 \N 114 Drama
tt1278060 movie Kara no Kyoukai: The Garden of Sinners - Paradox Spiral GekijΓ΄ ban Kara no kyΓ΄kai: Dai go shΓ΄ - Mujun rasen 0 2008 \N 114 Action,Animation,Crime
tt1280558 movie A Wednesday A Wednesday 0 2008 \N 104 Crime,Drama,Mystery
tt1282139 movie Cars of the Revolution Devrim Arabalari 0 2008 \N 115 Drama
tt1282371 movie Sorry for the Disturbance Aasef ala el-iz'ag 0 2008 \N 116 Comedy,Drama,Mystery
tt1291584 movie Warrior Warrior 0 2011 \N 140 Drama,Sport
tt1294182 movie Living in Emergency Living in Emergency 0 2008 \N 93 Documentary
tt1305806 movie The Secret in Their Eyes El secreto de sus ojos 0 2009 \N 129 Drama,Mystery,Romance
tt1305871 movie The Soviet Story The Soviet Story 0 2008 \N 86 Documentary,History,War
tt1313104 movie The Cove The Cove 0 2009 \N 92 Crime,Documentary
tt1320254 movie Presumed Guilty Presunto culpable 0 2008 \N 87 Documentary
tt1327035 movie Dev D Dev.D 0 2009 \N 144 Comedy,Drama,Romance
tt1327819 movie Boy Interrupted Boy Interrupted 0 2009 \N 92 Biography,Documentary,Drama
tt1333634 movie Burma VJ: Reporting from a Closed Country Burma VJ: Reporter i et lukket land 0 2008 \N 84 Documentary,History,News
tt1345836 movie The Dark Knight Rises The Dark Knight Rises 0 2012 \N 164 Action,Thriller
tt1360860 movie About Elly Darbareye Elly 0 2009 \N 119 Drama,Mystery,Thriller
tt1361558 movie Iron Maiden: Flight 666 Iron Maiden: Flight 666 0 2009 \N 112 Documentary,Music
tt1375666 movie Inception Inception 0 2010 \N 148 Action,Adventure,Sci-Fi
tt1392190 movie Mad Max: Fury Road Mad Max: Fury Road 0 2015 \N 120 Action,Adventure,Sci-Fi
tt1392214 movie Prisoners Prisoners 0 2013 \N 153 Crime,Drama,Mystery
tt1417299 movie Unnaipol Oruvan Unnaipol Oruvan 0 2009 \N 106 Crime,Drama,Thriller
tt1417592 movie Pearl Jam Twenty Pearl Jam Twenty 0 2011 \N 109 Documentary,History,Music
tt1419318 movie Facing Ali Facing Ali 0 2009 \N 100 Biography,Documentary,Sport
tt1424432 movie Senna Senna 0 2010 \N 106 Biography,Documentary,Sport
tt1430836 movie Yaavarum Nalam Yaavarum Nalam 0 2009 \N 138 Horror,Mystery,Sci-Fi
tt1431045 movie Deadpool Deadpool 0 2016 \N 108 Action,Adventure,Comedy
tt1454029 movie The Help The Help 0 2011 \N 146 Drama
tt1487275 movie The White Stripes Under Great White Northern Lights The White Stripes Under Great White Northern Lights 0 2009 \N 93 Documentary,Music
tt1496729 movie Aaranya Kaandam Aaranya Kaandam 0 2010 \N 153 Action,Comedy,Crime
tt1499666 movie Castaway on the Moon Kimssi pyoryugi 0 2009 \N 116 Drama,Romance
tt1501298 movie Kattradhu Thamizh Kattradhu Thamizh 0 2007 \N 147 Drama,Mystery
tt1504320 movie The King's Speech The King's Speech 0 2010 \N 118 Biography,Drama,History
tt1517451 movie A Star Is Born A Star Is Born 0 2018 \N 136 Drama,Music,Romance
tt1524539 movie Harishchandrachi Factory Harishchandrachi Factory 0 2009 \N 96 Biography,Comedy,Drama
tt1545103 movie Rush: Beyond the Lighted Stage Rush: Beyond the Lighted Stage 0 2010 \N 107 Biography,Documentary,Music
tt1555149 movie Elite Squad: The Enemy Within Tropa de Elite 2: O Inimigo Agora Γ© Outro 0 2010 \N 115 Action,Crime,Drama
tt1562872 movie Zindagi Na Milegi Dobara Zindagi Na Milegi Dobara 0 2011 \N 155 Comedy,Drama
tt1572781 movie The Disappearance of Haruhi Suzumiya Suzumiya Haruhi no shΓ΄shitsu 0 2010 \N 163 Animation,Comedy,Drama
tt1583256 movie Gamyam Gamyam 0 2008 \N 120 Drama
tt1583323 movie Steam of Life Miesten vuoro 0 2010 \N 81 Documentary
tt1587707 movie Exit Through the Gift Shop Exit Through the Gift Shop 0 2010 \N 87 Comedy,Crime,Documentary
tt1590129 movie Natarang Natarang 0 2010 \N 127 Drama
tt1601792 movie Aa Naluguru Aa Naluguru 0 2004 \N 141 Drama
tt1603362 movie Prasthanam Prasthanam 0 2010 \N 153 Action,Drama
tt1606829 movie The Other Dream Team The Other Dream Team 0 2012 \N 89 Documentary,Sport
tt1609168 movie Vinnaithaandi Varuvaayaa Vinnaithaandi Varuvaayaa 0 2010 \N 157 Drama,Romance
tt1613040 movie Leader Leader 0 2010 \N 167 Drama
tt1618448 movie Racing Extinction Racing Extinction 0 2015 \N 90 Adventure,Documentary,Drama
tt1620933 movie Paan Singh Tomar Paan Singh Tomar 0 2012 \N 135 Action,Biography,Crime
tt1621830 movie Monpura Monpura 0 2009 \N 136 Crime,Drama,Musical
tt1634013 movie Montevideo: Taste of a Dream Montevideo, Bog te video! 0 2010 \N 140 Adventure,Comedy,Drama
tt1639426 movie Udaan Udaan 0 2010 \N 134 Drama
tt1645089 movie Inside Job Inside Job 0 2010 \N 109 Crime,Documentary
tt1646967 movie The Art of Flight The Art of Flight 0 2011 \N 80 Adventure,Documentary,Sport
tt1649431 movie Vedam Vedam 0 2010 \N 135 Action,Crime,Drama
tt1659337 movie The Perks of Being a Wallflower The Perks of Being a Wallflower 0 2012 \N 103 Drama,Romance
tt1663202 movie The Revenant The Revenant 0 2015 \N 156 Action,Adventure,Biography
tt1670703 movie Footsteps in the Sand Stapki v pyasaka 0 2010 \N 89 Comedy,Drama
tt1675434 movie The Intouchables Intouchables 0 2011 \N 112 Biography,Comedy,Drama
tt1692928 movie The Last Lions The Last Lions 0 2011 \N 88 Documentary,Family
tt1695800 movie Pranchiyettan and the Saint Pranchiyettan and the Saint 0 2010 \N 140 Comedy,Drama
tt1698010 movie TT3D: Closer to the Edge TT3D: Closer to the Edge 0 2011 \N 104 Documentary,Sport
tt1715802 movie How to Die in Oregon How to Die in Oregon 0 2011 \N 107 Documentary,Drama,Family
tt1720035 movie 59 Seconds 59 Seconds 0 2016 \N 95 Action,Drama,Thriller
tt1727824 movie Bohemian Rhapsody Bohemian Rhapsody 0 2018 \N 134 Biography,Drama,Music
tt1754109 movie Les MisΓ©rables in Concert: The 25th Anniversary Les MisΓ©rables in Concert: The 25th Anniversary 0 2010 \N 178 Drama,Music,Musical
tt1773764 movie Ship of Theseus Ship of Theseus 0 2012 \N 140 Drama
tt1778338 movie The Culture High The Culture High 0 2014 \N 120 Documentary,News
tt1781069 movie Zeitgeist: Moving Forward Zeitgeist: Moving Forward 0 2011 \N 161 Documentary
tt1789083 movie The Weight of Chains The Weight of Chains 0 2010 \N 125 Documentary,History,News
tt1789810 movie JosΓ© and Pilar JosΓ© e Pilar 0 2010 \N 117 Biography,Documentary
tt1795369 movie Frankenstein National Theatre Live: Frankenstein 0 2011 \N 130 Drama
tt1798709 movie Her Her 0 2013 \N 126 Drama,Romance,Sci-Fi
tt1801071 movie See You in Montevideo Montevideo, vidimo se! 0 2014 \N 141 Adventure,Biography,Comedy
tt1805263 movie I Am Kalam I Am Kalam 0 2010 \N 88 Comedy,Drama
tt1807022 movie Super Super 0 2010 \N 140 Drama
tt1809387 movie The War You Don't See The War You Don't See 0 2010 \N 97 Documentary,War
tt1813757 movie Who Killed Captain Alex? Who Killed Captain Alex? 0 2010 \N 64 Action,Crime,Mystery
tt1821317 movie Aadukalam Aadukalam 0 2011 \N 160 Action,Drama,Sport
tt1821480 movie Kahaani Kahaani 0 2012 \N 122 Mystery,Thriller
tt1821682 movie Traffic Traffic 0 2011 \N 122 Action,Adventure,Mystery
tt1821700 movie Waar Waar 0 2013 \N 130 Action,Crime,Thriller
tt1825163 movie The Light Bulb Conspiracy PrΓͺt-Γ -jeter 0 2010 \N 75 Documentary
tt1826603 movie Asmaa Asmaa 0 2011 \N 96 Drama
tt1828232 movie Life Cycles Life Cycles 0 2010 \N 47 Documentary,Sport
tt1830802 movie Shala Shala 0 2011 \N 108 Romance
tt1832382 movie A Separation Jodaeiye Nader az Simin 0 2011 \N 123 Crime,Drama,Family
tt1843335 movie Yutham Sei Yutham Sei 0 2011 \N 160 Crime,Drama,Mystery
tt1853563 movie Foo Fighters: Back and Forth Foo Fighters: Back and Forth 0 2011 \N 101 Documentary,Music
tt1853728 movie Django Unchained Django Unchained 0 2012 \N 165 Drama,Western
tt1856101 movie Blade Runner 2049 Blade Runner 2049 0 2017 \N 164 Drama,Mystery,Sci-Fi
tt1857670 movie Deiva Thirumagal Deiva Thirumagal 0 2011 \N 166 Drama,Family,Musical
tt1865505 movie Song of the Sea Song of the Sea 0 2014 \N 93 Adventure,Animation,Drama
tt1874522 movie Here Without Me Inja bedoone man 0 2011 \N 100 Drama,Family
tt1877832 movie X-Men: Days of Future Past X-Men: Days of Future Past 0 2014 \N 132 Action,Adventure,Sci-Fi
tt1891757 movie Bol Bol 0 2011 \N 165 Drama
tt1895587 movie Spotlight Spotlight 0 2015 \N 129 Crime,Drama,History
tt1906386 movie Guerrilla Guerrilla 0 2011 \N \N Drama,History,War
tt1918969 movie Amar Bondhu Rashed Amar Bondhu Rashed 0 2011 \N 96 War
tt1922545 movie Anjathe Anjathe 0 2008 \N 185 Action,Drama,Thriller
tt1935156 movie Jodorowsky's Dune Jodorowsky's Dune 0 2013 \N 90 Documentary
tt1945039 movie Abu, Son of Adam Adaminte Makan Abu 0 2011 \N 101 Drama
tt1954470 movie Gangs of Wasseypur Gangs of Wasseypur 0 2012 \N 321 Action,Comedy,Crime
tt1979320 movie Rush Rush 0 2013 \N 123 Action,Biography,Drama
tt1998204 movie Home from Home: Chronicle of a Vision Die andere Heimat - Chronik einer Sehnsucht 0 2013 \N 231 Drama,History
tt2004304 movie Mission Blue Mission Blue 0 2014 \N 95 Documentary,Drama
tt2015381 movie Guardians of the Galaxy Guardians of the Galaxy 0 2014 \N 121 Action,Adventure,Comedy
tt2022522 movie Runway Runway 0 2010 \N 90 Drama
tt2024544 movie 12 Years a Slave 12 Years a Slave 0 2013 \N 134 Biography,Drama,History
tt2028530 movie Paradise Lost 3: Purgatory Paradise Lost 3: Purgatory 0 2011 \N 121 Crime,Documentary
tt2049586 movie Glen Campbell: I'll Be Me Glen Campbell: I'll Be Me 0 2014 \N 116 Biography,Documentary,Family
tt2070649 movie Silenced Do-ga-ni 0 2011 \N 125 Drama
tt2073600 movie Lad: A Yorkshire Story Lad: A Yorkshire Story 0 2013 \N 96 Drama
tt2075108 movie Baishe Srabon Baishe Srabon 0 2011 \N 120 Drama,Mystery,Thriller
tt2077886 movie The Phantom of the Opera at the Royal Albert Hall The Phantom of the Opera at the Royal Albert Hall 0 2011 \N 137 Drama,Music,Musical
tt2082197 movie Barfi! Barfi! 0 2012 \N 151 Comedy,Drama,Romance
tt2084970 movie The Imitation Game The Imitation Game 0 2014 \N 114 Biography,Drama,Thriller
tt2085783 movie Deool Deool 0 2011 \N 135 Drama
tt2096673 movie Inside Out Inside Out 0 2015 \N 95 Adventure,Animation,Comedy
tt2104994 movie Steve Jobs: The Lost Interview Steve Jobs: The Lost Interview 0 2012 \N 70 Documentary
tt2106476 movie The Hunt Jagten 0 2012 \N 115 Drama
tt2111478 movie Mea Maxima Culpa: Silence in the House of God Mea Maxima Culpa: Silence in the House of God 0 2012 \N 106 Documentary
tt2119532 movie Hacksaw Ridge Hacksaw Ridge 0 2016 \N 139 Biography,Drama,History
tt2120779 movie Jason Becker: Not Dead Yet Jason Becker: Not Dead Yet 0 2012 \N 87 Biography,Documentary,Music
tt2124189 movie The Revelation of the Pyramids La rΓ©vΓ©lation des pyramides 0 2010 \N 106 Documentary
tt2125608 movie Searching for Sugar Man Searching for Sugar Man 0 2012 \N 86 Biography,Documentary,Music
tt2129928 movie Bones Brigade: An Autobiography Bones Brigade: An Autobiography 0 2012 \N 90 Documentary,Sport
tt2140203 movie Wolf Children Ookami kodomo no Ame to Yuki 0 2012 \N 117 Animation,Drama,Family
tt2146960 movie Vazhakku Enn 18/9 Vazhakku Enn 18/9 0 2012 \N 124 Crime,Drama,Thriller
tt2150209 movie My Grandfather's People Dedemin Insanlari 0 2011 \N 126 Comedy,Drama,Family
tt2153891 movie Metal Warrior Metal Warrior 0 2011 \N 100 Musical
tt2170667 movie Wheels Wheels 0 2014 \N 115 Drama
tt2181831 movie Shahid Shahid 0 2012 \N 129 Biography,Crime,Drama
tt2199711 movie Vishwaroopam Vishwaroopam 0 2013 \N 148 Action,Thriller
tt2215151 movie Inequality for All Inequality for All 0 2013 \N 89 Documentary
tt2218988 movie Ustad Hotel Ustad Hotel 0 2012 \N 151 Comedy,Drama,Family
tt2220642 movie Bad 25 Bad 25 0 2012 \N 131 Documentary,Music
tt2243299 movie Final Cut: Ladies and Gentlemen Final Cut: HΓΆlgyeim Γ©s uraim 0 2012 \N 84 Comedy,Drama,Romance
tt2244877 movie Halima's Path Halimin put 0 2012 \N 97 Drama
tt2245544 movie Carry on Jatta Carry on Jatta 0 2012 \N 143 Comedy
tt2265171 movie The Raid 2 Serbuan maut 2: Berandal 0 2014 \N 150 Action,Crime,Thriller
tt2265179 movie Blood Brother Blood Brother 0 2013 \N 92 Documentary
tt2267998 movie Gone Girl Gone Girl 0 2014 \N 149 Crime,Drama,Mystery
tt2278388 movie The Grand Budapest Hotel The Grand Budapest Hotel 0 2014 \N 99 Adventure,Comedy,Drama
tt2283748 movie OMG: Oh My God! OMG: Oh My God! 0 2012 \N 125 Comedy,Drama,Fantasy
tt2296909 movie Pink Floyd: The Story of Wish You Were Here Pink Floyd: The Story of Wish You Were Here 0 2012 \N 85 Documentary,Music
tt2338151 movie PK PK 0 2014 \N 153 Comedy,Drama
tt2351177 movie Bhooter Bhabishyat Bhooter Bhabishyat 0 2012 \N 123 Comedy,Fantasy,Horror
tt2354205 movie McCullin McCullin 0 2012 \N 91 Documentary
tt2356180 movie Bhaag Milkha Bhaag Bhaag Milkha Bhaag 0 2013 \N 186 Biography,Drama,Sport
tt2358592 movie Lucia Lucia 0 2013 \N 135 Drama,Romance,Sci-Fi
tt2358913 movie Berserk: The Golden Age Arc III - The Advent Beruseruku: Ougon jidai-hen III - Kourin 0 2013 \N 110 Action,Adventure,Animation
tt2359814 movie Speciesism: The Movie Speciesism: The Movie 0 2013 \N 94 Comedy,Documentary
tt2370248 movie Short Term 12 Short Term 12 0 2013 \N 96 Drama
tt2370718 movie Artifact Artifact 0 2012 \N 103 Documentary,Music
tt2374144 movie Gintama the Movie: The Final Chapter - Be Forever Yorozuya Gekijouban Gintama Kanketsu-hen: Yorozuyayo eien nare 0 2013 \N 110 Action,Animation,Comedy
tt2375379 movie One Piece Film Z One Piece Film Z 0 2012 \N 108 Action,Adventure,Animation
tt2375559 movie 1 - Nenokkadine 1 - Nenokkadine 0 2014 \N 170 Action,Mystery,Thriller
tt2375605 movie The Act of Killing The Act of Killing 0 2012 \N 117 Crime,Documentary
tt2377938 movie Special 26 Special Chabbis 0 2013 \N 144 Crime,Thriller
tt2380307 movie Coco Coco 0 2017 \N 105 Adventure,Animation,Comedy
tt2391492 movie Balak Palak Balak Palak 0 2012 \N 109 Family
tt2396224 movie It's Such a Beautiful Day It's Such a Beautiful Day 0 2012 \N 62 Animation,Comedy,Drama
tt2412748 movie Saving Mes Aynak Saving Mes Aynak 0 2014 \N 60 Documentary
tt2414166 movie Led Zeppelin: Celebration Day Led Zeppelin: Celebration Day 0 2012 \N 124 Documentary,Music
tt2415372 movie Inner Worlds, Outer Worlds Inner Worlds, Outer Worlds 0 2012 \N 122 Documentary,History
tt2471640 movie The Mountain Dag 0 2012 \N 90 Adventure,Drama,Thriller
tt2473476 movie Be Here Now Be Here Now 0 2015 \N 100 Biography,Documentary,Drama
tt2486682 movie The Square Al midan 0 2013 \N 108 Documentary,Drama,History
tt2488496 movie Star Wars: The Force Awakens Star Wars: Episode VII - The Force Awakens 0 2015 \N 136 Action,Adventure,Fantasy
tt2499076 movie The Crash Reel The Crash Reel 0 2013 \N 108 Biography,Documentary,Sport
tt2516280 movie Dream Team 1935 Sapnu komanda 1935 0 2012 \N 120 Comedy,Drama,History
tt2518788 movie 1 1 0 2013 \N 112 Documentary,History,Sport
tt2542406 movie Vagabond Paradesi 0 2013 \N 126 Drama,History
tt2543472 movie Wonder Wonder 0 2017 \N 113 Drama,Family
tt2545118 movie Blackfish Blackfish 0 2013 \N 83 Documentary,Drama
tt2550858 movie Antikaci Antikaci 0 2015 \N 84 Drama,Thriller
tt2564144 movie Naduvula Konjam Pakkatha Kaanom Naduvula Konjam Pakkatha Kaanom 0 2012 \N 155 Comedy,Drama
tt2564706 movie Television Television 0 2012 \N 106 Drama
tt2576852 movie The Tale of the Princess Kaguya Kaguyahime no monogatari 0 2013 \N 137 Adventure,Animation,Drama
tt2582802 movie Whiplash Whiplash 0 2014 \N 106 Drama,Music
tt2585562 movie Pizza Pizza 0 2012 \N 127 Horror,Thriller
tt2592910 movie CM101MMXI Fundamentals CM101MMXI Fundamentals 0 2013 \N 139 Comedy,Documentary
tt2593392 movie Alive Inside Alive Inside 0 2014 \N 78 Documentary,News
tt2631186 movie Baahubali: The Beginning Bahubali: The Beginning 0 2015 \N 159 Action,Drama,Fantasy
tt2640460 movie Queen of the Mountains Kurmanjan datka 0 2014 \N 135 Action,Biography,Drama
tt2659414 movie Miracle in Cell No. 7 7-beon-bang-ui seon-mul 0 2013 \N 127 Comedy,Drama
tt2700330 movie The Two Escobars The Two Escobars 0 2010 \N 104 Biography,Documentary,Sport
tt2758880 movie Winter Sleep Kis Uykusu 0 2014 \N 196 Drama
tt2827320 movie Fandry Fandry 0 2013 \N 101 Drama,Family
tt2865822 movie All the World in a Design School All the World in a Design School 0 2015 \N 59 Documentary,Drama,News
tt2869878 movie The Poodles: In the Flesh The Poodles: In the Flesh 0 2010 \N \N Documentary
tt2877108 movie Soodhu Kavvum Soodhu Kavvum 0 2013 \N 135 Comedy,Crime,Thriller
tt2882328 movie Ugly Ugly 0 2013 \N 128 Crime,Drama,Mystery
tt2905772 movie Thou Gild'st the Even Sen Aydinlatirsin Geceyi 0 2013 \N 107 Drama,Fantasy,Romance
tt2924472 movie Mumbai Police Mumbai Police 0 2013 \N 145 Mystery,Thriller
tt2926068 movie Manam Manam 0 2014 \N 163 Comedy,Drama,Fantasy
tt2948356 movie Zootopia Zootopia 0 2016 \N 108 Adventure,Animation,Comedy
tt2976176 movie Lakshmi Lakshmi 0 2014 \N 115 Drama
tt2991224 movie Tangerines Mandariinid 0 2013 \N 87 Drama,War
tt3006576 movie Simple Agi Ondh Love Story Simple Agi Ondh Love Story 0 2013 \N 130 Comedy,Romance
tt3011894 movie Wild Tales Relatos salvajes 0 2014 \N 122 Comedy,Drama,Thriller
tt3034728 movie Left Right Left Left Right Left 0 2013 \N 155 Crime,Drama
tt3037582 movie The Guide Povodyr 0 2014 \N 122 Drama,History
tt3039472 movie Return to Return to Nuke 'Em High Aka Vol. 2 Return to Return to Nuke 'Em High Aka Vol. 2 0 2017 \N 85 Action,Adventure,Comedy
tt3124456 movie Memories Memories 0 2013 \N 132 Mystery,Thriller
tt3153634 movie Wish So-won 0 2013 \N 122 Drama
tt3170832 movie Room Room 0 2015 \N 118 Drama,Thriller
tt3185772 movie The Wolf and the Lamb Onaayum Aattukkuttiyum 0 2013 \N 141 Crime,Mystery,Thriller
tt3203290 movie Free to Play Free to Play 0 2014 \N 75 Action,Documentary
tt3268458 movie The Internet's Own Boy: The Story of Aaron Swartz The Internet's Own Boy: The Story of Aaron Swartz 0 2014 \N 105 Biography,Crime,Documentary
tt3270538 movie Requiem for the American Dream Requiem for the American Dream 0 2015 \N 73 Biography,Documentary,News
tt3274484 movie Ekskursante Ekskursante 0 2013 \N 110 Action,Drama
tt3302820 movie Cowspiracy: The Sustainability Secret Cowspiracy: The Sustainability Secret 0 2014 \N 90 Documentary
tt3311384 movie The Class of 92 The Class of 92 0 2013 \N 99 Documentary,Sport
tt3313066 movie Coriolanus National Theatre Live: Coriolanus 0 2014 \N 192 Drama,History,War
tt3315342 movie Logan Logan 0 2017 \N 137 Action,Drama,Sci-Fi
tt3320542 movie Ugramm Ugramm 0 2014 \N 132 Action,Drama,Thriller
tt3322420 movie Queen Queen 0 2013 \N 146 Comedy,Drama
tt3327994 movie Human Human 0 2015 \N 190 Documentary
tt3341582 movie Killa Killa 0 2014 \N 78 Comedy,Drama
tt3365690 movie Jaatishwar Jaatishwar 0 2014 \N 150 Biography,Drama,History
tt3390572 movie Haider Haider 0 2014 \N 160 Action,Crime,Drama
tt3394420 movie As Seen by the Rest Ulidavaru Kandanthe 0 2014 \N 154 Comedy,Crime,Drama
tt3395608 movie O21 O21 0 2014 \N 124 Action,Thriller
tt3417422 movie Drishyam Drishyam 0 2013 \N 160 Crime,Drama,Thriller
tt3445270 movie The Battered Bastards of Baseball The Battered Bastards of Baseball 0 2014 \N 80 Documentary,Sport
tt3449292 movie Manjhi: The Mountain Man Manjhi: The Mountain Man 0 2015 \N 120 Adventure,Biography,Drama
tt3455224 movie Virunga Virunga 0 2014 \N 100 Documentary,War
tt3455822 movie Night Will Fall Night Will Fall 0 2014 \N 75 Documentary,History,War
tt3461252 movie The Blue Elephant The Blue Elephant 0 2014 \N 170 Drama,Horror,Mystery
tt3521134 movie The Look of Silence The Look of Silence 0 2014 \N 103 Biography,Documentary,History
tt3526810 movie Adolf Hitler: The Greatest Story Never Told Adolf Hitler: The Greatest Story Never Told 0 2013 \N 350 Biography,Documentary,History
tt3544112 movie Sing Street Sing Street 0 2016 \N 106 Comedy,Drama,Music
tt3557258 movie Caur Adatu Caur Adatu 0 2014 \N 74 Biography,Documentary,Drama
tt3560686 movie The Fool Durak 0 2014 \N 116 Crime,Drama,Thriller
tt3569782 movie Jigarthanda Jigarthanda 0 2014 \N 171 Action,Crime,Drama
tt3576728 movie Bilal: A New Breed of Hero Bilal: A New Breed of Hero 0 2015 \N 105 Action,Adventure,Animation
tt3590482 movie Bey Yaar Bey Yaar 0 2014 \N 150 Comedy,Drama,Family
tt3592030 movie Mystery: Born to Rock Mystery: Born to Rock 0 2014 \N 85 Musical
tt3606756 movie Incredibles 2 Incredibles 2 0 2018 \N 118 Action,Adventure,Animation
tt3607198 movie Punjab 1984 Punjab 1984 0 2014 \N 159 Drama,Family,History
tt3612616 movie Mommy Mommy 0 2014 \N 139 Drama
tt3614516 movie Ankhon Dekhi Ankhon Dekhi 0 2013 \N 107 Comedy,Drama
tt3646462 movie Let's Sin Itirazim Var 0 2014 \N 110 Action,Crime,Drama
tt3659388 movie The Martian The Martian 0 2015 \N 144 Adventure,Drama,Sci-Fi
tt3668162 movie Bangalore Days Bangalore Days 0 2014 \N 171 Comedy,Drama,Romance
tt3674140 movie The Salt of the Earth The Salt of the Earth 0 2014 \N 110 Biography,Documentary,History
tt3686998 movie The Red Pill The Red Pill 0 2016 \N 108 Documentary
tt3711164 movie Sathuranga Vettai Sathuranga Vettai 0 2014 \N 145 Crime,Thriller
tt3722234 movie Gosnell: The Trial of America's Biggest Serial Killer Gosnell: The Trial of America's Biggest Serial Killer 0 2018 \N 93 Crime,Drama
tt3741834 movie Lion Lion 0 2016 \N 118 Biography,Drama
tt3783958 movie La La Land La La Land 0 2016 \N 128 Comedy,Drama,Music
tt3784160 movie Valley Uprising Valley Uprising 0 2014 \N 103 Documentary
tt3801314 movie The Knife Kaththi 0 2014 \N 166 Action,Drama
tt3810932 movie Drushyam Drushyam 0 2014 \N 150 Drama,Family,Thriller
tt3822388 movie Madras Madras 0 2014 \N 156 Action,Drama
tt3848892 movie Baby Baby 0 2015 \N 159 Action,Thriller
tt3863552 movie Bajrangi Bhaijaan Bajrangi Bhaijaan 0 2015 \N 163 Action,Comedy,Drama
tt3917908 movie An Insignificant Man An Insignificant Man 0 2016 \N 96 Documentary,Thriller
tt3970482 movie Roger Waters: The Wall Roger Waters: The Wall 0 2014 \N 132 Documentary,Music
tt3973410 movie Kaakkaa Muttai Kaakkaa Muttai 0 2014 \N 91 Comedy,Drama
tt3982254 movie Mad_e in Bangladesh Mad_e in Bangladesh 0 2007 \N \N Comedy,Drama
tt4016934 movie The Handmaiden Ah-ga-ssi 0 2016 \N 144 Drama,Romance,Thriller
tt4044364 movie Citizenfour Citizenfour 0 2014 \N 114 Biography,Documentary
tt4058426 movie India's Daughter India's Daughter 0 2015 \N 63 Biography,Crime,Documentary
tt4085696 movie Billy Elliot Billy Elliot the Musical Live 0 2014 \N 169 Comedy,Drama,Music
tt4088588 movie Rainbow Dhanak 0 2015 \N 106 Drama
tt4115752 movie Chotushkone Chotushkone 0 2014 \N 148 Thriller
tt4130418 movie Hip-Hop Evolution Hip-Hop Evolution 0 2016 \N 90 Documentary,Music
tt4131686 movie I Want to Live I Want to Live 0 2015 \N 106 Adventure,Biography,Documentary
tt4145178 movie Listen to Me Marlon Listen to Me Marlon 0 2015 \N 103 Biography,Documentary
tt4154756 movie Avengers: Infinity War Avengers: Infinity War 0 2018 \N 149 Action,Adventure,Fantasy
tt4168188 movie Chaar Sahibzaade Chaar Sahibzaade 0 2014 \N 135 Animation,History
tt4257858 movie Going Clear: Scientology & the Prison of Belief Going Clear: Scientology & the Prison of Belief 0 2015 \N 119 Documentary
tt4295126 movie Maikol Yordan Traveling Lost Maikol Yordan de Viaje Perdido 0 2014 \N 101 Comedy
tt4309356 movie Ivy Sarmasik 0 2015 \N 104 Drama,Fantasy,Thriller
tt4387040 movie Airlift Airlift 0 2016 \N 130 Drama,History
tt4393514 movie Bitter Lake Bitter Lake 0 2015 \N 136 Documentary
tt4429128 movie Papanasam Papanasam 0 2015 \N 179 Drama,Thriller
tt4430212 movie Drishyam Drishyam 0 2015 \N 163 Crime,Drama,Thriller
tt4432480 movie RangiTaranga RangiTaranga 0 2015 \N 149 Adventure,Music,Mystery
tt4449576 movie Tomorrow Demain 0 2015 \N 118 Documentary
tt4476736 movie Hamlet National Theatre Live: Hamlet 0 2015 \N 217 Drama
tt4519488 movie Mudras Calling Mudras Calling 0 2018 \N 95 Adventure,Drama,Romance
tt4523112 movie Selam: Bahara Yolculuk Selam: Bahara Yolculuk 0 2015 \N 126 Biography,Drama,History
tt4532404 movie Marching to Zion Marching to Zion 0 2015 \N 101 Documentary,History
tt4618398 movie Boruto: Naruto the Movie Boruto: Naruto the Movie 0 2015 \N 95 Action,Adventure,Animation
tt4632316 movie Gleason Gleason 0 2016 \N 110 Documentary
tt4635372 movie Masaan Masaan 0 2015 \N 109 Drama
tt4640206 movie Sachin Sachin 0 2017 \N 138 Biography,Documentary,Drama
tt4658770 movie Belaseshe Belaseshe 0 2015 \N 141 Drama,Family
tt4679210 movie Premam Premam 0 2015 \N 156 Comedy,Drama,Romance
tt4741412 movie Uriyadi Uriyadi 0 2016 \N 90 Action,Thriller
tt4806232 movie Indru Netru Naalai Indru Netru Naalai 0 2015 \N 146 Comedy,Romance,Sci-Fi
tt4833824 movie Uppi 2 Uppi 2 0 2015 \N 135 Action,Mystery,Thriller
tt4846952 movie Angrej Angrej 0 2015 \N 130 Comedy,Romance
tt4849438 movie Baahubali 2: The Conclusion Baahubali 2: The Conclusion 0 2017 \N 167 Action,Drama,Fantasy
tt4851630 movie Maheshinte Prathikaaram Maheshinte Prathikaaram 0 2016 \N 120 Comedy,Drama,Thriller
tt4857264 movie The Invisible Guest Contratiempo 0 2016 \N 106 Crime,Mystery,Thriller
tt4881362 movie Thithi Thithi 0 2015 \N 123 Drama
tt4888834 movie Ennu Ninte Moideen Ennu Ninte Moideen 0 2015 \N 166 Biography,Drama,Romance
tt4899880 movie Shah Shah 0 2015 \N 109 Biography,Drama,Sport
tt4908644 movie Winter on Fire: Ukraine's Fight for Freedom Winter on Fire: Ukraine's Fight for Freedom 0 2015 \N 102 Documentary
tt4909506 movie Godhi Banna Sadharana Mykattu Godhi Banna Sadharana Mykattu 0 2016 \N 144 Drama,Family
tt4912910 movie Mission: Impossible - Fallout Mission: Impossible - Fallout 0 2018 \N 147 Action,Adventure,Thriller
tt4928620 movie Pathemari Pathemari 0 2015 \N 118 Drama
tt4934950 movie Talvar Talvar 0 2015 \N 132 Crime,Drama,Mystery
tt4943992 movie Manto Manto 0 2015 \N 127 Biography,Drama
tt4973112 movie Hitting the Apex Hitting the Apex 0 2015 \N 138 Documentary
tt4987556 movie Thani Oruvan Thani Oruvan 0 2015 \N 160 Action,Crime,Thriller
tt4991384 movie Interrogation Visaaranai 0 2015 \N 117 Crime,Drama,Thriller
tt5005684 movie Nil Battey Sannata Nil Battey Sannata 0 2015 \N 100 Drama,Family
tt5016442 movie Gujjubhai the Great Gujjubhai the Great 0 2015 \N 145 Comedy
tt5021536 movie Kendasampige Kendasampige 0 2015 \N 99 Romance,Thriller
tt5027202 movie Colombia magia salvaje Colombia magia salvaje 0 2015 \N 94 Documentary
tt5027774 movie Three Billboards Outside Ebbing, Missouri Three Billboards Outside Ebbing, Missouri 0 2017 \N 115 Comedy,Crime,Drama
tt5039054 movie Oopiri Oopiri 0 2016 \N 158 Comedy,Drama
tt5046534 movie Felix Manalo Felix Manalo 0 2015 \N 178 Biography
tt5066616 movie Crusty Demons 18: Twenty Years of Fear Crusty Demons 18: Twenty Years of Fear 0 2015 \N 67 Documentary
tt5069074 movie A Billion Lives A Billion Lives 0 2016 \N 95 Documentary,History,News
tt5074352 movie Dangal Dangal 0 2016 \N 161 Action,Biography,Drama
tt5078886 movie Fanged Up Fanged Up 0 2017 \N 88 Comedy,Horror
tt5083738 movie The Favourite The Favourite 0 2018 \N 119 Biography,Drama
tt5086104 movie Chhello Divas Chhello Divas 0 2015 \N 138 Comedy
tt5104080 movie Surga Yang Tak Dirindukan Surga Yang Tak Dirindukan 0 2015 \N 125 Drama
tt5104604 movie Isle of Dogs Isle of Dogs 0 2018 \N 101 Adventure,Animation,Comedy
tt5116410 movie Tower Tower 0 2016 \N 82 Animation,Crime,Documentary
tt5137380 movie Kanche Kanche 0 2015 \N 126 Action,Drama,History
tt5161204 movie Diary of a Fatman Diary of a Fatman 0 2016 \N \N Comedy,Fantasy,Sci-Fi
tt5190958 movie Katyar Kaljat Ghusali Katyar Kaljat Ghusali 0 2015 \N 162 Musical
tt5271442 movie Maalik Maalik 0 2016 \N 155 Action,Crime,Drama
tt5275892 movie O.J.: Made in America O.J.: Made in America 0 2016 \N 467 Biography,Crime,Documentary
tt5311514 movie Your Name. Kimi no na wa. 0 2016 \N 106 Animation,Drama,Fantasy
tt5311546 movie Natsamrat Natsamrat 0 2016 \N 166 Drama,Family
tt5312232 movie Wild Sairat 0 2016 \N 174 Drama,Romance
tt5323662 movie The Shape of Voice Koe no katachi 0 2016 \N 130 Animation,Drama,Romance
tt5335314 movie One Step Behind the Seraphim One Step Behind the Seraphim 0 2017 \N 150 Drama
tt5354160 movie Aynabaji Aynabaji 0 2016 \N 147 Crime,Mystery,Thriller
tt5458088 movie Kammatti Paadam Kammatti Paadam 0 2016 \N 177 Action,Drama
tt5460658 movie Life+1Day Abad va yek rooz 0 2016 \N 95 Drama
tt5466576 movie BoBoiBoy: The Movie BoBoiBoy: The Movie 0 2016 \N 100 Action,Animation,Comedy
tt5477194 movie Iraivi Iraivi 0 2016 \N 160 Crime,Drama
tt5494396 movie Tickling Giants Tickling Giants 0 2016 \N 111 Documentary
tt5504168 movie Kshanam Kshanam 0 2016 \N 110 Thriller
tt5510934 movie The Unnamed Oggatonama 0 2016 \N 100 Drama
tt5534436 movie Dhaka Attack Dhaka Attack 0 2017 \N 147 Action,Thriller
tt5559528 movie Maanagaram Maanagaram 0 2017 \N 137 Action,Thriller
tt5571734 movie Pink Pink 0 2016 \N 136 Drama
tt5600714 movie Red Color Laal Rang 0 2016 \N 147 Action,Crime,Drama
tt5611648 movie Joker Joker 0 2016 \N 130 Comedy,Drama
tt5691226 movie Pashupati Prasad Pashupati Prasad 0 2016 \N 140 Drama,Romance
tt5726616 movie Call Me by Your Name Call Me by Your Name 0 2017 \N 132 Drama,Romance
tt5732482 movie Hans Zimmer: Live in Prague Hans Zimmer: Live in Prague 0 2017 \N 150 Documentary,Music
tt5777628 movie One More Time with Feeling One More Time with Feeling 0 2016 \N 113 Documentary,Music
tt5813916 movie The Mountain II Dag II 0 2016 \N 135 Drama,War
tt5824826 movie #Pellichoopulu #Pellichoopulu 0 2016 \N 125 Comedy,Drama,Romance
tt5836866 movie The Work The Work 0 2017 \N 89 Documentary
tt5849148 movie Appa Appa 0 2016 \N 124 Drama
tt5867800 movie Aruvi Aruvi 0 2016 \N 130 Drama
tt5870084 movie Beyond Brotherhood Mas Que Hermanos 0 2017 \N 110 Drama
tt5889462 movie Guppy Guppy 0 2016 \N 158 Comedy,Drama,Family
tt5895028 movie 13th 13th 0 2016 \N 100 Crime,Documentary,History
tt5906392 movie The Mainour and the Witness Thondimuthalum Dhriksakshiyum 0 2017 \N 135 Crime,Drama,Thriller
tt5929776 movie Before the Flood Before the Flood 0 2016 \N 96 Documentary,News
tt5959980 movie Vada Chennai Vada Chennai 0 2018 \N 164 Action,Crime
tt5963218 movie Aloko Udapadi Aloko Udapadi 0 2017 \N 113 Drama,History
tt6019206 movie Kill Bill: The Whole Bloody Affair Kill Bill: The Whole Bloody Affair 0 2011 \N 247 Action,Crime,Thriller
tt6023118 movie Word from a Gamer Word from a Gamer 0 2018 \N 92 Documentary
tt6027478 movie Dhruva Dhruva 0 2016 \N 165 Action,Thriller
tt6054758 movie Kirik Party Kirik Party 0 2016 \N 159 Comedy,Drama
tt6058226 movie Ekvtime: Man of God Ekvtime: Man of God 0 2018 \N 132 Biography,Drama,Romance
tt6076366 movie Aandavan Kattalai Aandavan Kattalai 0 2016 \N 151 Drama
tt6108090 movie Secret Superstar Secret Superstar 0 2017 \N 150 Drama,Music
tt6148156 movie Vikram Vedha Vikram Vedha 0 2017 \N 147 Action,Crime,Thriller
tt6155172 movie Roma Roma 0 2018 \N 135 Drama
tt6156350 movie HyperNormalisation HyperNormalisation 0 2016 \N 166 Documentary
tt6167894 movie Angamaly Diaries Angamaly Diaries 0 2017 \N 132 Action,Comedy,Thriller
tt6212984 movie Parava Parava 0 2017 \N 147 Action,Comedy,Drama
tt6223974 movie The Farthest The Farthest 0 2017 \N 121 Documentary,History
tt6315524 movie Take Off Take Off 0 2017 \N 139 Action,Drama,Thriller
tt6316138 movie Ayla: The Daughter of War Ayla: The Daughter of War 0 2017 \N 125 Drama,History,War
tt6333054 movie Chasing Coral Chasing Coral 0 2017 \N 93 Documentary
tt6346162 movie Disney's Newsies the Broadway Musical Disney's Newsies the Broadway Musical 0 2017 \N 149 Musical
tt6413712 movie Finding Fatimah Finding Fatimah 0 2017 \N 99 Comedy,Romance
tt6452574 movie Sanju Sanju 0 2018 \N 155 Biography,Drama
tt6485666 movie Mersal Mersal 0 2017 \N 172 Action,Thriller
tt6510332 movie McQueen McQueen 0 2018 \N 111 Biography,Documentary
tt6520954 movie Debi Debi 0 2018 \N 107 Drama,Mystery
tt6543652 movie Cold War Zimna wojna 0 2018 \N 88 Drama,Music,Romance
tt6628102 movie The Wild Pear Tree Ahlat Agaci 0 2018 \N 188 Drama
tt6679360 movie On vam ne Dimon On vam ne Dimon 0 2017 \N 50 Documentary
tt6794424 movie LA 92 LA 92 0 2017 \N 114 Documentary,History
tt6980546 movie Bharat Ane Nenu Bharat Ane Nenu 0 2018 \N 173 Action,Drama
tt7019842 movie 96 96 0 2018 \N 158 Romance
tt7060460 movie Braveman - Chapter One Theeran Adhigaaram Ondru 0 2017 \N 157 Action,Crime,Thriller
tt7180544 movie The Brawler Mukkabaaz 0 2017 \N 154 Action,Drama,Sport
tt7218518 movie Padman Padman 0 2018 \N 140 Biography,Drama
tt7252000 movie Maktub Maktub 0 2017 \N 100 Comedy,Drama
tt7294534 movie Arjun Reddy Arjun Reddy 0 2017 \N 182 Action,Drama,Romance
tt7320560 movie Cuba and the Cameraman Cuba and the Cameraman 0 2017 \N 113 Documentary
tt7345930 movie Abrahaminte Santhathikal Abrahaminte Santhathikal 0 2018 \N 131 Action,Crime,Thriller
tt7362036 movie Dying to Survive Wo bu shi yao shen 0 2018 \N 117 Comedy,Drama
tt7391996 movie C/o Kancharapalem C/o Kancharapalem 0 2018 \N 152 Drama
tt7392212 movie Stage Rangasthalam 0 2018 \N 170 Action,Drama
tt7465992 movie The Great Actress Mahanati 0 2018 \N 177 Biography,Drama,History
tt7581572 movie Sudani from Nigeria Sudani from Nigeria 0 2018 \N 123 Comedy,Sport
tt7610196 movie Do Women Have A Higher Sex Drive? Do Women Have A Higher Sex Drive? 0 2018 \N 70 Documentary
tt7642818 movie Aile Arasinda Aile Arasinda 0 2017 \N 124 Comedy
tt7681902 movie Won't You Be My Neighbor? Won't You Be My Neighbor? 0 2018 \N 94 Biography,Documentary
tt7689966 movie Robin Williams: Come Inside My Mind Robin Williams: Come Inside My Mind 0 2018 \N 116 Biography,Comedy,Documentary
tt7691572 movie Kiborgy Cyborgs: Heroes Never Die 0 2017 \N 112 War
tt7725596 movie Badhaai Ho Badhaai Ho 0 2018 \N 124 Comedy,Drama
tt7745068 movie My Hero Academia: The Movie Boku no Hero Academia the Movie 0 2018 \N 96 Action,Animation,Comedy
tt7748244 movie Γ–lΓΌmlΓΌ DΓΌnya Γ–lΓΌmlΓΌ DΓΌnya 0 2018 \N 107 Action,Comedy
tt7762982 movie Sajjan Singh Rangroot Sajjan Singh Rangroot 0 2018 \N 140 Action,Biography,Drama
tt7765910 movie Aravindha Sametha Veera Raghava Aravindha Sametha Veera Raghava 0 2018 \N 162 Action,Drama
tt7797658 movie Awe! Awe! 0 2018 \N 150 Sci-Fi,Thriller
tt7914416 movie One Cut of the Dead Kamera o tomeru na! 0 2017 \N 96 Comedy,Horror
tt8043306 movie Teefa in Trouble Teefa in Trouble 0 2018 \N 155 Action,Comedy,Crime
tt8075192 movie Shoplifters Manbiki kazoku 0 2018 \N 121 Crime,Drama
tt8108198 movie Andhadhun Andhadhun 0 2018 \N 139 Comedy,Mystery,Thriller
tt8108202 movie Stree Stree 0 2018 \N 128 Comedy,Horror
tt8135494 movie Active Measures Active Measures 0 2018 \N 109 Documentary
tt8176054 movie Pariyerum Perumal Pariyerum Perumal 0 2018 \N 154 Drama
tt8239946 movie Tumbbad Tumbbad 0 2018 \N 104 Drama,Horror,Thriller
tt8288836 movie Red Forrest Red Forrest 0 2018 \N 75 Horror
tt8368032 movie Miss Granny Miss Granny 0 2018 \N 120 Comedy,Fantasy,Music
tt8590896 movie Geetha Govindam Geetha Govindam 0 2018 \N 142 Drama,Romance
tt9042284 movie Ratsasan Ratsasan 0 2018 \N 170 Thriller
tt9063106 movie Paskal: The Movie Paskal: The Movie 0 2018 \N 115 Action,Drama

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,645 @@
tconst averageRating numVotes
tt0120737 8.8 1446962
tt0133093 8.7 1441344
tt0166924 8.0 281217
tt0167260 8.9 1430168
tt0167261 8.7 1292839
tt0168629 8.0 90379
tt0169102 8.1 89307
tt0172495 8.5 1161031
tt0180093 8.3 675819
tt0198781 8.1 707554
tt0208092 8.3 695561
tt0209144 8.5 996060
tt0209180 8.0 3176
tt0222012 8.0 9770
tt0242256 8.3 3868
tt0242519 8.2 45773
tt0246578 8.1 677939
tt0253474 8.5 609776
tt0264464 8.1 679907
tt0264476 8.3 2066
tt0266543 8.1 832715
tt0266697 8.1 869155
tt0268978 8.2 740055
tt0270053 8.1 26739
tt0276919 8.0 121929
tt0278736 8.0 9202
tt0282864 8.5 2485
tt0283509 8.0 41211
tt0287467 8.0 95064
tt0296574 8.0 12540
tt0307385 8.1 2112
tt0309061 8.0 3632
tt0310793 8.0 132657
tt0312859 8.5 5841
tt0314067 8.5 11304
tt0317705 8.0 570177
tt0317910 8.2 21087
tt0319061 8.0 382218
tt0319736 8.1 12337
tt0325980 8.0 936766
tt0327056 8.0 378306
tt0329393 8.0 4428
tt0338013 8.3 792691
tt0342804 8.4 1652
tt0343121 8.0 8473
tt0347048 8.0 45958
tt0347149 8.2 269225
tt0347779 8.1 2270
tt0352248 8.0 163119
tt0358456 8.7 15541
tt0361748 8.3 1070753
tt0363163 8.2 293997
tt0363510 8.4 2253
tt0364569 8.4 438259
tt0364647 8.3 3378
tt0366840 8.1 7027
tt0367110 8.3 71715
tt0367495 8.8 11942
tt0368711 8.0 3246
tt0369702 8.0 71332
tt0371392 8.0 1155
tt0372784 8.3 1149747
tt0375611 8.2 29956
tt0376127 8.2 10839
tt0378194 8.0 589929
tt0378647 8.1 1521
tt0379225 8.1 19370
tt0379357 8.0 1789
tt0379370 8.2 7216
tt0379557 8.0 30499
tt0379730 8.0 1255
tt0381061 8.0 525226
tt0381681 8.1 199511
tt0382932 8.0 551811
tt0383846 8.5 8081
tt0384116 8.0 46929
tt0385928 8.0 2444
tt0386032 8.0 70699
tt0386064 8.1 35034
tt0393597 8.0 13625
tt0393775 8.0 7731
tt0395169 8.1 305237
tt0396962 8.3 1219
tt0399040 8.1 1487
tt0400234 8.6 15271
tt0401085 8.0 29691
tt0401383 8.0 96932
tt0401792 8.0 705633
tt0405094 8.4 309958
tt0405159 8.1 567927
tt0405508 8.2 96347
tt0407887 8.5 1032965
tt0408664 8.1 20278
tt0410407 8.1 1087
tt0411469 8.0 4248
tt0412631 8.2 1140
tt0413615 8.3 1203
tt0416960 8.6 10155
tt0419781 8.8 6419
tt0423866 8.1 43799
tt0424227 8.1 15693
tt0425162 8.2 1711
tt0425333 8.2 15165
tt0428870 8.2 18842
tt0434409 8.2 923238
tt0435761 8.3 652935
tt0436231 8.0 8185
tt0436971 8.1 9450
tt0440963 8.0 569575
tt0442268 8.0 3854
tt0450259 8.0 450745
tt0453729 8.1 13804
tt0454921 8.0 392056
tt0455829 8.0 5390
tt0456144 8.1 36536
tt0456149 8.0 11829
tt0457430 8.2 552555
tt0457496 8.0 1696
tt0458050 8.2 2382
tt0459516 8.4 3006
tt0462441 8.2 1174
tt0466460 8.3 18791
tt0468569 9.0 1978028
tt0469494 8.1 446593
tt0471571 8.3 9762
tt0473453 8.0 1780
tt0473604 8.0 1031
tt0476735 8.4 61410
tt0477348 8.1 735534
tt0478209 8.1 10846
tt0478331 8.1 1538
tt0480732 8.5 1329
tt0482571 8.5 1021299
tt0483180 8.3 2592
tt0488414 8.1 16434
tt0493393 8.3 3540
tt0494724 8.0 1023
tt0758758 8.1 508089
tt0759952 8.4 1899
tt0770802 8.5 28432
tt0772153 8.0 2381
tt0790636 8.0 388197
tt0796366 8.0 548410
tt0805188 8.0 1831
tt0807956 8.4 1645
tt0808417 8.1 79717
tt0814075 8.0 9186
tt0816692 8.6 1221035
tt0824316 8.0 4950
tt0839967 8.2 2505
tt0841119 8.3 2306
tt0843326 8.2 6773
tt0848228 8.1 1129960
tt0851577 8.0 9725
tt0855822 8.3 2157
tt0856008 8.0 4546
tt0860906 8.0 13202
tt0861739 8.1 90399
tt0867145 8.1 2456
tt0870112 8.0 1319
tt0871510 8.2 63620
tt0892375 8.4 2747
tt0892425 8.7 1118
tt0892769 8.1 582348
tt0910970 8.4 868188
tt0911010 8.4 1534
tt0912593 8.3 7800
tt0923752 8.1 32879
tt0925130 8.1 1010
tt0925248 8.1 6024
tt0929620 8.2 1122
tt0947798 8.0 627123
tt0965382 8.6 1559
tt0978762 8.1 145714
tt0986264 8.4 130973
tt0986329 8.3 2268
tt0988108 8.0 13033
tt0993846 8.2 981863
tt1010048 8.0 725044
tt1014762 8.6 20115
tt1015246 8.0 1044
tt1028532 8.1 212046
tt1029172 8.3 2055
tt1034053 8.0 1381
tt1039647 8.3 7417
tt1049413 8.3 807732
tt1068956 8.4 8622
tt1069238 8.1 43515
tt1087578 8.0 10023
tt1094594 8.6 7428
tt1097256 8.0 1208
tt1113829 8.1 8822
tt1128075 8.1 9487
tt1129435 8.0 2234
tt1130884 8.1 960580
tt1152758 8.6 28027
tt1157720 8.1 1149
tt1171701 8.2 28767
tt1178197 8.1 5291
tt1180583 8.0 6648
tt1183919 8.0 12256
tt1185616 8.0 51205
tt1187043 8.4 283929
tt1188996 8.0 87514
tt1194437 8.0 1181
tt1194620 8.0 4796
tt1201607 8.1 662199
tt1205489 8.1 650001
tt1220719 8.0 192042
tt1249171 8.0 4189
tt1255953 8.2 115764
tt1260502 8.0 12220
tt1261047 8.1 11867
tt1277737 8.0 16577
tt1278060 8.0 1116
tt1280558 8.2 65551
tt1282139 8.0 8899
tt1282371 8.0 3156
tt1291584 8.2 391630
tt1294182 8.4 1004
tt1305806 8.2 165787
tt1305871 8.1 2866
tt1313104 8.5 44374
tt1320254 8.2 1265
tt1327035 8.1 25070
tt1327819 8.0 1573
tt1333634 8.0 2229
tt1345836 8.4 1337117
tt1360860 8.1 35735
tt1361558 8.4 4840
tt1375666 8.8 1758078
tt1392190 8.1 740633
tt1392214 8.1 501376
tt1417299 8.1 4471
tt1417592 8.3 7177
tt1419318 8.0 3046
tt1424432 8.6 52591
tt1430836 8.0 1125
tt1431045 8.0 770241
tt1454029 8.1 379215
tt1487275 8.0 2246
tt1496729 8.6 2310
tt1499666 8.1 13573
tt1501298 8.4 1831
tt1504320 8.0 574694
tt1517451 8.3 71921
tt1524539 8.4 1644
tt1545103 8.4 3510
tt1555149 8.1 73498
tt1562872 8.1 55865
tt1572781 8.1 5060
tt1583256 8.3 2147
tt1583323 8.0 2307
tt1587707 8.0 57405
tt1590129 8.4 1250
tt1601792 8.5 1824
tt1603362 8.4 1917
tt1606829 8.4 3275
tt1609168 8.0 4128
tt1613040 8.1 3122
tt1618448 8.2 6592
tt1620933 8.2 28262
tt1621830 8.6 4354
tt1634013 8.3 10044
tt1639426 8.2 37639
tt1645089 8.3 63892
tt1646967 8.3 7178
tt1649431 8.2 5148
tt1659337 8.0 410511
tt1663202 8.0 580687
tt1670703 8.3 1387
tt1675434 8.5 639496
tt1692928 8.3 3735
tt1695800 8.3 3464
tt1698010 8.0 5800
tt1715802 8.3 1746
tt1720035 8.1 1400
tt1727824 8.4 6365
tt1754109 8.8 4358
tt1773764 8.1 5340
tt1778338 8.3 3430
tt1781069 8.2 16655
tt1789083 8.2 3821
tt1789810 8.4 2140
tt1795369 9.0 1657
tt1798709 8.0 443348
tt1801071 8.3 5711
tt1805263 8.0 2958
tt1807022 8.0 1725
tt1809387 8.5 1064
tt1813757 8.2 4248
tt1821317 8.0 3623
tt1821480 8.2 49645
tt1821682 8.1 3243
tt1821700 8.2 28758
tt1825163 8.1 1872
tt1826603 8.3 3662
tt1828232 8.4 1126
tt1830802 8.2 1527
tt1832382 8.3 187133
tt1843335 8.0 1358
tt1853563 8.3 5200
tt1853728 8.4 1158275
tt1856101 8.0 339136
tt1857670 8.2 4433
tt1865505 8.1 40994
tt1874522 8.0 3500
tt1877832 8.0 598031
tt1891757 8.3 8049
tt1895587 8.1 340552
tt1906386 8.2 2216
tt1918969 8.2 2229
tt1922545 8.1 1516
tt1935156 8.1 18054
tt1945039 8.0 1016
tt1954470 8.2 65267
tt1979320 8.1 379194
tt1998204 8.0 1120
tt2004304 8.0 1123
tt2015381 8.1 881685
tt2022522 8.2 1106
tt2024544 8.1 552879
tt2028530 8.1 7910
tt2049586 8.0 1322
tt2070649 8.0 7486
tt2073600 8.7 1319
tt2075108 8.2 3240
tt2077886 8.8 5555
tt2082197 8.1 66221
tt2084970 8.0 590884
tt2085783 8.2 1196
tt2096673 8.2 503190
tt2104994 8.1 1578
tt2106476 8.3 228295
tt2111478 8.1 3103
tt2119532 8.1 338679
tt2120779 8.1 1088
tt2124189 8.0 1268
tt2125608 8.2 56835
tt2129928 8.1 2156
tt2140203 8.2 28862
tt2146960 8.3 1732
tt2150209 8.0 6889
tt2153891 8.5 1167
tt2170667 9.4 14469
tt2181831 8.3 11795
tt2199711 8.4 37441
tt2215151 8.1 6102
tt2218988 8.3 8318
tt2220642 8.0 1705
tt2243299 8.1 1935
tt2244877 8.2 1645
tt2245544 8.3 2583
tt2265171 8.0 100609
tt2265179 8.3 1522
tt2267998 8.1 724838
tt2278388 8.1 605503
tt2283748 8.2 41191
tt2296909 8.0 1317
tt2338151 8.2 128958
tt2351177 8.1 2688
tt2354205 8.2 2054
tt2356180 8.2 52240
tt2358592 8.4 10032
tt2358913 8.0 6088
tt2359814 8.3 1140
tt2370248 8.0 69679
tt2370718 8.1 4344
tt2374144 8.2 1290
tt2375379 8.0 4701
tt2375559 8.3 40285
tt2375605 8.2 29853
tt2377938 8.0 42958
tt2380307 8.4 227849
tt2391492 8.2 1345
tt2396224 8.3 8608
tt2412748 9.1 1038
tt2414166 8.8 1898
tt2415372 8.7 1352
tt2471640 8.1 16342
tt2473476 8.7 2449
tt2486682 8.1 7795
tt2488496 8.0 757296
tt2499076 8.2 4437
tt2516280 8.0 1414
tt2518788 8.0 3247
tt2542406 8.0 1500
tt2543472 8.0 92202
tt2545118 8.1 54712
tt2550858 8.2 1011
tt2564144 8.2 3664
tt2564706 8.2 3821
tt2576852 8.1 27238
tt2582802 8.5 575703
tt2585562 8.1 4908
tt2592910 9.3 40150
tt2593392 8.2 2701
tt2631186 8.2 89063
tt2640460 8.4 3218
tt2659414 8.2 10185
tt2700330 8.2 1392
tt2758880 8.2 34102
tt2827320 8.2 1537
tt2865822 8.4 1273
tt2869878 9.2 1061
tt2877108 8.4 6623
tt2882328 8.1 15522
tt2905772 8.0 7223
tt2924472 8.0 3947
tt2926068 8.1 8290
tt2948356 8.0 358178
tt2976176 8.1 1142
tt2991224 8.2 30456
tt3006576 8.0 1596
tt3011894 8.1 141242
tt3034728 8.1 1826
tt3037582 8.0 2326
tt3039472 8.1 1228
tt3124456 8.0 4165
tt3153634 8.3 3613
tt3170832 8.2 290539
tt3185772 8.2 1692
tt3203290 8.0 12613
tt3268458 8.1 13972
tt3270538 8.1 7398
tt3274484 8.3 1187
tt3302820 8.3 16425
tt3311384 8.1 6985
tt3313066 8.7 1277
tt3315342 8.1 515696
tt3320542 8.2 3947
tt3322420 8.3 52767
tt3327994 8.7 5720
tt3341582 8.0 1067
tt3365690 8.0 1889
tt3390572 8.2 44347
tt3394420 8.5 5404
tt3395608 8.1 2929
tt3417422 8.8 21563
tt3445270 8.0 2289
tt3449292 8.0 15414
tt3455224 8.3 8564
tt3455822 8.0 2674
tt3461252 8.2 21232
tt3521134 8.3 9578
tt3526810 8.0 4592
tt3544112 8.0 70049
tt3557258 9.2 1103
tt3560686 8.0 7402
tt3569782 8.4 7739
tt3576728 8.3 16546
tt3590482 8.5 1676
tt3592030 8.7 1209
tt3606756 8.0 114281
tt3607198 8.5 1599
tt3612616 8.1 40684
tt3614516 8.0 7746
tt3646462 8.0 8366
tt3659388 8.0 640561
tt3668162 8.3 11666
tt3674140 8.4 16430
tt3686998 8.4 7668
tt3711164 8.1 1895
tt3722234 8.2 1240
tt3741834 8.1 167664
tt3783958 8.1 399248
tt3784160 8.2 2843
tt3801314 8.0 14576
tt3810932 8.3 2794
tt3822388 8.0 2607
tt3848892 8.1 46282
tt3863552 8.0 60096
tt3917908 8.8 1471
tt3970482 8.6 3831
tt3973410 8.5 3988
tt3982254 8.0 1201
tt4016934 8.1 68296
tt4044364 8.1 46508
tt4058426 8.2 1760
tt4085696 8.5 1009
tt4088588 8.0 2737
tt4115752 8.2 2055
tt4130418 8.4 2163
tt4131686 9.8 1076
tt4145178 8.2 6041
tt4154756 8.6 511320
tt4168188 8.3 7406
tt4257858 8.0 33103
tt4295126 8.1 1642
tt4309356 8.0 5436
tt4387040 8.1 45929
tt4393514 8.2 2199
tt4429128 8.5 4366
tt4430212 8.3 50048
tt4432480 8.6 10696
tt4449576 8.1 2629
tt4476736 8.6 1475
tt4519488 9.0 1027
tt4523112 8.2 4054
tt4532404 8.3 1614
tt4618398 8.1 6569
tt4632316 8.4 2020
tt4635372 8.1 16771
tt4640206 8.7 6861
tt4658770 8.5 1533
tt4679210 8.3 12305
tt4741412 8.2 1385
tt4806232 8.1 2792
tt4833824 8.3 7572
tt4846952 8.6 2339
tt4849438 8.4 60670
tt4851630 8.3 3893
tt4857264 8.1 67612
tt4881362 8.4 1929
tt4888834 8.1 3914
tt4899880 8.6 1294
tt4908644 8.4 10044
tt4909506 8.9 1897
tt4912910 8.0 145733
tt4928620 8.4 2566
tt4934950 8.2 23461
tt4943992 8.6 1018
tt4973112 8.4 2316
tt4987556 8.5 12396
tt4991384 8.6 4167
tt5005684 8.5 4036
tt5016442 8.2 1108
tt5021536 8.4 1495
tt5027202 8.3 1059
tt5027774 8.2 287654
tt5039054 8.0 3123
tt5046534 8.7 3874
tt5066616 8.2 1716
tt5069074 9.0 2690
tt5074352 8.5 107111
tt5078886 8.4 1254
tt5083738 8.1 1266
tt5086104 8.3 2001
tt5104080 8.9 1965
tt5104604 8.0 76745
tt5116410 8.0 5838
tt5137380 8.1 2190
tt5161204 8.7 1095
tt5190958 8.8 1478
tt5271442 8.5 3397
tt5275892 9.0 14087
tt5311514 8.4 111276
tt5311546 9.2 4012
tt5312232 8.4 8778
tt5323662 8.2 19070
tt5335314 8.4 1313
tt5354160 9.4 17873
tt5458088 8.1 2656
tt5460658 8.3 5111
tt5466576 8.0 2318
tt5477194 8.1 2191
tt5494396 8.3 1233
tt5504168 8.3 3115
tt5510934 9.1 2672
tt5534436 8.3 2690
tt5559528 8.2 2068
tt5571734 8.2 29644
tt5600714 8.2 3476
tt5611648 8.6 2246
tt5691226 8.9 1033
tt5726616 8.0 129241
tt5732482 9.1 1374
tt5777628 8.3 3428
tt5813916 9.4 98395
tt5824826 8.4 3378
tt5836866 8.0 1316
tt5849148 8.1 1030
tt5867800 8.9 6969
tt5870084 8.9 1176
tt5889462 8.1 1324
tt5895028 8.2 17889
tt5906392 8.1 2177
tt5929776 8.3 21690
tt5959980 9.2 2623
tt5963218 9.6 6446
tt6019206 8.8 2592
tt6023118 8.8 1225
tt6027478 8.1 4344
tt6054758 8.5 3112
tt6058226 9.7 2561
tt6076366 8.2 1505
tt6108090 8.0 14052
tt6148156 8.8 15266
tt6155172 8.3 1203
tt6156350 8.3 3604
tt6167894 8.1 3038
tt6212984 8.3 2078
tt6223974 8.1 2308
tt6315524 8.3 2911
tt6316138 8.9 22769
tt6333054 8.1 2732
tt6346162 8.6 1287
tt6413712 8.0 1156
tt6452574 8.2 32544
tt6485666 8.1 20505
tt6510332 8.2 2277
tt6520954 8.9 1265
tt6543652 8.0 4206
tt6628102 8.5 5150
tt6679360 9.2 2632
tt6794424 8.2 1948
tt6980546 8.2 11779
tt7019842 9.3 4330
tt7060460 8.4 4990
tt7180544 8.1 4084
tt7218518 8.1 11486
tt7252000 8.0 1223
tt7294534 8.5 11984
tt7320560 8.2 1479
tt7345930 8.0 3539
tt7362036 8.2 1306
tt7391996 9.4 1134
tt7392212 8.7 13604
tt7465992 8.8 5341
tt7581572 8.3 1283
tt7610196 8.7 1396
tt7642818 8.0 10569
tt7681902 8.5 9506
tt7689966 8.0 3438
tt7691572 8.5 5718
tt7725596 8.3 3547
tt7745068 8.3 1207
tt7748244 8.0 7814
tt7762982 8.0 1091
tt7765910 8.8 4415
tt7797658 8.3 1240
tt7914416 8.2 1517
tt8043306 8.2 1796
tt8075192 8.1 3207
tt8108198 9.1 11042
tt8108202 8.3 8750
tt8135494 8.6 6284
tt8176054 9.5 1779
tt8239946 8.8 2515
tt8288836 8.0 1266
tt8368032 8.8 1450
tt8590896 8.2 3457
tt9042284 9.3 1830
tt9063106 9.2 1279

Π Π°Π·Π½ΠΈΡ†Π° ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ Π½Π΅ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° ΠΈΠ·-Π·Π° своСго большого Ρ€Π°Π·ΠΌΠ΅Ρ€Π° Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ Ρ€Π°Π·Π½ΠΈΡ†Ρƒ

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,89 @@
# Azure Active Directory
> Note: This document roughly follows [this tutorial](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v1-add-azure-ad-app).
To enable authentication, one may wish to use [Azure Active Directory (AAD)](https://azure.microsoft.com/en-us/services/active-directory/).
This document will describe how to configure and leverage AAD for this application.
## Setup AAD App
> TL;DR: After these steps you should have two values noted - an `Application ID` and an `App ID URI`.
1. Login to [Azure Portal](https://portal.azure.com)
2. Select "Azure Active Directory" from the portal left-hand sidebar
3. Select "App Registrations" from the left-most panel
4. Select "New Application" from the top panel
5. Name your application, and enter a valid url for sign-on (hint: we use the url of our hosted service)
6. When the application has been created, the newly visible panel will display its "Application ID" - please note its value
7. Select "Settings" from the top panel
8. Select "Properties" from the right-most panel
9. Find "App ID URI" in the panel, note its value
## Configure SpringDAL
> Note: Authentication is only enabled in the `production` profile.
> TL;DR: After these steps you should have two application environment variables set, `security.oauth2.resource.jwk.key-set-uri` and `security.oauth2.resource.id`.
Ensure that the `security.oauth2.resource.jwk.key-set-uri` environment variable is set to `https://login.microsoftonline.com/common/discovery/keys` - this is because a common key set is used for all Azure Active Directory applications. This will validate that your service only allows valid AAD tokens, but it won't yet verify that the token is for your application.
To ensure the token was granted for your application, you must set `security.oauth2.resource.id` to your `App ID URI` as noted [above](#setup-aad-app).
## Test Authentication
> Note: Authentication is only enabled in the `production` profile.
To ensure authentication is working properly, we need to issue ourselves a token and validate it works. To do so, we'll use [Postman](https://www.getpostman.com/). Please download and install it now.
### Configure AAD Test information
> TL;DR: After these steps you should have one value noted - a `Value`.
1. Login to [Azure Portal](https://portal.azure.com)
2. Select "Azure Active Directory" from the portal left-hand sidebar
3. Select "App Registrations" from the left-most panel
4. Enter your `Application ID` as noted above, into the search field
5. Click your application, to enter its panel
6. Select "Settings" from the top panel
7. Select "Reply URLs" from the right-most panel
8. Ensure `https://www.getpostman.com/oauth2/callback` is the only reply URL entry
9. Select "Save" from the top panel
10. Close the "Reply URLs" panel by selecting the "x" in the top right
11. Select "Settings" from the top panel
12. Select "Keys" from the right-most panel
13. Under "Passwords" type a new "Key Description" in the field, and choose an expiration time
14. Select "Save" from the top panel - please note the value that appears in the `Value` field
### Configure Postman Test information
> TL;DR: After these steps you should have one value noted - an `access_token`.
1. Open Postman
2. Navigate to the Body panel
3. Select "x-www-form-urlencoded"
4. Populate the table that appears with the following values
+ `grant_type`: `client_credentials`
+ `client_id`: `<yourApplicationId>` where `<yourApplicationId>` is `Application ID` from above
+ `client_secret`: `<yourKey>` where `<yourKey>` is the Key `Value` from above
+ `resource`: `<yourApplicationIdUrl>` where `<yourApplicationIdUrl>` is `App ID URI` from above
5. Change the method in the address bar to "POST"
6. Enter `https://login.microsoftonline.com/microsoft.onmicrosoft.com/oauth2/token` for the url
7. Select "Send"
8. The "Body" section of the bottom pane should now be populated
9. Select `access_token` from the "Body" section - please note its value
### Run test
> TL;DR: After these steps you should know if authentication is working properly!
1. Start the `SpringDAL` application with the `production` profile (see the Readme for more information)
2. Open Postman
3. Change the method in the address bar to "GET"
4. Enter `http://localhost:8080/` for the url
5. Navigate to the Headers panel
6. Add one header in the table - `Authorization`: `Bearer <yourAccessToken>` where `<yourAccessToken>` is `access_token` from [above](#configure-postman-test-information)
7. Issue the Postman request by clicking "Send"
8. Validate that Postman shows a successful response
9. Navigate to "Headers" in Postman
10. Toggle off the "Authorization" header by clicking the checkbox next to it
11. Issue the Postman request by clicking "Send"
12. Validate that Postman shows a failure response

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,43 @@
# Build and Release Pipelines
For Project Jackson, the team utilized [Azure Dev Ops](https://azure.microsoft.com/en-us/services/devops/) for source control, work tracking, build and release pipelines. The build pipelines were set up for the API and for the small client application. The build artifacts were used as the kickoff point for the release pipelines for each of the deployment pipelines.
## API Build
The API utilizes Spring Boot to create a standalone Java application. The API is described in depth in [the swagger doc](../swagger.yml). The API uses [Maven](https://maven.apache.org/) with [Spring Boot](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/) to test, compile, run and package the self-contained executable jar that runs in production. To create the executable jar, a dependency in `pom.xml` is added:
```
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
```
Once this is added, the command `mvn package` can be run, which will produce a `example-name.jar` which can be run with the command `java -jar example-name.jar`.
To build the Project Jackson API repo in Azure Dev Ops, a build pipeline was created and described with the [`azure-pipelines.yml`](../azure-pipelines.yml) file. It is broken up into sections based on build step. It starts by running `mvn test` which runs all the unit and integration tests and if that step is successful, it runs `mvn package` and creates that executable jar described previously.
The next steps involve building the docker image defined in the [`Dockerfile`](../Dockerfile), tagging it with the Azure Container Registry (ACR) name and build number, and pushing the tagged Docker image to ACR. The variables for ACR such as `$ACR_SERVER`, `$ACR_CONTAINER_TAG`, `$ACR_PASSWORD`, and `$ACR_USERNAME` are defined in Azure Dev Ops under the build for this repo. The team created a variable group in Azure Dev Ops, under Pipelines, then Library, called `ACR Credentials` that contains the `ACR_SERVER`, `ACR_USERNAME`, and `ACR_PASSWORD` variables. Then in the build pipeline for the API repository, there is a `Variables` tab that has `Variable Groups` as an option and that is where the `ACR Credentials` variable group is linked to the build pipeline for the repository. The `$ACR_CONTAINER_TAG` is set in the `Variables` tab as well, but under `Pipeline Variables` and the team uses a value of `pj-api/pj-api-combined:$(Build.BuildNumber)`.
## Client Build
The UI is a React and TypeScript web app that allows users to easily test the API endpoints and see the data that is returned from the database in a cleaner way. The build pipeline for it was also setup with an `azure-pipelines.yml` file, broken into npm and docker steps. The npm steps include linting, testing and building through the following commands.
```
npm run lint
npm run test
npm run build
```
After those steps finish successfully, a docker image is built, tagged, and pushed into ACR in the same way that the API container is dockerized. The difference is that the `$ACR_CONTAINER_TAG` variable in the UI build `Variables` is `pj-ui:$(Build.BuildNumber)` to differentiate the docker container from the API in ACR.
## API Release Pipeline
The release pipeline for the API was built with Azure Dev Ops, under `Pipelines` and `Releases`. The pipeline was configured so that when there was a new build of the master branch in the API repository, a deployment of the API to Azure App Services would start. The API Deployment pipeline has two tasks in the `Stages` section, which are `Person Endpoint Deploy` and `Title Endpoint Deploy`. Each task deploys an Azure App Service of the type `Linux App` to the `jackson-person` and `jackson-title`, respectively, App Services. The `Image Source` is `Container Registry`, the `Registry or Namespace` is `jacksoncontainer.azurecr.io`, the `Repository` is `pj-api/pj-api-combined`, and the `Tag` is `$(Build.BuildNumber)` which match the values for the docker image pushed into ACR that was set in the build step. For each task, there were some environment variables that needed to be configured under `App settings` and those variables and values are the ones described in the [`README.md`](../README.md). In order for the front end to communicate with the API successfully, the `ALLOWED_ORIGIN` variable is set to `*` for both tasks. By setting the `App settings` in the deployment task, the `Application Settings` in the Azure Portal for the App Service will get set to the values from the deployment.
## Client Release Pipeline
Like the build pipeline, the release pipeline for the UI is almost identical to the API release pipeline, with key differences being that only the `pj-client` App Service is deployed to and that the docker image being pulled from ACR is from the `pj-client` repository. Only a single environment variable is defined in `Application settings` and it is named `WEBSITES_PORT` with a value of `8080`. In a similar fashion to the API, when there is a build on the master branch of the UI repository, a new release deployment is kicked off.

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅
docs/images/high_level_architecture.png Normal file

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» Π½Π΅ отобраТаСтся.

ПослС

Π¨ΠΈΡ€ΠΈΠ½Π°:  |  Высота:  |  Π Π°Π·ΠΌΠ΅Ρ€: 239 KiB

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,5 @@
# ACR
Project Jackson uses Azure Container Registry to push and pull images. Deploy your own instance using the button below based off the included ARM template.
[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/)

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,28 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"registries_containers_name": {
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.ContainerRegistry/registries",
"sku": {
"name": "Basic",
"tier": "Basic"
},
"name": "[parameters('registries_containers_name')]",
"apiVersion": "2017-10-01",
"location": "westus",
"tags": {},
"scale": null,
"properties": {
"adminUserEnabled": true
},
"dependsOn": []
}
]
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,5 @@
# CosmosDB
Project Jackson uses a CosmosDB instance enabled with the MongoDB API. Deploy your own instance using the button below based off the included ARM template.
[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/)

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,35 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resource-name": {
"type": "string"
},
"database-name": {
"type": "string"
}
},
"resources": [
{
"type": "Microsoft.DocumentDB/databaseAccounts",
"kind": "MongoDB",
"name": "[parameters('resource-name')]",
"apiVersion": "2015-04-08",
"location": "West US",
"tags": {
"defaultExperience": "MongoDB"
},
"scale": null,
"properties": {
"databaseAccountOfferType": "Standard",
"consistencyPolicy": {
"defaultConsistencyLevel": "Session",
"maxIntervalInSeconds": 5,
"maxStalenessPrefix": 100
},
"name": "[parameters('database-name')]"
},
"dependsOn": []
}
]
}

60
infrastructure/README.md Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,60 @@
# Project Jackson Infrastructure
This repository tracks various Project Jackson ARM templates.
### Table Of Contents
- [ACR](/ACR/README.md)
- [CosmosDB](/CosmosDB/README.md)
## Performance Testing
How to performance test an App Service.
1. Navigate to the Azure Portal for your App Service
2. Under `Developer Tools` select `Performance Testing`
!['This image is of the performance testing menu item'](/images/perftest1.png)
3. Select `New`
!['This image is of new performance testing button'](/images/perftest2.png)
4. Name the new performance test and configure the settings appropriately
!['This image is of performance testing settings'](/images/perftest3.png)
5. Submit the test and after resources are automatically allocated it will run
After it completes, Azure will automatically generate graphs and charts for you to easily analyze the performance test.
## ARM Template
To deploy all the resources, the script deploy.sh can be used.
The below values are required as the script inputs:
1. Azure Subscription ID
2. Azure Resource Group (Add existing if any else create a new one)
3. Azure Deployment Location (eastus, westus, etc)
4. App-name: Application Name
Another way to deploy is to run one-click deploy for all resources using below Deploy to Azure:
[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/)
Once the ACR is deployed using the above deployment method follow the below manual steps to set up CD pipeline:
1. Create a new variable group in Azure Pipeline Library
2. Create variable ACR_SERVER and set value to the server name which will be the output of your deployment (<application name>container.azurecr.io)
3. Get values of username and password from container using Azure Portal
4. Create variables ACR_USERNAME and ACR_PASSWORD and set it using the above values respectively.
5. Once you set the above variables, your deployment resources can now be used as part of your CD pipeline.
## Environments
- Different environments like Dev, QA, Staging and Production environments are created under the resource group for all the resources to be deployed using ARM Template.
- Policies can be created between each of the environments to promote builds from one environment to another based on the requirements of the customer.
- These policies can differ for each customer and product.
- Once the tests under Dev environment passes, they can be approved to run on the QA environment based on policies set for approvals on each. These policies can be set under Azure DevOps Release Pipeline.
## Redis Cache
- A Redis cache is part of the resources to enhance performance of queries.
- The capacity of the Redis cache can be changed in the ARM template anywhere between 1 to 6.
- One also has the option to enable or disable Non SSL port from the ARM template.
- Azure allows 3 different values for the sku viz. Basic, Standard and Premium having different costs for each.
## Auto Scaling
- The app service is enabled with auto scaling
- When the CPU used is above 70 percent, the app will automatically be scaled to add another compute instance and this can be done for up to a max of 5 instances which can be changed based on requirements.
- When the memory on an instance used is above 70 percent, the app will automatically be scaled to add another compute instance and this can be done for up to a max of 5 instances which can be changed based on requirements.
- The minimum number of instances is set to be 1 which means, if the memory used on an instance is less, the instances will be scaled down automatically.

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,35 @@
trigger:
branches:
include:
- master
paths:
include:
- infrastructure/*
pool:
vmImage: 'Ubuntu 16.04'
steps:
# Validate ARM template
- task: AzureResourceGroupDeployment@2
displayName: 'Validate ARM template'
inputs:
azureSubscription: $(serviceConnectionAzureSubscription)
resourceGroupName: $(resourceGroupName)
location: $(location)
csmFile: infrastructure/azuredeploy.json
overrideParameters: '-application_name $(applicationName) -docker_registry_url $(ACR_SERVER) -docker_registry_username $(ACR_USERNAME) -docker_registry_password $(ACR_PASSWORD)'
deploymentMode: Validation
# Copy ARM deployment JSON
- task: CopyFiles@2
displayName: 'Copy ARM deployment JSON'
inputs:
contents: 'infrastructure/azuredeploy.json'
targetFolder: '$(Build.ArtifactStagingDirectory)'
# Publish Build Artifacts
- task: PublishBuildArtifacts@1
inputs:
PathToPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: package
publishLocation: Container

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,283 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"application_name": {
"type": "string"
},
"docker_registry_url": {
"type": "string"
},
"docker_registry_username": {
"type": "securestring"
},
"docker_registry_password": {
"type": "securestring"
}
},
"variables": {
"autoscale_settings_name": "[concat(parameters('application_name'), '-autoscale')]",
"components_insights_name": "[concat(parameters('application_name'), '-componentsinsights')]",
"database_accounts_name": "[concat(parameters('application_name'), '-databaseaccounts')]",
"sites_api_title_name": "[concat(parameters('application_name'), '-title')]",
"sites_api_person_name": "[concat(parameters('application_name'), '-person')]",
"serverfarms_appservice_plan_name": "[concat(parameters('application_name'), '-appserviceplan')]",
"traffic_manager_profiles_name": "[concat(parameters('application_name'), '-trafficManager')]"
},
"resources": [
{
"type": "Microsoft.DocumentDB/databaseAccounts",
"kind": "MongoDB",
"name": "[variables('database_accounts_name')]",
"apiVersion": "2015-04-08",
"location": "[resourceGroup().location]",
"tags": {
"defaultExperience": "MongoDB"
},
"scale": null,
"properties": {
"databaseAccountOfferType": "Standard",
"consistencyPolicy": {
"defaultConsistencyLevel": "Session",
"maxIntervalInSeconds": 5,
"maxStalenessPrefix": 100
},
"name": "[variables('database_accounts_name')]"
},
"dependsOn": []
},
{
"type": "Microsoft.Insights/components",
"kind": "web",
"name": "[variables('components_insights_name')]",
"apiVersion": "2015-05-01",
"location": "South Central US",
"tags": {},
"scale": null,
"properties": {
"Application_Type": "java",
"Flow_Type": "Redfield",
"Request_Source": "IbizaAIExtension",
"HockeyAppId": null,
"SamplingPercentage": null
},
"dependsOn": []
},
{
"type": "Microsoft.Web/serverfarms",
"sku": {
"name": "S1",
"tier": "Basic",
"size": "S1",
"family": "S",
"capacity": 1
},
"kind": "linux",
"name": "[variables('serverfarms_appservice_plan_name')]",
"apiVersion": "2016-09-01",
"location": "[resourceGroup().location]",
"scale": null,
"properties": {
"name": "[variables('serverfarms_appservice_plan_name')]",
"workerTierName": null,
"adminSiteName": null,
"hostingEnvironmentProfile": null,
"perSiteScaling": false,
"reserved": true,
"targetWorkerCount": 0,
"targetWorkerSizeId": 0
},
"dependsOn": []
},
{
"type": "Microsoft.Web/sites",
"name": "[variables('sites_api_title_name')]",
"apiVersion": "2016-03-01",
"location": "[resourceGroup().location]",
"properties": {
"name": "[variables('sites_api_title_name')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2016-03-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('sites_api_title_name'))]"
],
"tags": {
"displayName": "appSettings"
},
"properties": {
"DB_CONNSTR": "[concat('mongodb://', variables('database_accounts_name'), ':', listKeys(resourceId('Microsoft.DocumentDb/databaseAccounts', variables('database_accounts_name')), '2015-04-08').primaryMasterKey, '@', variables('database_accounts_name'), '.documents.azure.com:10250/?ssl=true&replicaSet=globaldb')]",
"DB_NAME": "IMDb",
"RESOURCE_GROUP": "[resourceGroup().name]",
"spring.profiles.active": "production",
"DOCKER_REGISTRY_SERVER_USERNAME": "[parameters('docker_registry_username')]",
"DOCKER_REGISTRY_SERVER_PASSWORD": "[parameters('docker_registry_password')]",
"DOCKER_REGISTRY_SERVER_URL": "[parameters('docker_registry_url')]",
"EXCLUDE_FILTER": "PersonRepository"
}
}
]
},
{
"type": "Microsoft.Web/sites",
"name": "[variables('sites_api_person_name')]",
"apiVersion": "2016-03-01",
"location": "[resourceGroup().location]",
"properties": {
"name": "[variables('sites_api_person_name')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2016-03-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('sites_api_person_name'))]"
],
"tags": {
"displayName": "appSettings"
},
"properties": {
"DB_CONNSTR": "[concat('mongodb://', variables('database_accounts_name'), ':', listKeys(resourceId('Microsoft.DocumentDb/databaseAccounts', variables('database_accounts_name')), '2015-04-08').primaryMasterKey, '@', variables('database_accounts_name'), '.documents.azure.com:10250/?ssl=true&replicaSet=globaldb')]",
"DB_NAME": "IMDb",
"RESOURCE_GROUP": "[resourceGroup().name]",
"spring.profiles.active": "production",
"DOCKER_REGISTRY_SERVER_USERNAME": "[parameters('docker_registry_username')]",
"DOCKER_REGISTRY_SERVER_PASSWORD": "[parameters('docker_registry_password')]",
"DOCKER_REGISTRY_SERVER_URL": "[parameters('docker_registry_url')]",
"EXCLUDE_FILTER": "PersonRepository"
}
}
]
},
{
"type": "Microsoft.Network/trafficManagerProfiles",
"name": "[variables('traffic_manager_profiles_name')]",
"apiVersion": "2017-05-01",
"location": "global",
"tags": {},
"scale": null,
"properties": {
"profileStatus": "Enabled",
"trafficRoutingMethod": "Geographic",
"dnsConfig": {
"relativeName": "[variables('traffic_manager_profiles_name')]",
"fqdn": "[concat(variables('traffic_manager_profiles_name'),'.trafficmanager.net')]",
"ttl": 60
},
"monitorConfig": {
"protocol": "HTTP",
"port": 80,
"path": "/",
"intervalInSeconds": 30,
"toleratedNumberOfFailures": 3,
"timeoutInSeconds": 10
},
"endpoints": [
{
"name": "[concat(variables('traffic_manager_profiles_name'),'-endpoint-west')]",
"type": "Microsoft.Network/trafficManagerProfiles/azureEndpoints",
"properties": {
"endpointStatus": "Enabled",
"targetResourceId": "[resourceId('Microsoft.Web/sites', variables('sites_api_title_name'))]",
"target": "[concat(variables('sites_api_title_name'),'.azurewebsites.net')]",
"weight": 1,
"priority": 1,
"endpointLocation": "westus",
"geoMapping": [
"US-WA"
]
}
}
]
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('sites_api_title_name'))]"
]
},
{
"comments": "Autoscale Settings",
"type": "microsoft.insights/autoscalesettings",
"name": "[variables('autoscale_settings_name')]",
"apiVersion": "2014-04-01",
"location": "[resourceGroup().location]",
"tags": {},
"scale": null,
"properties": {
"profiles": [
{
"name": "Auto created scale condition",
"capacity": {
"minimum": "1",
"maximum": "5",
"default": "1"
},
"rules": [
{
"metricTrigger": {
"metricName": "CpuPercentage",
"metricNamespace": "",
"metricResourceUri": "[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]",
"timeGrain": "PT1M",
"statistic": "Average",
"timeWindow": "PT10M",
"timeAggregation": "Average",
"operator": "GreaterThan",
"threshold": 70
},
"scaleAction": {
"direction": "Increase",
"type": "ChangeCount",
"value": "1",
"cooldown": "PT5M"
}
},
{
"metricTrigger": {
"metricName": "MemoryPercentage",
"metricNamespace": "",
"metricResourceUri": "[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]",
"timeGrain": "PT1M",
"statistic": "Average",
"timeWindow": "PT10M",
"timeAggregation": "Average",
"operator": "GreaterThan",
"threshold": 70
},
"scaleAction": {
"direction": "Increase",
"type": "ChangeCount",
"value": "1",
"cooldown": "PT5M"
}
}
]
}
],
"enabled": true,
"name": "[variables('autoscale_settings_name')]",
"targetResourceUri": "[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('serverfarms_appservice_plan_name'))]"
]
}
],
"outputs": {
"connectionString": {
"value": "[concat('mongodb://', variables('database_accounts_name'), ':', listKeys(resourceId('Microsoft.DocumentDb/databaseAccounts', variables('database_accounts_name')), '2015-04-08').primaryMasterKey, '@', variables('database_accounts_name'), '.documents.azure.com:10250/?ssl=true&replicaSet=globaldb')]",
"type": "string"
}
}
}

117
infrastructure/deploy.sh Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,117 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
# -e: immediately exit if any command has a non-zero exit status
# -o: prevents errors in a pipeline from being masked
# IFS new value is less likely to cause confusing bugs when looping arrays or arguments (e.g. $@)
usage() { echo "Usage: $0 -i <subscriptionId> -g <resourceGroupName> -n <deploymentName> -l <resourceGroupLocation>" 1>&2; exit 1; }
declare subscriptionId=""
declare resourceGroupName=""
declare deploymentName=""
declare resourceGroupLocation=""
# Initialize parameters specified from command line
while getopts ":i:g:n:l:" arg; do
case "${arg}" in
i)
subscriptionId=${OPTARG}
;;
g)
resourceGroupName=${OPTARG}
;;
n)
deploymentName=${OPTARG}
;;
l)
resourceGroupLocation=${OPTARG}
;;
esac
done
shift $((OPTIND-1))
#Prompt for parameters is some required parameters are missing
if [[ -z "$subscriptionId" ]]; then
echo "Your subscription ID can be looked up with the CLI using: az account show --out json "
echo "Enter your subscription ID:"
read subscriptionId
[[ "${subscriptionId:?}" ]]
fi
if [[ -z "$resourceGroupName" ]]; then
echo "This script will look for an existing resource group, otherwise a new one will be created "
echo "You can create new resource groups with the CLI using: az group create "
echo "Enter a resource group name"
read resourceGroupName
[[ "${resourceGroupName:?}" ]]
fi
if [[ -z "$resourceGroupLocation" ]]; then
echo "If creating a *new* resource group, you need to set a location "
echo "You can lookup locations with the CLI using: az account list-locations "
echo "Enter resource group location:"
read resourceGroupLocation
fi
#templateFile Path - template file to be used
templateFilePath="azuredeploy.json"
if [ ! -f "$templateFilePath" ]; then
echo "$templateFilePath not found"
exit 1
fi
#parameter file path
#parametersFilePath="parameters.json"
#if [ ! -f "$parametersFilePath" ]; then
# echo "$parametersFilePath not found"
# exit 1
#fi
if [ -z "$subscriptionId" ] || [ -z "$resourceGroupName" ]; then
echo "Either one of subscriptionId, resourceGroupName, deploymentName is empty"
usage
fi
#login to azure using your credentials
az account show 1> /dev/null
if [ $? != 0 ];
then
az login
fi
#set the default subscription id
az account set --subscription $subscriptionId
set +e
#Check for existing RG
az group show --name $resourceGroupName 1> /dev/null
if [ $? != 0 ]; then
echo "Resource group with name" $resourceGroupName "could not be found. Creating new resource group.."
set -e
(
set -x
az group create --name $resourceGroupName --location $resourceGroupLocation 1> /dev/null
)
else
echo "Using existing resource group..."
fi
#Start deployment
echo "Starting deployment..."
(
set -x
az group deployment create --name "$deploymentName" --resource-group "$resourceGroupName" --template-file "$templateFilePath" #--parameters "@${parametersFilePath}"
)
if [ $? == 0 ];
then
echo "Template has been successfully deployed"
fi

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅
infrastructure/images/perftest1.png Normal file

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» Π½Π΅ отобраТаСтся.

ПослС

Π¨ΠΈΡ€ΠΈΠ½Π°:  |  Высота:  |  Π Π°Π·ΠΌΠ΅Ρ€: 411 KiB

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅
infrastructure/images/perftest2.png Normal file

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» Π½Π΅ отобраТаСтся.

ПослС

Π¨ΠΈΡ€ΠΈΠ½Π°:  |  Высота:  |  Π Π°Π·ΠΌΠ΅Ρ€: 169 KiB

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅
infrastructure/images/perftest3.png Normal file

Π”Π²ΠΎΠΈΡ‡Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» Π½Π΅ отобраТаСтся.

ПослС

Π¨ΠΈΡ€ΠΈΠ½Π°:  |  Высота:  |  Π Π°Π·ΠΌΠ΅Ρ€: 88 KiB

214
load_env.sh Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,214 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
# -e: immediately exit if any command has a non-zero exit status
# -o: prevents errors in a pipeline from being masked
# IFS new value is less likely to cause confusing bugs when looping arrays or arguments (e.g. $@)
#******************************************************************************
# Script to set environment variables
#******************************************************************************
usage() { echo "Usage: $0 -i <subscriptionId> -g <resourceGroupName> -n <appName> -l <resourceGroupLocation>" 1>&2; exit 1; }
declare subscriptionId=""
declare resourceGroupName=""
declare resourceGroupLocation=""
declare dbName=""
declare connStr=""
declare dbPassword=""
# Initialize parameters specified from command line
while getopts ":i:g:n:l:" arg; do
case "${arg}" in
i)
subscriptionId=${OPTARG}
;;
g)
resourceGroupName=${OPTARG}
;;
l)
resourceGroupLocation=${OPTARG}
;;
d)
dbName=${OPTARG}
esac
done
shift $((OPTIND-1))
#******************************************************************************
# Helper functions
#******************************************************************************
azLogin() {
(
set +e
#login to azure using your credentials
az account show &> /dev/null
if [ $? != 0 ];
then
echo "Azure login required..."
az login -o table
else
az account list -o table
fi
)
}
validatedRead() {
prompt=$1
regex=$2
error=$3
userInput=""
while [[ ! $userInput =~ $regex ]]; do
if [[ (-n $userInput) ]]; then
printf "'%s' is not valid. %s\n" $userInput $error
fi
printf $prompt
read userInput
done
}
readSubscriptionId () {
currentSub="$(az account show -o tsv | cut -f2)"
subNames="$(az account list -o tsv | cut -f4)"
subIds="$(az account list -o tsv | cut -f2)"
while ([[ -z "$subscriptionId" ]]); do
printf "Enter your subscription ID [%s]: " $currentSub
read userInput
if [[ (-z "$userInput") && (-n "$currentSub")]]; then
userInput=$currentSub
fi
set +e
nameExists="$(echo $subNames | grep $userInput)"
idExists="$(echo $subIds | grep $userInput)"
if [[ (-z "$nameExists") && (-z "$idExists") ]]; then
printf "'${userInput}' is not a valid subscription name or ID.\n"
else
subscriptionId=$userInput
printf "Using subscription '$subscriptionId'...\n"
fi
done
}
readResourceGroupName () {
printf "Existing resource groups:\n"
groups="$(az group list -o tsv | cut -f4 | tr '\n' ', ' | sed "s/,/, /g")"
printf "\n%s\n" "${groups%??}"
validatedRead "\nEnter a resource group name: " "^[a-zA-Z0-9_]+$" "Only letters, numbers and underscores are allowed."
resourceGroupName=$userInput
set +e
#Check for existing RG
az group show --name $resourceGroupName &> /dev/null
if [ $? != 0 ]; then
echo "To create a new resource group, please enter an Azure location:"
readLocation
(set -ex; az group create --name $resourceGroupName --location $resourceGroupLocation)
else
resourceGroupLocation="$(az group show -n $resourceGroupName -o tsv | cut -f2)"
printf "Using resource group '$resourceGroupName'...\n"
fi
set -e
}
readLocation() {
if [[ -z "$resourceGroupLocation" ]]; then
locations="$(az account list-locations --output tsv | cut -f5 | tr '\n' ', ' | sed "s/,/, /g")"
printf "\n%s\n" "${locations%??}"
declare locationExists
while ([[ -z $resourceGroupLocation ]]); do
validatedRead "\nEnter resource group location: " "^[a-zA-Z0-9]+$" "Only letters & numbers are allowed."
locationExists="$(echo $locations | grep $userInput)"
if [[ -z $locationExists ]]; then
printf "'${userInput}' is not a valid location.\n"
else
resourceGroupLocation=$userInput
printf "Using resource group '$resourceGroupName'...\n"
fi
done
fi
}
readDbName () {
dbNames="$(az cosmosdb list -g $resourceGroupName -o tsv | cut -f12)"
defaultDb=(${dbNames[@]})
while ([[ -z "$dbName" ]]); do
printf "Cosmos DB instances in group '$resourceGroupName':\n\n"
dbNames="$(az cosmosdb list -g $resourceGroupName -o tsv | cut -f12 | tr '\n' ', ' | sed "s/,/, /g")"
printf "${dbNames%??}\n\n"
printf "Enter the Cosmos DB name [%s]: " $defaultDb
read userInput
if [[ (-z "$userInput") && (-n "$defaultDb")]]; then
userInput=$defaultDb
fi
set +e
nameExists="$(echo $dbNames | grep $userInput)"
if [[ (-z "$nameExists") ]]; then
printf "'${userInput}' is not a valid Cosmos DB name.\n"
else
dbName=$userInput
printf "Using database '$dbName'...\n"
fi
done
}
#******************************************************************************
# Script to set environment variables
#******************************************************************************
azLogin
#Prompt for parameters if some required parameters are missing
if [[ -z "$subscriptionId" ]]; then
echo
readSubscriptionId
fi
#set the default subscription id
az account set --subscription $subscriptionId
if [[ -z "$resourceGroupName" ]]; then
echo
readResourceGroupName
fi
if [[ -z "$dbName" ]]; then
echo
readDbName
fi
# At this time, list-connection-strings does not support '-o tsv', so this command uses sed to extract the connection string from json results
connString="$(az cosmosdb list-connection-strings --ids $dbName -g $resourceGroupName | sed -n -e '4 p' | sed -E -e 's/.*mongo(.*)true.*/mongo\1true/')"
# But list-keys does support `-o tsv`
dbPassword="$(az cosmosdb list-keys --resource-group $resourceGroupName --name $dbName -o tsv | sed -e 's/\s.*$//')"
touch vars.env
echo "export RESOURCE_GROUP=${resourceGroupName}" > vars.env
echo "export COSMOSDB_NAME=${dbName}" >> vars.env
echo "export COSMOSDB_PASSWORD=${dbPassword}" >> vars.env
# Creates a distinction - some of these ENV variables will be used exclusively to connect to an Azure CosmosDB instance,
# but in an Azure-agnostic setup, the DB_NAME and DB_CONNSTR may not be on CosmosDB
echo "export DB_NAME=${dbName}" >> vars.env
echo "export DB_CONNSTR=${connString}" >> vars.env
echo
echo "Variables written to file 'vars.env'"
echo

3
ui/.gitignore поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,3 @@
node_modules/
dist/
.vscode/

12
ui/Dockerfile Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,12 @@
# Nginx web server from the official docker registry
FROM nginx:1.14.0-alpine
EXPOSE 8080
RUN rm -rv /etc/nginx/conf.d
COPY conf /etc/nginx
# The static site is built using npm run build
# the output of build is stored in the dist dir
WORKDIR /usr/share/nginx/html
COPY ./dist/ /usr/share/nginx/html

23
ui/README.md Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,23 @@
# Project Jackson Client
- Built using `React`
- Compiled and bundled using `TypeScript` and `Webpack`
- Tested using `Jest` and `Enzyme`
## Deploy
In order to deploy this app, in development or production, you must define the AAD Client ID as an environment variable. It will be injected during build time with Webpack. The environment variable that needs to be set is called `WEBPACK_PROP_AAD_CLIENT_ID` and is provided by your AAD App settings available on the AAD Azure Portal.
In development, you should need to set the env variable on process and then run `npm run dev` as usual. For example, on a UNIX Bash shell you can run `WEBPACK_PROP_AAD_CLIENT_ID=<insert-id-here> npm run dev`.
In production, make sure to set this in the build pipeline such as in Azure Dev Ops.
## Contributing
Run `npm run dev` to launch a hot-reloading webpack server
Before commiting your changes make sure to run `npm run lint`
Testing with `npm run test` will run all test files in the `src/__tests__` directory
The `conf` directory is for production deployment purposes with NGINX

38
ui/azure-pipelines.yml Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,38 @@
trigger:
branches:
include:
- master
paths:
include:
- ui/*
pool:
vmImage: 'Ubuntu 16.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.13.0'
failOnStandardError: 'true'
displayName: 'Install Node.js'
- script: |
npm install
displayName: 'NPM Install Step'
- script: |
npm run lint
displayName: 'NPM Lint Step'
- script: |
npm run test
displayName: 'NPM Test Step'
- script: |
npm run build
displayName: 'NPM Build Step'
- script: |
docker build -t $ACR_SERVER/$ACR_CONTAINER_TAG .
displayName: 'Docker Build'
- script: |
docker login $(ACR_SERVER) -u $(ACR_USERNAME) -p $(ACR_PASSWORD)
displayName: 'Docker Login'
- script: |
docker push $ACR_SERVER/$ACR_CONTAINER_TAG
displayName: 'Docker Push'

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,17 @@
server {
listen 8080;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

22
ui/jest.config.js Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,22 @@
module.exports = {
moduleFileExtensions: [ "ts", "tsx", "js" ],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest"
},
globals: {
"ts-jest": {
"tsConfig": "tsconfig.json"
},
"WEBPACK_PROP_AAD_CLIENT_ID": ''
},
testMatch: [
"**/__tests__/**/*.(ts|tsx|js)"
],
testPathIgnorePatterns: [
"setup.ts"
],
moduleNameMapper: {
"\\.(css)$": "identity-obj-proxy"
},
setupTestFrameworkScriptFile: "<rootDir>/src/__tests__/setup.ts"
}

9896
ui/package-lock.json сгСнСрированный Normal file

Π Π°Π·Π½ΠΈΡ†Π° ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ Π½Π΅ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° ΠΈΠ·-Π·Π° своСго большого Ρ€Π°Π·ΠΌΠ΅Ρ€Π° Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ Ρ€Π°Π·Π½ΠΈΡ†Ρƒ

47
ui/package.json Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,47 @@
{
"name": "projectjacksonclient",
"version": "1.0.0",
"description": "Web App demo for Project Jackson ",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --hot --open --config webpack.dev.js",
"test": "jest --config jest.config.js",
"build": "webpack --config webpack.prod.js",
"lint": "tslint -c tslint.json 'src/**/*.tsx'"
},
"author": "Ethan Arrowood",
"license": "MIT",
"dependencies": {
"@reach/router": "^1.2.1",
"msal": "^0.2.3",
"react": "^16.6.0",
"react-dom": "^16.6.0",
"whatwg-fetch": "^3.0.0"
},
"devDependencies": {
"@types/enzyme": "^3.1.15",
"@types/jest": "^23.3.8",
"@types/reach__router": "^1.2.1",
"@types/react": "^16.4.18",
"@types/react-dom": "^16.0.9",
"awesome-typescript-loader": "^5.2.1",
"css-loader": "^1.0.1",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.6.0",
"html-webpack-plugin": "^3.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^23.6.0",
"jest-dom": "^2.1.0",
"react-test-renderer": "^16.6.0",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
"ts-jest": "^23.10.4",
"ts-loader": "^5.2.2",
"tslint": "^5.11.0",
"typescript": "^3.1.3",
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"webpack-merge": "^4.1.4"
}
}

13
ui/src/__tests__/App.tsx Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,13 @@
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { App } from '../components/App'
describe('<App />', () => {
it('renders correctly', () => {
const tree = renderer
.create(<App />)
.toJSON()
expect(tree).toMatchSnapshot()
})
})

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,49 @@
import {
createHistory,
createMemorySource,
LocationProvider,
} from '@reach/router'
import { render } from 'enzyme'
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { App } from '../components/App'
import { DefaultComponent } from '../components/DefaultComponent'
describe('<DefaultComponent />', () => {
it('renders correctly', () => {
const tree = renderer
.create(<DefaultComponent default />)
.toJSON()
expect(tree).toMatchSnapshot()
})
describe('It is the default 404 page', () => {
test('It finds the page if user tries a nonsense path', () => {
const badPath = '/awefwaef'
const source = createMemorySource(badPath)
const hist = createHistory(source)
const r = render(
<LocationProvider history={hist}>
<App />
</LocationProvider>,
)
expect(r.find('.default-component')).toHaveLength(1)
expect(r.find('.default-component1')).toHaveLength(0)
})
test('It doesn\'t find the page if user tries a valid path', () => {
const goodPath = '/titles'
const source = createMemorySource(goodPath)
const hist = createHistory(source)
const r = render(
<LocationProvider history={hist}>
<App />
</LocationProvider>,
)
expect(r.find('.default-component')).toHaveLength(0)
})
})
})

14
ui/src/__tests__/Home.tsx Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,14 @@
import { mount } from 'enzyme'
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { Home } from '../components/Home'
describe('<Home />', () => {
it('renders correctly', () => {
const tree = renderer
.create(<Home path='/'/>)
.toJSON()
expect(tree).toMatchSnapshot()
})
})

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,25 @@
import {mount } from 'enzyme'
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { Navbar } from '../components/Navbar'
describe('<Navbar />', () => {
it('renders correctly', () => {
const tree = renderer
.create(<Navbar />)
.toJSON()
expect(tree).toMatchSnapshot()
})
it('should activate Home link by default', () => {
const navbarWrapper = mount(<Navbar />)
// @reach/router sets the 'aria-current' property to 'page' when the Link element is active
// The Navbar component adds the 'active' class to the className list too
const linkElementWrapper = navbarWrapper.find({
'aria-current': 'page',
'className': 'nav-link active',
'href': '/',
})
expect(linkElementWrapper.text()).toBe('Home')
})
})

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,49 @@
import { mount } from 'enzyme'
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { PageForm } from '../components/PageForm'
describe('<PageForm />', () => {
it('renders correctly', () => {
const tree = renderer
.create(
<PageForm
inputTitle='test'
inputPlaceholder='test'
onInputChange={() => null}
onSubmitClick={() => null}
/>,
)
.toJSON()
expect(tree).toMatchSnapshot()
})
it('should fire input change handler', () => {
const mockFn = jest.fn()
const pageFormWrapper = mount(
<PageForm
inputTitle='test'
inputPlaceholder='test'
onInputChange={mockFn}
onSubmitClick={() => null}
/>,
)
const textInput = pageFormWrapper.find('.form-field-input')
textInput.simulate('change', {})
expect(mockFn.mock.calls.length).toBe(1)
})
it('should fire submit button handler', () => {
const mockFn = jest.fn()
const pageFormWrapper = mount(
<PageForm
inputTitle='test'
inputPlaceholder='test'
onInputChange={() => null}
onSubmitClick={mockFn}
/>,
)
const submitButton = pageFormWrapper.find('.form-field-submit')
submitButton.simulate('click', {})
expect(mockFn.mock.calls.length).toBe(1)
})
})

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,13 @@
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { Person } from '../components/Person'
describe('<Person />', () => {
it('renders correctly', () => {
const tree = renderer
.create(<Person path='/people'/>)
.toJSON()
expect(tree).toMatchSnapshot()
})
})

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,13 @@
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { Title } from '../components/Title'
describe('<Title />', () => {
it('renders correctly', () => {
const tree = renderer
.create(<Title path='/titles'/>)
.toJSON()
expect(tree).toMatchSnapshot()
})
})

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,169 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<App /> renders correctly 1`] = `
<div
className="app-container"
>
<nav
className="nav-container"
>
<span
className="nav-title"
>
Project Jackson
</span>
<div
className="nav-link-container"
>
<a
aria-current="page"
className="nav-link active"
href="/"
onClick={[Function]}
>
Home
</a>
<a
className="nav-link"
href="/people"
onClick={[Function]}
>
People
</a>
<a
className="nav-link"
href="/titles"
onClick={[Function]}
>
Titles
</a>
<button
className="login-button"
onClick={[Function]}
>
Log In
</button>
</div>
</nav>
<div
role="group"
style={
Object {
"outline": "none",
}
}
tabIndex="-1"
>
<div
className="page-container--home"
>
<h1
className="home-title"
>
Welcome to Project Jackson!
</h1>
<img
alt="Welcome to the Home Page"
className="home-img"
src="https://media.giphy.com/media/3o6Ztrs0GnTt4GkFO0/giphy.gif"
/>
<p
className="center"
>
<b>
Project Jackson
</b>
is an open-source project, created by members of Microsoft's CSE team, to demonstrate the handiness and effictiveness of Azure resources.
<br />
<br />
Azure resources used include
<a
href="https://docs.microsoft.com/en-us/azure/cosmos-db/"
>
CosmosDB
</a>
,
<a
href="https://azure.microsoft.com/en-us/services/traffic-manager/"
>
Traffic Manager
</a>
,
<a
href="https://azure.microsoft.com/en-us/services/app-service/containers/"
>
App Service for Containers
</a>
, and
<a
href="https://azure.microsoft.com/en-us/services/application-gateway/"
>
Application Gateway
</a>
.
<br />
<br />
In order to demonstrate Cosmos DB performance with large amounts of data, the project imports historical movie data from IMDb. See
<a
href="https://datasets.imdbws.com/"
>
here for downloadable IMDB datasets
</a>
. The datasets include 8.9 million people, 5.3 million movies and 30 million relationships between them.
<br />
<br />
Languages used for this project include Java, Javascript, and Typescript. These languages were selected because they are all well documented and well-suited for our purposes.
<br />
<br />
Technologies used include:
</p>
<ul
className="center"
>
<li>
<i>
Java Spring
</i>
, a platform that provides infrastructure support for Java applications
</li>
<li>
<i>
Docker
</i>
, a tool used in order to isolate different microservices, allowing for easy maintenance and testing
</li>
<li>
<i>
React
</i>
, a component-based front-end Javascript library, used for building UIs
</li>
<li>
<i>
Reach Router
</i>
, a Javascript library that manages the focus of apps on route transitions and focuses on user accessibility
</li>
<li>
<i>
Jest
</i>
, a Javascript library used to test front-end rendering
</li>
<li>
<i>
Webpack
</i>
, a bundler for Javascript files
</li>
<li>
<i>
Custom Azure Resource Manager (ARM) templates
</i>
were utilized, to allow one-click deployment of Azure resources and services
</li>
</ul>
</div>
</div>
</div>
`;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<DefaultComponent /> renders correctly 1`] = `
<div
className="default-component cover"
>
<h1>
Oh Deer :(
<br />
<small>
Error 404
</small>
</h1>
<p
className="lead"
>
The requested resource could not be found but may be available again in the future.
</p>
<img
className="default-page-image"
src="https://i.gifer.com/KG8.gif"
/>
</div>
`;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,114 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Home /> renders correctly 1`] = `
<div
className="page-container--home"
>
<h1
className="home-title"
>
Welcome to Project Jackson!
</h1>
<img
alt="Welcome to the Home Page"
className="home-img"
src="https://media.giphy.com/media/3o6Ztrs0GnTt4GkFO0/giphy.gif"
/>
<p
className="center"
>
<b>
Project Jackson
</b>
is an open-source project, created by members of Microsoft's CSE team, to demonstrate the handiness and effictiveness of Azure resources.
<br />
<br />
Azure resources used include
<a
href="https://docs.microsoft.com/en-us/azure/cosmos-db/"
>
CosmosDB
</a>
,
<a
href="https://azure.microsoft.com/en-us/services/traffic-manager/"
>
Traffic Manager
</a>
,
<a
href="https://azure.microsoft.com/en-us/services/app-service/containers/"
>
App Service for Containers
</a>
, and
<a
href="https://azure.microsoft.com/en-us/services/application-gateway/"
>
Application Gateway
</a>
.
<br />
<br />
In order to demonstrate Cosmos DB performance with large amounts of data, the project imports historical movie data from IMDb. See
<a
href="https://datasets.imdbws.com/"
>
here for downloadable IMDB datasets
</a>
. The datasets include 8.9 million people, 5.3 million movies and 30 million relationships between them.
<br />
<br />
Languages used for this project include Java, Javascript, and Typescript. These languages were selected because they are all well documented and well-suited for our purposes.
<br />
<br />
Technologies used include:
</p>
<ul
className="center"
>
<li>
<i>
Java Spring
</i>
, a platform that provides infrastructure support for Java applications
</li>
<li>
<i>
Docker
</i>
, a tool used in order to isolate different microservices, allowing for easy maintenance and testing
</li>
<li>
<i>
React
</i>
, a component-based front-end Javascript library, used for building UIs
</li>
<li>
<i>
Reach Router
</i>
, a Javascript library that manages the focus of apps on route transitions and focuses on user accessibility
</li>
<li>
<i>
Jest
</i>
, a Javascript library used to test front-end rendering
</li>
<li>
<i>
Webpack
</i>
, a bundler for Javascript files
</li>
<li>
<i>
Custom Azure Resource Manager (ARM) templates
</i>
were utilized, to allow one-click deployment of Azure resources and services
</li>
</ul>
</div>
`;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Navbar /> renders correctly 1`] = `
<nav
className="nav-container"
>
<span
className="nav-title"
>
Project Jackson
</span>
<div
className="nav-link-container"
>
<a
aria-current="page"
className="nav-link active"
href="/"
onClick={[Function]}
>
Home
</a>
<a
className="nav-link"
href="/people"
onClick={[Function]}
>
People
</a>
<a
className="nav-link"
href="/titles"
onClick={[Function]}
>
Titles
</a>
<button
className="login-button"
onClick={[Function]}
>
Log In
</button>
</div>
</nav>
`;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<PageForm /> renders correctly 1`] = `
<form
className="form"
>
<label>
<span
className="form-field-pre-text"
>
test
</span>
<input
className="form-field-input"
name="id"
onChange={[Function]}
placeholder="test"
type="text"
/>
<span
className="form-field-sub-text"
>
Leave empty for random sample
</span>
</label>
<input
className="form-field-submit"
onClick={[Function]}
type="submit"
value="Search"
/>
</form>
`;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Person /> renders correctly 1`] = `
<div
className="page-container"
>
<div
className="form-container"
>
<h1>
Search People
</h1>
<p
className="form-description"
>
Enter the ID for a person to get their information from the database. Leave the form empty for a random sample of people from the database.
</p>
<form
className="form"
>
<label>
<span
className="form-field-pre-text"
>
Person ID:
</span>
<input
className="form-field-input"
name="id"
onChange={[Function]}
placeholder="Person ID"
type="text"
/>
<span
className="form-field-sub-text"
>
Leave empty for random sample
</span>
</label>
<input
className="form-field-submit"
onClick={[Function]}
type="submit"
value="Search"
/>
</form>
</div>
<div
className="results-container"
>
<h2
className="results-title"
>
Results for PersonId:
</h2>
<pre
className="results-view"
>
null
</pre>
<h4 />
</div>
</div>
`;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Title /> renders correctly 1`] = `
<div
className="page-container"
>
<div
className="form-container"
>
<h1>
Search Titles
</h1>
<p
className="form-description"
>
Enter the ID for a title to get their information from the database. Leave the form empty for a random sample of people from the database.
</p>
<form
className="form"
>
<label>
<span
className="form-field-pre-text"
>
Title ID:
</span>
<input
className="form-field-input"
name="id"
onChange={[Function]}
placeholder="Title ID"
type="text"
/>
<span
className="form-field-sub-text"
>
Leave empty for random sample
</span>
</label>
<input
className="form-field-submit"
onClick={[Function]}
type="submit"
value="Search"
/>
</form>
</div>
<div
className="results-container"
>
<h2
className="results-title"
>
Results for TitleId:
</h2>
<pre
className="results-view"
>
null
</pre>
<h4 />
</div>
</div>
`;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,4 @@
import * as Enzyme from 'enzyme'
import * as Adapter from 'enzyme-adapter-react-16'
Enzyme.configure({ adapter: new Adapter() })

85
ui/src/components/App.tsx Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,85 @@
import { Router } from '@reach/router'
import * as Msal from 'msal'
import * as React from 'react'
import { AuthProvider } from './AuthContext'
import { AuthResponseBar } from './AuthResponseBar'
import { DefaultComponent } from './DefaultComponent'
import { Home } from './Home'
import { Navbar } from './Navbar'
import { Person } from './Person'
import { PrivateRoute } from './PrivateRoute'
import { Title } from './Title'
import './../styles/App.css'
const appConfig = {
clientID: WEBPACK_PROP_AAD_CLIENT_ID,
}
// initialize the UserAgentApplication globally so popup and iframe can run in the background
const userAgentApp = new Msal.UserAgentApplication(appConfig.clientID, null, null)
export class App extends React.Component {
// App is still responsible for managing the auth state
// it uses the AuthContext to share this with other aspects of the UI
// including the navbar, private route component, and the pages themselves
public state = {
accessToken: null,
authResponse: null,
}
public handleAuth = async () => {
let accessToken = null
try {
if (this.state.accessToken) {
// log out
await userAgentApp.logout()
} else {
// log in
if (!appConfig.clientID) {
throw new Error(
'AAD Client ID has not been configured. See the \'deploy\' documentation for more details.')
}
const graphScopes = [appConfig.clientID]
await userAgentApp.loginPopup(graphScopes)
accessToken = await userAgentApp.acquireTokenSilent(graphScopes,
'https://login.microsoftonline.com/microsoft.onmicrosoft.com')
}
this.setState({ accessToken })
} catch (err) {
this.setAuthResponse(err.toString())
}
}
public setAuthResponse = (msg: string) => {
this.setState({ authResponse: msg })
}
public render() {
// Implementing the authprovider at app root to share the auth state
// with all internal components (if they subscribe to it)
// by linking the accessToken to the app state we can be certain the
// context will always update and propogate the value to subscribed nodes
return (
<AuthProvider value={{
accessToken: this.state.accessToken,
authResponse: this.state.authResponse,
handleAuth: this.handleAuth,
setAuthResponse: this.setAuthResponse,
}}>
<div className='app-container'>
<Navbar />
<AuthResponseBar />
<Router>
<Home path='/' />
<PrivateRoute as={Person} path='/people' />
<PrivateRoute as={Title} path='/titles' />
<DefaultComponent default />
</Router>
</div>
</AuthProvider>
)
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,17 @@
import * as React from 'react'
import { AuthContext } from './AuthContext'
export class AuthButton extends React.Component {
public static contextType = AuthContext
public render() {
return (
<button
onClick={this.context.handleAuth}
className='login-button'
>
{this.context.accessToken ? 'Log Out' : 'Log In'}
</button>
)
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,21 @@
import { createContext } from 'react'
export interface IAuthContextValues {
accessToken: string,
authResponse: string,
handleAuth: () => void,
setAuthResponse: (msg?: string) => void,
}
// Default values and enforce use of interface
const defaultAuthContextValue: IAuthContextValues = {
accessToken: null,
authResponse: null,
handleAuth: () => null,
setAuthResponse: () => null,
}
export const AuthContext = createContext<IAuthContextValues>(defaultAuthContextValue)
// exporting the Provider and Consumer components for more specific imports
export const AuthProvider = AuthContext.Provider
export const AuthConsumer = AuthContext.Consumer

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,15 @@
import * as React from 'react'
import { AuthContext } from './AuthContext'
export class AuthResponseBar extends React.Component {
public static contextType = AuthContext
public render() {
return this.context.authResponse && (
<div className='auth-message-container'>
<button className='auth-message-close-button' onClick={() => this.context.setAuthResponse(null)}>X</button>
<span className='auth-message'>{this.context.authResponse}</span>
</div>
)
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,37 @@
import * as React from 'react'
export interface IDefaultComponentProps {
default: boolean,
}
export interface IDefaultComponentState {
deerSrc: string,
}
export class DefaultComponent extends React.Component<IDefaultComponentProps> {
public state = {
deerSrc: null,
}
public componentWillMount() {
this.setState({ deerSrc: 'https://i.gifer.com/KG8.gif' })
}
public render() {
return (
<div className='default-component cover'>
<h1>
Oh Deer :(
<br />
<small>
Error 404
</small>
</h1>
<p className='lead'>
The requested resource could not be found but may be available again in the future.
</p>
<img className='default-page-image' src={this.state.deerSrc} />
</div>
)
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,73 @@
import * as React from 'react'
export interface IHomeProps { path?: string, }
export interface IHomeState { image: string }
export class Home extends React.Component<IHomeProps, IHomeState> {
public state = {
image: null,
}
public componentDidMount() {
this.setState({ image: 'https://media.giphy.com/media/3o6Ztrs0GnTt4GkFO0/giphy.gif' })
}
public componentWillUnmount() {
this.setState({ image: null })
}
public render() {
return(
<div className = 'page-container--home' >
<h1 className='home-title'>Welcome to Project Jackson!</h1>
<img className='home-img'
src={ this.state.image }
alt='Welcome to the Home Page'>
</img>
<p className='center'>
<b>Project Jackson</b> is an open-source project, created by members of Microsoft's CSE team,
to demonstrate the handiness and effictiveness of Azure resources.
<br />
<br />
Azure resources used include <a
href='https://docs.microsoft.com/en-us/azure/cosmos-db/'>CosmosDB
</a>, <a href='https://azure.microsoft.com/en-us/services/traffic-manager/'>Traffic Manager</a>, <a
href='https://azure.microsoft.com/en-us/services/app-service/containers/'>
App Service for Containers
</a>,
and <a href='https://azure.microsoft.com/en-us/services/application-gateway/'>Application Gateway</a>.
<br />
<br />
In order to demonstrate Cosmos DB performance with large amounts of data,
the project imports historical movie data from IMDb.
See <a href='https://datasets.imdbws.com/'> here for downloadable IMDB datasets</a>.
The datasets include 8.9 million people, 5.3 million movies and 30 million relationships between them.
<br />
<br />
Languages used for this project include Java, Javascript, and Typescript.
These languages were selected because they are all well documented and well-suited for our purposes.
<br />
<br />
Technologies used include:
</p>
<ul className='center'>
<li><i>Java Spring</i>, a platform that provides infrastructure support for Java applications </li>
<li><i>Docker</i>, a tool used in order to isolate different microservices,
allowing for easy maintenance and testing</li>
<li><i>React</i>, a component-based front-end Javascript library,
used for building UIs </li>
<li><i>Reach Router</i>, a Javascript library that manages the focus of apps on
route transitions and focuses on user accessibility</li>
<li><i>Jest</i>, a Javascript library used to test front-end rendering</li>
<li><i>Webpack</i>, a bundler for Javascript files</li>
<li><i>Custom Azure Resource Manager (ARM) templates</i> were utilized,
to allow one-click deployment of Azure resources and services</li>
</ul>
</div>
)
}
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,30 @@
import { Link } from '@reach/router'
import * as React from 'react'
import { AuthButton } from './AuthButton'
import './../styles/Navbar.css'
const isActive = ({ isCurrent }) => {
return {
className: isCurrent ? 'nav-link active' : 'nav-link',
}
}
export class Navbar extends React.Component {
public render() {
return (
<nav className='nav-container'>
<span className='nav-title'>
Project Jackson
</span>
<div className='nav-link-container'>
<Link getProps={isActive} to='/'>Home</Link>
<Link getProps={isActive} to='/people'>People</Link>
<Link getProps={isActive} to='/titles'>Titles</Link>
<AuthButton />
</div>
</nav>
)
}
}

НСкоторыС Ρ„Π°ΠΉΠ»Ρ‹ Π½Π΅ Π±Ρ‹Π»ΠΈ ΠΏΠΎΠΊΠ°Π·Π°Π½Ρ‹ ΠΈΠ·-Π·Π° слишком большого количСства ΠΈΠ·ΠΌΠ΅Π½Π΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ большС