From 5a982c5063f7aafa4be80676c7361458f1b021cb Mon Sep 17 00:00:00 2001 From: RenoAverill Date: Tue, 27 Jul 2021 14:41:43 -0600 Subject: [PATCH] Reno and Stephens completed news-site-v --- news-site-v/.gitignore | 18 + news-site-v/README.md | 188 ++ news-site-v/api_server/.editorconfig | 13 + news-site-v/api_server/.gitignore | 1 + news-site-v/api_server/.jshintignore | 2 + news-site-v/api_server/.jshintrc | 28 + news-site-v/api_server/.npmignore | 16 + news-site-v/api_server/README.md | 256 ++ .../api_server/common/models/article.js | 12 + .../api_server/common/models/article.json | 33 + .../api_server/common/models/project.js | 60 + .../api_server/common/models/project.json | 65 + news-site-v/api_server/common/models/team.js | 8 + .../api_server/common/models/team.json | 25 + news-site-v/api_server/common/models/user.js | 8 + .../api_server/common/models/user.json | 21 + news-site-v/api_server/package.json | 36 + .../api_server/server/boot/authentication.js | 9 + .../server/boot/create-sample-models.js | 25 + .../api_server/server/boot/role-resolver.js | 46 + news-site-v/api_server/server/boot/routes.js | 52 + .../server/boot/sample-articles.json | 322 ++ .../api_server/server/boot/sample-models.js | 80 + .../api_server/server/component-config.json | 5 + news-site-v/api_server/server/config.json | 23 + .../api_server/server/datasources.json | 6 + news-site-v/api_server/server/middleware.json | 34 + .../api_server/server/model-config.json | 46 + news-site-v/api_server/server/server.js | 44 + news-site-v/api_server/server/views/index.ejs | 67 + .../api_server/server/views/projects.ejs | 100 + news-site-v/api_server/temp/ArticlesAPI.t.js | 63 + news-site-v/api_server/temp/article.json | 61 + news-site-v/api_server/test/rest_api_test.js | 101 + news-site-v/api_server/test/start-server.js | 18 + news-site-v/package.json | 51 + news-site-v/public/favicon.ico | Bin 0 -> 24838 bytes news-site-v/public/index.html | 31 + news-site-v/src/App.css | 24 + news-site-v/src/App.js | 47 + news-site-v/src/api/ArticlesAPI.js | 69 + news-site-v/src/api/ArticlesAPI.test.js | 44 + news-site-v/src/components/AppNav/AppNav.js | 39 + news-site-v/src/components/Article/Article.js | 42 + .../src/components/Article/article.css | 7 + .../src/components/ArticleList/ArticleList.js | 34 + .../components/ArticleTeaser/ArticleTeaser.js | 36 + news-site-v/src/config/Sections.json | 18 + news-site-v/src/data/news.json | 2893 +++++++++++++++++ news-site-v/src/index.css | 4 + news-site-v/src/index.js | 10 + news-site-v/src/pages/AddArticlePage.js | 56 + news-site-v/src/pages/ArticlePage.js | 59 + news-site-v/src/pages/HomePage.js | 85 + news-site-v/src/pages/LoginPage.js | 29 + news-site-v/src/pages/SectionPage.js | 72 + 56 files changed, 5542 insertions(+) create mode 100644 news-site-v/.gitignore create mode 100644 news-site-v/README.md create mode 100755 news-site-v/api_server/.editorconfig create mode 100755 news-site-v/api_server/.gitignore create mode 100755 news-site-v/api_server/.jshintignore create mode 100755 news-site-v/api_server/.jshintrc create mode 100755 news-site-v/api_server/.npmignore create mode 100755 news-site-v/api_server/README.md create mode 100644 news-site-v/api_server/common/models/article.js create mode 100644 news-site-v/api_server/common/models/article.json create mode 100755 news-site-v/api_server/common/models/project.js create mode 100755 news-site-v/api_server/common/models/project.json create mode 100755 news-site-v/api_server/common/models/team.js create mode 100755 news-site-v/api_server/common/models/team.json create mode 100755 news-site-v/api_server/common/models/user.js create mode 100755 news-site-v/api_server/common/models/user.json create mode 100755 news-site-v/api_server/package.json create mode 100755 news-site-v/api_server/server/boot/authentication.js create mode 100755 news-site-v/api_server/server/boot/create-sample-models.js create mode 100755 news-site-v/api_server/server/boot/role-resolver.js create mode 100755 news-site-v/api_server/server/boot/routes.js create mode 100644 news-site-v/api_server/server/boot/sample-articles.json create mode 100755 news-site-v/api_server/server/boot/sample-models.js create mode 100755 news-site-v/api_server/server/component-config.json create mode 100755 news-site-v/api_server/server/config.json create mode 100755 news-site-v/api_server/server/datasources.json create mode 100755 news-site-v/api_server/server/middleware.json create mode 100755 news-site-v/api_server/server/model-config.json create mode 100755 news-site-v/api_server/server/server.js create mode 100755 news-site-v/api_server/server/views/index.ejs create mode 100755 news-site-v/api_server/server/views/projects.ejs create mode 100644 news-site-v/api_server/temp/ArticlesAPI.t.js create mode 100644 news-site-v/api_server/temp/article.json create mode 100755 news-site-v/api_server/test/rest_api_test.js create mode 100755 news-site-v/api_server/test/start-server.js create mode 100644 news-site-v/package.json create mode 100644 news-site-v/public/favicon.ico create mode 100644 news-site-v/public/index.html create mode 100755 news-site-v/src/App.css create mode 100644 news-site-v/src/App.js create mode 100644 news-site-v/src/api/ArticlesAPI.js create mode 100644 news-site-v/src/api/ArticlesAPI.test.js create mode 100644 news-site-v/src/components/AppNav/AppNav.js create mode 100644 news-site-v/src/components/Article/Article.js create mode 100644 news-site-v/src/components/Article/article.css create mode 100644 news-site-v/src/components/ArticleList/ArticleList.js create mode 100644 news-site-v/src/components/ArticleTeaser/ArticleTeaser.js create mode 100644 news-site-v/src/config/Sections.json create mode 100644 news-site-v/src/data/news.json create mode 100644 news-site-v/src/index.css create mode 100644 news-site-v/src/index.js create mode 100644 news-site-v/src/pages/AddArticlePage.js create mode 100644 news-site-v/src/pages/ArticlePage.js create mode 100644 news-site-v/src/pages/HomePage.js create mode 100644 news-site-v/src/pages/LoginPage.js create mode 100644 news-site-v/src/pages/SectionPage.js diff --git a/news-site-v/.gitignore b/news-site-v/.gitignore new file mode 100644 index 0000000..8491b15 --- /dev/null +++ b/news-site-v/.gitignore @@ -0,0 +1,18 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +!public/* + +# dependencies +node_modules +package-lock.json + +# testing +coverage + +# production +build + +# misc +.DS_Store +.env +npm-debug.log diff --git a/news-site-v/README.md b/news-site-v/README.md new file mode 100644 index 0000000..6d1ba7c --- /dev/null +++ b/news-site-v/README.md @@ -0,0 +1,188 @@ +# News Site Part V + +## High Level Objectives +1. Create ability to add an article to the news site +2. Add a `log in` UI (no real functionality yet) + +## Quick Review + +Let's first review a few HTTP methods: +- **GET:** Grabbing data and resources from a server. Remember that you can add filters into the query string. An example: `GET http://localhost:3001/api/articles/?filter={"where": {"section": "opinion"}}` +- **POST:** Creates new records. You will use this today to create a new article +- **PATCH / PUT:** Updating a record that already exists. +- **DELETE:** Deletes a record + +We've become kind of familiar with the `fetch` method for JS. We have been using it for mostly `GET` requests. Here is an example: + +```js +fetch('https://jsonip.com') // makes a request to this URL + .then((response) => response.json()) // after the request is made, you THEN get a Promise as a response. After I receive the Promise, I turn it into a JSON object + .catch((error) => console.log(error)) // in the case that there's an error, CATCH it and console the error +``` + +More popularly, however, we can use `fetch` to create `POST` requests. Here is an example: + +```javascript +const articleObject = { title: 'test', byline: 'byline test', abstract: 'asdf' } + +fetch('http://localhost:3001/api/articles', { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(articleObject) // whenever you make an API request, you have to stringify your request +}).then((response) => { + return response.json() +}).then((json) => { + console.log(json) +}) +``` + +There are a number of differences between the `GET` and `POST` examples with fetch. The `POST` `fetch` request includes `headers`, `method`, and `body`. `headers` contains data that the server needs to know what type of data it's about to receive. The `method` tells the server what kind of request is being made. Finally, the `body` contains the contents (i.e., `body`) of the request you are making. + +## Initial Setup + +If you'd like to use your own code from `news-site-IV`, you can copy and paste the entire `src` directory from the `news-site-IV` repo and replace the starter `src` code in this repo. + +**After copying over your source directory, run `npm run update-tests`.** This command will update a few unit tests in your `src/` directory. + +Once you've performed the steps above, run `npm install ; npm run start` - verify that no errors appear in your browser console or terminal, and that your app functions the same as it did in the last challenge. Also try running `npm run test` - you should see a single failure coming from the `ArticlesAPI.js` module. This is to be expected - the test that's failing is because the functionality the test is attempting to run hasn't been built yet - we'll be doing that next. + +## Testing Fetch +Let's test this `fetch` command in the console: + +```js +const articleObject = { title: 'test', byline: 'byline test', abstract: 'asdf' } + +fetch('http://localhost:3001/api/articles', { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(articleObject) // whenever you make an API request, you have to stringify your request +}).then((response) => { + return response.json() +}).then((json) => { + console.log(json) +}) +``` + +If you refresh the page and scroll to the bottom, you'll see our new `test` article! What if you have a bad request? Let's try passing an incomplete data set: + +```javascript +const articleObject = { byline: 'byline test', abstract: 'asdf' } + +fetch('http://localhost:3001/api/articles', { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(articleObject) // whenever you make an API request, you have to stringify your request +}).then((response) => { + return response.json() +}).then((json) => { + console.log(json) +}) +``` + +Check the console for some errors. Part of your job today is to handle these types of issues/errors. + +## Release 0: ArticlesAPI.addArticle() + +To start, let's build a function within `ArticlesAPI.js` that we can use to send article data to the API. + +Up until now, we've only used Fetch to make `GET` requests to our API (this is the default request method `fetch` uses). When reading data from an API (such as reading a list of articles), it's customary to use the GET request method. When writing/submitting data, it's customary to use the POST request method - POST allows data to be sent securely. + +To send a POST request, you will still use `fetch` by passing some additional data that will instruct it to use the POST request method. Posting data to our API would look something like this: + +```javascript +return fetch('http://localhost:3001/api/articles', { + headers: { + 'Content-Type': 'application/json' + }, + method: "POST", + body: JSON.stringify(articleObject) +}) +``` + +Note the second argument passed to `fetch` - an object of options. This object allows you to control specific aspects of the request that's made, such as headers and the request method. + +Headers allow you to pass additional information with a request. Here, we're including a header that says that we are sending JSON. Headers can also include things like authentication tokens, instructions on how to handle cached files, to name a few. + +The `method` key in the options object defines the type of request that should be made. By default, this is set to `GET` - here, we're setting it to `POST`. + +Lastly, the `body` key contains the `POST` payload - the data that will be sent to the API, and then processed and stored in a database. The value of this property should be a JSON-encoded string. + +In `ArticlesAPI.js`, you should define a new function called `addArticle`. This function should accept a single parameter - `articleObject`. This function should perform a Fetch call similar to the one above - you'll want to convert the object that's passed into the function into a JSON string, and set the `body` property of the options object (the second parameter in the Fetch call) to this JSON string. + +A new test has been added to verify this behavior - once all of your unit tests succeed, you may continue to the next section. + + +## Release 1: The Add Article Page +The Add Article Page will be used to display a form that will allow users to submit an article. Let's first begin by creating the route and the page. + +The route that should display the Add Article Page should be `/add-article` - no parameters are necessary. + +Once your page component and route are established, add a link to the Nav component that points to this page. + +Once you've added the "Add An Article" link to your AppNav.js component, verify that clicking the link redirects you to the appropriate route. + +Lastly, let's create the content that `AddArticlePage.js` should render. + +We need to render a `
`, and that form should contain 3 fields: + + 1. Title - a text input + 2. Byline - a text input + 3. Abstract - a text area + +In addition to the form fields, you'll also need a submit button. + +Consider using either `react-bootstrap` or `reactstrap` components to create these form elements. A demo form using `react-bootstrap` can be found [here](https://5c507d49471426000887a6a7--react-bootstrap.netlify.com/components/forms/), and documentation for `reactstrap` forms can be found [here](https://reactstrap.github.io/components/form/). + +Once you have the form appearing on screen, you will need to build the behavior that should occur when the form is submitted. The `` element can fire a unique event - onSubmit. Example: + + +
+ +The event object that's passed into your event handler will contain references to all of the input fields through a property called "elements". Example: + + handleFormSubmit = (event) => { + console.log(event.target.elements[0].value) // This will print out the value contained within the first input field on the form. + } + +Within your `
`'s onSubmit event handler, you will want to first construct an object that resembles the following: + + { + title: "Some value of the title input field Lorem Ipsum", + byline: "Some value of the bylien input field lorem ipsum", + abstract: "this text should come from the abstract text field" + } + +You will then want to pass it to ArticlesAPI.addArticle(), and then use the then() function and define a callback function that should be executed when the ArticlesAPI.addArticle promise/request is resolved. + + ArticlesAPI.addArticle(articleObject) + .then((json) => { + // callback function logic should appear here. + }) + +As far as the callback function logic goes, **we ultimately want to show a success message when an article is submitted.** I'll leave it up to you to determine how to best accomplish this, but your solution will likely involve using setState within this callback function - and setting some sort of indicator within state that says that the form was submitted (this.state.hasArticleBeenSubmitted, perhaps?) You can then build some conditional logic into your render function based off of this state variable that either shows the Add Article form, or the a message that should appear after an article is submitted. + +## Release 2: The Log In Page +Again, we'll want to create a new page. By now, you probably know the routine. The page component should be named LoginPage.js, and the route that should load this component should be `/login`. + +This page should also display a form. Within this form, there should be two text inputs - one for an email, and one for a password - and a submit button. + +Password fields are slightly different than text inputs (``. Password input fields hide the text that's typed into the text field, as you're probably familiar. + +If you're using `react-bootstrap`, revisit [this](https://reactstrap.github.io/components/form/) section for examples of how to create a password field using the components from that library. Or if you're using `reactstrap`, documentation can be found [here](https://reactstrap.github.io/components/form/). If you're using vanilla HTML, a password input field would look like this: `` + +After you have form UI completed, attach an event listener to the form's onSubmit event. In your event handler, simply console.log the values in the email and password fields. + +## Secondary Objectives +If you haven't noticed, there is a bit of a flaw in our AddArticle.js component - there is no error handling. If you go to the AddArticle.js page, leave all of the form fields empty, and push submit, the API request to submit the article will fail but no messaging is portrayed. + +This can be alleviated by adding a bit more logic into the `ArticlesAPI.addArticle().then()` callback function. When the required data isn't submitted, the API will respond with a JSON object that contains an "error" key. "error" is an object that contains details about the error - an error message, the fields that were invalid. If you store this data into the AddArticle.js component's state, it will be possible to `render()` information about errors when they exist. As far as the specifics, I leave that up to you. + +As always, if you finish early, go ahead and create your `functional-version` branch and refactor! + +**Look ahead:** If you really want another task, start looking into the documentation for [React's Context API](https://reactjs.org/docs/context.html). Think about how we might incorporate this now that we have a login page... diff --git a/news-site-v/api_server/.editorconfig b/news-site-v/api_server/.editorconfig new file mode 100755 index 0000000..3ee22e5 --- /dev/null +++ b/news-site-v/api_server/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/news-site-v/api_server/.gitignore b/news-site-v/api_server/.gitignore new file mode 100755 index 0000000..3c3629e --- /dev/null +++ b/news-site-v/api_server/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/news-site-v/api_server/.jshintignore b/news-site-v/api_server/.jshintignore new file mode 100755 index 0000000..ee8c771 --- /dev/null +++ b/news-site-v/api_server/.jshintignore @@ -0,0 +1,2 @@ +/client/ +/node_modules/ diff --git a/news-site-v/api_server/.jshintrc b/news-site-v/api_server/.jshintrc new file mode 100755 index 0000000..25956ef --- /dev/null +++ b/news-site-v/api_server/.jshintrc @@ -0,0 +1,28 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "eqeqeq": true, + "eqnull": true, + "immed": true, + "indent": 2, + "latedef": "nofunc", + "newcap": true, + "nonew": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": false, + "trailing": true, + "sub": true, + "maxlen": 80, + "globals" : { + /* MOCHA */ + "describe" : false, + "it" : false, + "before" : false, + "after" : false + } +} diff --git a/news-site-v/api_server/.npmignore b/news-site-v/api_server/.npmignore new file mode 100755 index 0000000..7ec7473 --- /dev/null +++ b/news-site-v/api_server/.npmignore @@ -0,0 +1,16 @@ +.idea +.project +*.sublime-* +.DS_Store +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.swp +*.swo +node_modules +coverage +*.tgz +*.xml diff --git a/news-site-v/api_server/README.md b/news-site-v/api_server/README.md new file mode 100755 index 0000000..545187f --- /dev/null +++ b/news-site-v/api_server/README.md @@ -0,0 +1,256 @@ +# loopback-example-access-control + +``` +$ git clone https://github.com/strongloop/loopback-example-access-control +$ cd loopback-example-access-control +$ npm install +$ node . +``` + +In this example, we create "Startkicker" (a basic Kickstarter-like +application) to demonstrate authentication and authorization mechanisms in +LoopBack. The application consists of four types of users: + + - `guest` + - `owner` + - `team member` + - `administrator` + +Each user type has permission to perform tasks based on their role and the +application's ACL (access control list) entries. + +## Prerequisites + +### Tutorials + +- [Getting started with LoopBack](http://loopback.io/doc/en/lb3/Getting-started-with-LoopBack.html) +- [Other tutorials and examples](http://loopback.io/doc/en/lb3/Tutorials-and-examples.html) + +### Knowledge + +- [EJS](https://github.com/visionmedia/ejs) +- [body-parser](https://github.com/expressjs/body-parser) +- [JSON](http://json.org/) +- [LoopBack models](http://docs.strongloop.com/display/LB/Defining+models) +- [LoopBack adding application logic](http://docs.strongloop.com/display/LB/Adding+application+logic) + +## Procedure + +### Create the application + +#### Application information + +- Name: `loopback-example-access-control` +- Directory to contain the project: `loopback-example-access-control` + +``` +$ lb app loopback-example-access-control +... # follow the prompts +$ cd loopback-example-access-control +``` + +### Add the models + +#### Model information +- Name: `user` + - Datasource: `db (memory)` + - Base class: `User` + - Expose via REST: `No` + - Custom plural form: *Leave blank* + - Properties + - *None* +- Name: `team` + - Datasource: `db (memory)` + - Base class: `PersistedModel` + - Expose via REST: `No` + - Custom plural form: *Leave blank* + - Properties + - `ownerId` + - Number + - Not required + - `memberId` + - Number + - Required +- Name: `project` + - Datasource: `db (memory)` + - Base class: `PersistedModel` + - Expose via REST: `Yes` + - Custom plural form: *Leave blank* + - Properties + - `name` + - String + - Not required + - `balance` + - Number + - Not required + +> No properties are required for the `user` model because we inherit them from +> the built-in `User` model by specifying it as the base class. + +``` +$ lb model user +... # follow the prompts, repeat for `team` and `project` +``` + +### Define the remote methods + +Define three remote methods in [`project.js`](https://github.com/strongloop/loopback-example-access-control/blob/master/common/models/project.js): + +- [`listProjects`](https://github.com/strongloop/loopback-example-access-control/blob/master/common/models/project.js#L2-L13) +- [`donate`](https://github.com/strongloop/loopback-example-access-control/blob/master/common/models/project.js#L15-L31) +- [`withdraw`](https://github.com/strongloop/loopback-example-access-control/blob/master/common/models/project.js#L33-54) + +### Create the model relations + +#### Model relation information + +- `user` + - has many + - `project` + - Property name for the relation: `projects` + - Custom foreign key: `ownerId` + - Require a through model: No + - `team` + - Property name for the relation: `teams` + - Custom foreign key: `ownerId` + - Require a through model: No +- `team` + - has many + - `user` + - Property name for the relation: `members` + - Custom foreign key: `memberId` + - Require a through model: No +- `project` + - belongs to + - `user` + - Property name for the relation: `user` + - Custom foreign key: `ownerId` + +### Add model instances + +Create a boot script named [`sample-models.js`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/boot/sample-models.js). + +This script does the following: + +- [Creates 3 users](/server/boot/sample-models.js#L7-L11) (`John`, `Jane`, and + `Bob`) +- [Creates project 1, sets `John` as the owner, and adds `John` and `Jane` as team + members](/server/boot/sample-models.js#L14-L29) +- [Creates project 2, sets `Jane` as the owner and solo team + member](/server/boot/sample-models.js#L33-L48) +- [Creates a role named `admin` and adds a role mapping to make `Bob` an + `admin`](/server/boot/sample-models.js#L50-L65) + +### Configure server-side views + +> LoopBack comes preconfigured with EJS out-of-box. This means we can use +> server-side templating by simply setting the proper view engine and a +> directory to store the views. + +Create a [`views` directory](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views) to store server-side templates. + +``` +$ mkdir server/views +``` + +Create [`index.ejs` in the views directory](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views/index.ejs). + +[Configure `server.js`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/server.js#L11-L20) to use server-side +templating. Remember to import the [`path`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/server.js#L4) package. + +### Add routes + +Create [`routes.js`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/boot/routes.js). This script does the following: + +- Sets the [`GET /` route to render `index.ejs`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views/index.ejs) +- Sets the [`GET /projects` route to render `projects.ejs`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views/projects.ejs) +- Sets the [`POST /projects` route to to render `projects.ejs` when credentials are valid](server/views/projects.ejs) and [renders `index.ejs`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views/index.ejs) when credentials are invalid +- Sets the [`GET /logout` route to log the user out](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views/routes.js) + +> When you log in sucessfully, `projects.html` is rendered with the authenticated user's access token embedded into each link. + +### Create the views + +Create the [`views` directory](https://github.com/strongloop/loopback-example-access-control/tree/master/server/views) to store views. + +In this directory, create [`index.ejs`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views/index.ejs) and [`projects.ejs`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/views/projects.ejs). + +### Create a role resolver + +Create [`role-resolver.js`](https://github.com/strongloop/loopback-example-access-control/blob/master/server/boot/role-resolver.js). + +> This file checks if the context relates to the project model and if the +> request maps to a user. If these two requirements are not met, the request is +> denied. Otherwise, we check to see if the user is a team member and process +> the request accordingly. + +### Create ACL entries + +> ACLs are used to restrict access to application REST endpoints. + +#### ACL information + +- Deny access to all project REST endpoints + - Select the model to apply the ACL entry to: `(all existing models)` + - Select the ACL scope: `All methods and properties` + - Select the access type: `All (match all types)` + - Select the role: `All users` + - Select the permission to apply: `Explicitly deny access` +- Allow unrestricted access to `GET /api/projects/listProjects` + - Select the model to apply the ACL entry to: `project` + - Select the ACL scope: `A single method` + - Enter the method name: `listProjects` + - Select the role: `All users` + - Select the permission to apply: `Explicitly grant access` +- Only allow admin unrestricted access to `GET /api/projects` + - Select the model to apply the ACL entry to: `project` + - Select the ACL scope: `A single method` + - Enter the method name: `find` + - Select the role: `other` + - Enter the role name: `admin` + - Select the permission to apply: `Explicitly grant access` +- Only allow team members access to `GET /api/projects/:id` + - Select the model to apply the ACL entry to: `project` + - Select the ACL scope: `A single method` + - Enter the method name: `findById` + - Select the role: `other` + - Enter the role name: `teamMember` + - Select the permission to apply: `Explicitly grant access` +- Allow authenticated users to access `POST /api/projects/donate` + - Select the model to apply the ACL entry to: `project` + - Select the ACL scope: `A single method` + - Enter the method name: `donate` + - Select the role: `Any authenticated user` + - Select the permission to apply: `Explicitly grant access` +- Allow owners access to `POST /api/projects/withdraw` + - Select the model to apply the ACL entry to: `project` + - Select the ACL scope: `A single method` + - Enter the method name: `withdraw` + - Select the role: `The user owning the object` + - Select the permission to apply: `Explicitly grant access` + +``` +$ lb acl +# follow the prompts, repeat for each ACL listed above +``` + +### Try the application + +Start the server (`node .`) and open [`localhost:3000`](http://localhost:3000) in your browser to view the app. You will see logins and explanations related to each user type we created: + +- Guest `Guest` + - Role = $everyone, $unauthenticated + - Has access to the "List projects" function, but none of the others +- John `Project owner` + - Role = $everyone, $authenticated, teamMember, $owner + - Can access all functions except "View all projects" +- Jane `Project team member` + - Role = $everyone, $authenticated, teamMember + - Can access all functions except "View all projects" and "Withdraw" +- Bob `Administator` + - Role = $everyone, $authenticated, admin + - Can access all functions except "Withdraw" + +--- + +[More LoopBack examples](https://loopback.io/doc/en/lb3/Tutorials-and-examples.html) diff --git a/news-site-v/api_server/common/models/article.js b/news-site-v/api_server/common/models/article.js new file mode 100644 index 0000000..93716b4 --- /dev/null +++ b/news-site-v/api_server/common/models/article.js @@ -0,0 +1,12 @@ +'use strict'; + +const RemoteRouting = require('loopback-remote-routing'); + +module.exports = function(Article) { + RemoteRouting(Article, {only: [ + '@find', + '@findById', + '@create' + ]}) + +}; diff --git a/news-site-v/api_server/common/models/article.json b/news-site-v/api_server/common/models/article.json new file mode 100644 index 0000000..b7b1a06 --- /dev/null +++ b/news-site-v/api_server/common/models/article.json @@ -0,0 +1,33 @@ +{ + "name": "article", + "base": "PersistedModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": { + "title": { + "type": "string", + "required": true + }, + "created_date": { + "type": "date", + "required": true, + "default": "$now" + }, + "byline": { + "type": "string" + }, + "abstract": { + "type": "string", + "required": true + }, + "image": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/news-site-v/api_server/common/models/project.js b/news-site-v/api_server/common/models/project.js new file mode 100755 index 0000000..b54ec13 --- /dev/null +++ b/news-site-v/api_server/common/models/project.js @@ -0,0 +1,60 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +module.exports = function(Project) { + // listProjects + Project.listProjects = function(cb) { + Project.find({ + fields: { + balance: false + } + }, cb); + }; + Project.remoteMethod('listProjects', { + returns: {arg: 'projects', type: 'array'}, + http: {path:'/list-projects', verb: 'get'} + }); + + // donate + Project.donate = function(id, amount, cb) { + Project.findById(id, function(err, project) { + if (err) return cb(err); + + project.balance += amount; + project.save(); + + cb(null, true); + }); + }; + Project.remoteMethod('donate', { + accepts: [ + {arg: 'id', type: 'number'}, + {arg: 'amount', type: 'number'}, + ], + returns: {arg: 'success', type: 'boolean'}, + http: {path:'/donate', verb: 'post'} + }); + + // withdraw + Project.withdraw = function(id, amount, cb) { + Project.findById(id, function(err, project) { + if (err) return cb(err); + + project.balance = project.balance >= amount ? + project.balance - amount : 0; + project.save(); + + cb(null, true); + }); + }; + Project.remoteMethod('withdraw', { + accepts: [ + {arg: 'id', type: 'number'}, + {arg: 'amount', type: 'number'}, + ], + returns: {arg: 'success', type: 'boolean'}, + http: {path:'/withdraw', verb: 'post'} + }); +}; diff --git a/news-site-v/api_server/common/models/project.json b/news-site-v/api_server/common/models/project.json new file mode 100755 index 0000000..40626a8 --- /dev/null +++ b/news-site-v/api_server/common/models/project.json @@ -0,0 +1,65 @@ +{ + "name": "project", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "balance": { + "type": "number" + } + }, + "validations": [], + "relations": { + "user": { + "type": "belongsTo", + "model": "user", + "foreignKey": "ownerId" + } + }, + "acls": [ + { + "accessType": "*", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "DENY" + }, + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW", + "property": "listProjects" + }, + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "admin", + "permission": "ALLOW", + "property": "find" + }, + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "teamMember", + "permission": "ALLOW", + "property": "findById" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW", + "property": "donate" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$owner", + "permission": "ALLOW", + "property": "withdraw" + } + ], + "methods": [] +} diff --git a/news-site-v/api_server/common/models/team.js b/news-site-v/api_server/common/models/team.js new file mode 100755 index 0000000..148448c --- /dev/null +++ b/news-site-v/api_server/common/models/team.js @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +module.exports = function(Team) { + +}; diff --git a/news-site-v/api_server/common/models/team.json b/news-site-v/api_server/common/models/team.json new file mode 100755 index 0000000..0abfbd9 --- /dev/null +++ b/news-site-v/api_server/common/models/team.json @@ -0,0 +1,25 @@ +{ + "name": "team", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "ownerId": { + "type": "string", + "required": true + }, + "memberId": { + "type": "string", + "required": true + } + }, + "validations": [], + "relations": { + "members": { + "type": "hasMany", + "model": "user", + "foreignKey": "memberId" + } + }, + "acls": [], + "methods": [] +} diff --git a/news-site-v/api_server/common/models/user.js b/news-site-v/api_server/common/models/user.js new file mode 100755 index 0000000..20cf31b --- /dev/null +++ b/news-site-v/api_server/common/models/user.js @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +module.exports = function(User) { + +}; diff --git a/news-site-v/api_server/common/models/user.json b/news-site-v/api_server/common/models/user.json new file mode 100755 index 0000000..a35c9da --- /dev/null +++ b/news-site-v/api_server/common/models/user.json @@ -0,0 +1,21 @@ +{ + "name": "user", + "base": "User", + "idInjection": true, + "properties": {}, + "validations": [], + "relations": { + "projects": { + "type": "hasMany", + "model": "project", + "foreignKey": "ownerId" + }, + "teams": { + "type": "hasMany", + "model": "team", + "foreignKey": "ownerId" + } + }, + "acls": [], + "methods": [] +} diff --git a/news-site-v/api_server/package.json b/news-site-v/api_server/package.json new file mode 100755 index 0000000..027a3a9 --- /dev/null +++ b/news-site-v/api_server/package.json @@ -0,0 +1,36 @@ +{ + "name": "news-site", + "version": "1.0.0", + "main": "server/server.js", + "engines": { + "node": ">=4" + }, + "scripts": { + "lint": "eslint .", + "start": "node .", + "posttest": "npm run lint && nsp check" + }, + "dependencies": { + "compression": "^1.7.4", + "cors": "^2.8.5", + "helmet": "^4.4.1", + "loopback": "^3.28.0", + "loopback-boot": "^3.3.1", + "loopback-component-explorer": "^6.5.1", + "loopback-connector-postgresql": "^5.3.0", + "loopback-remote-routing": "^1.5.2", + "serve-favicon": "^2.5.0", + "strong-error-handler": "^4.0.0" + }, + "devDependencies": { + "eslint": "^7.23.0", + "eslint-config-loopback": "^13.1.0", + "nsp": "^3.2.1" + }, + "repository": { + "type": "", + "url": "" + }, + "license": "UNLICENSED", + "description": "news-site" +} diff --git a/news-site-v/api_server/server/boot/authentication.js b/news-site-v/api_server/server/boot/authentication.js new file mode 100755 index 0000000..8e166e4 --- /dev/null +++ b/news-site-v/api_server/server/boot/authentication.js @@ -0,0 +1,9 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +module.exports = function enableAuthentication(server) { + // enable authentication + server.enableAuth(); +}; diff --git a/news-site-v/api_server/server/boot/create-sample-models.js b/news-site-v/api_server/server/boot/create-sample-models.js new file mode 100755 index 0000000..edca581 --- /dev/null +++ b/news-site-v/api_server/server/boot/create-sample-models.js @@ -0,0 +1,25 @@ +'use strict'; + +var async = require('async'); +module.exports = function(app) { + //data sources + var db = app.dataSources.db; + + //create all models + async.parallel({ + articles: async.apply(createArticles) + }, function(err, results) { + if (err) throw err; + createArticles(results.articles, function(err) { + console.log('> models created sucessfully'); + }); + }); + //create Articles + function createArticles(cb) { + db.automigrate('article', function(err) { + if (err) return cb(err); + var article = app.models.article; + article.create(require('./sample-articles.json'), cb); + }); + } +}; diff --git a/news-site-v/api_server/server/boot/role-resolver.js b/news-site-v/api_server/server/boot/role-resolver.js new file mode 100755 index 0000000..5ce7b46 --- /dev/null +++ b/news-site-v/api_server/server/boot/role-resolver.js @@ -0,0 +1,46 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +module.exports = function(app) { + var Role = app.models.Role; + + Role.registerResolver('teamMember', function(role, context, cb) { + function reject() { + process.nextTick(function() { + cb(null, false); + }); + } + + // if the target model is not project + if (context.modelName !== 'project') { + return reject(); + } + + // do not allow anonymous users + var userId = context.accessToken.userId; + if (!userId) { + return reject(); + } + + // check if userId is in team table for the given project id + context.model.findById(context.modelId, function(err, project) { + if (err || !project) + return reject(); + + var Team = app.models.Team; + Team.count({ + ownerId: project.ownerId, + memberId: userId + }, function(err, count) { + if (err) { + console.log(err); + return cb(null, false); + } + + cb(null, count > 0); // true = is a team member + }); + }); + }); +}; diff --git a/news-site-v/api_server/server/boot/routes.js b/news-site-v/api_server/server/boot/routes.js new file mode 100755 index 0000000..e24ab9d --- /dev/null +++ b/news-site-v/api_server/server/boot/routes.js @@ -0,0 +1,52 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +module.exports = function(app) { + var router = app.loopback.Router(); + + router.get('/', function(req, res) { + res.render('index', { + loginFailed: false + }); + }); + + router.get('/projects', function(req, res) { + res.render('projects'); + }); + + router.post('/projects', function(req, res) { + var email = req.body.email; + var password = req.body.password; + + app.models.User.login({ + email: email, + password: password + }, 'user', function(err, token) { + if (err) + return res.render('index', { + email: email, + password: password, + loginFailed: true + }); + + token = token.toJSON(); + + res.render('projects', { + username: token.user.username, + accessToken: token.id + }); + }); + }); + + router.get('/logout', function(req, res) { + var AccessToken = app.models.AccessToken; + var token = new AccessToken({id: req.query['access_token']}); + token.destroy(); + + res.redirect('/'); + }); + + app.use(router); +}; diff --git a/news-site-v/api_server/server/boot/sample-articles.json b/news-site-v/api_server/server/boot/sample-articles.json new file mode 100644 index 0000000..c42c44f --- /dev/null +++ b/news-site-v/api_server/server/boot/sample-articles.json @@ -0,0 +1,322 @@ +[ + { + "abstract": "Nostrum aut ea atque.", + "byline": "By DAVID ZUCCHINO", + "created_date": "2017-02-10T03:00:25-05:00", + "image": "https://static01.nyt.com/images/2017/02/07/world/falluja1/falluja1-thumbStandard.jpg", + "section": "world", + "title": "Nostrum aut ea atque." + }, + { + "abstract": "Praesentium dolorum nemo deserunt.", + "byline": "Photographs and Text by RUKMINI CALLIMACHI", + "created_date": "2017-02-09T17:14:52-05:00", + "image": "https://static01.nyt.com/images/2017/02/09/world/middleeast/10Mosul-slide-4RH2/10Mosul-slide-4RH2-thumbStandard.jpg", + "section": "world", + "title": "Praesentium dolorum nemo deserunt." + }, + { + "abstract": "Voluptatem ut et aut officiis in est rerum.", + "byline": "By GAIA PIANIGIANI", + "created_date": "2017-02-10T17:36:51-05:00", + "image": "https://static01.nyt.com/images/2017/02/09/world/00calabria1/00calabria1-thumbStandard.jpg", + "section": "world", + "title": "Voluptatem ut et aut officiis in est rerum." + }, + { + "abstract": "Et ab similique minima.", + "byline": "By ALISON SMALE", + "created_date": "2017-02-10T12:29:46-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11Germany1/11Germany1-thumbStandard.jpg", + "section": "business", + "title": "Et ab similique minima." + }, + { + "abstract": "Tenetur tenetur repudiandae dicta delectus amet quia deserunt.", + "byline": "By BENOÎT MORENNE", + "created_date": "2017-02-10T07:35:42-05:00", + "image": null, + "section": "opinion", + "title": "Tenetur tenetur repudiandae dicta delectus amet quia deserunt." + }, + { + "abstract": "Sit natus incidunt culpa earum molestiae.", + "byline": "By MICHAEL FORSYTHE and PAUL MOZUR", + "created_date": "2017-02-10T15:56:43-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11Hongkong1/11Hongkong1-thumbStandard.jpg", + "section": "national", + "title": "Sit natus incidunt culpa earum molestiae." + }, + { + "abstract": "Praesentium voluptas ut temporibus.", + "byline": "By FELIPE VILLAMOR", + "created_date": "2017-02-10T12:49:30-05:00", + "image": "https://static01.nyt.com/images/2017/02/08/world/asia/08PHILIPPINES-1/08PHILIPPINES-1-thumbStandard.jpg", + "section": "business", + "title": "Praesentium voluptas ut temporibus." + }, + { + "abstract": "Voluptate sed dolores reprehenderit.", + "byline": "By PAULINA VILLEGAS and ELISABETH MALKIN", + "created_date": "2017-02-10T18:40:00-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11mexico1/11mexico1-thumbStandard.jpg", + "section": "business", + "title": "Voluptate sed dolores reprehenderit." + }, + { + "abstract": "Maiores nihil laboriosam ut quia aliquam neque velit.", + "byline": "By SIMON ROMERO", + "created_date": "2017-02-10T09:30:24-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11brazil1/11brazil1-thumbStandard.jpg", + "section": "business", + "title": "Maiores nihil laboriosam ut quia aliquam neque velit." + }, + { + "abstract": "Perferendis rerum distinctio.", + "byline": "By DIONNE SEARCEY and FRANCOIS ESSOMBA", + "created_date": "2017-02-10T13:37:22-05:00", + "image": "https://static01.nyt.com/images/2017/02/08/world/00internetshutdown1/00internetshutdown1-thumbStandard.jpg", + "section": "business", + "title": "Perferendis rerum distinctio." + }, + { + "abstract": "Sequi nemo alias sed necessitatibus beatae quia facilis atque.", + "byline": "By JEFFREY GETTLEMAN", + "created_date": "2017-02-09T14:43:53-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/africa/10kenya1/10kenya1-thumbStandard.jpg", + "section": "business", + "title": "Sequi nemo alias sed necessitatibus beatae quia facilis atque." + }, + { + "abstract": "Eligendi non enim voluptatum eum maxime perspiciatis sed voluptatem.", + "byline": "By JOANNA KLEIN", + "created_date": "2017-02-10T18:08:38-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/science/11tb-amazon01/11tb-amazon01-thumbStandard.jpg", + "section": "national", + "title": "Eligendi non enim voluptatum eum maxime perspiciatis sed voluptatem." + }, + { + "abstract": "Quo eos laudantium vitae.", + "byline": "By THE ASSOCIATED PRESS", + "created_date": "2017-02-10T17:16:00-05:00", + "image": null, + "section": "national", + "title": "Quo eos laudantium vitae." + }, + { + "abstract": "Eum consequuntur voluptas.", + "byline": "By MICHAEL SCHWIRTZ", + "created_date": "2017-02-10T16:47:41-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11Ukraine1/11Ukraine1-thumbStandard.jpg", + "section": "world", + "title": "Eum consequuntur voluptas." + }, + { + "abstract": "Repellendus est facere modi nihil ratione.", + "byline": "By CHRISTINE HAUSER", + "created_date": "2017-02-10T15:02:55-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/middleeast/11camel2_xp/11camel2_xp-thumbStandard.jpg", + "section": "opinion", + "title": "Repellendus est facere modi nihil ratione." + }, + { + "abstract": "Corrupti dolorem quo non excepturi sit reiciendis sint.", + "byline": "By PETER BAKER", + "created_date": "2017-02-10T11:44:16-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11Settlements/11Settlements-thumbStandard.jpg", + "section": "business", + "title": "Corrupti dolorem quo non excepturi sit reiciendis sint." + }, + { + "abstract": "Voluptate et vero.", + "byline": "By JULIE HIRSCHFELD DAVIS and PETER BAKER", + "created_date": "2017-02-10T09:35:46-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/us/10abe/10abe-thumbStandard.jpg", + "section": "business", + "title": "Voluptate et vero." + }, + { + "abstract": "Quis sit dolore.", + "byline": "By ALAN WONG", + "created_date": "2017-02-10T09:18:56-05:00", + "image": null, + "section": "opinion", + "title": "Quis sit dolore." + }, + { + "abstract": "Quisquam necessitatibus et aut excepturi laborum.", + "byline": "By JANE PERLEZ", + "created_date": "2017-02-10T07:43:27-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11CHINA-1/11CHINA-1-thumbStandard.jpg", + "section": "national", + "title": "Quisquam necessitatibus et aut excepturi laborum." + }, + { + "abstract": "Deserunt et nemo.", + "byline": "By THOMAS ERDBRINK", + "created_date": "2017-02-10T07:04:30-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11Iran2/11Iran2-thumbStandard-v2.jpg", + "section": "world", + "title": "Deserunt et nemo." + }, + { + "abstract": "Reiciendis est est sunt maiores.", + "byline": "By AURELIEN BREEDEN", + "created_date": "2017-02-10T07:01:58-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11Montpellier/11Montpellier-thumbStandard.jpg", + "section": "national", + "title": "Reiciendis est est sunt maiores." + }, + { + "abstract": "Dolores velit quasi quis amet.", + "byline": "By JASON HOROWITZ", + "created_date": "2017-02-10T05:00:35-05:00", + "image": "https://static01.nyt.com/images/2017/02/12/us/21Evola1/10Evola1-thumbStandard.jpg", + "section": "opinion", + "title": "Dolores velit quasi quis amet." + }, + { + "abstract": "Tempora harum quae dolores similique accusamus rerum porro et ut.", + "byline": "By MEGAN SPECIA and YARA BISHARA", + "created_date": "2017-02-10T03:22:14-05:00", + "image": null, + "section": "national", + "title": "Tempora harum quae dolores similique accusamus rerum porro et ut." + }, + { + "abstract": "Officiis reprehenderit dolores maiores veniam iste inventore et quasi.", + "byline": "By GERRY MULLANY", + "created_date": "2017-02-10T01:10:33-05:00", + "image": "https://static01.nyt.com/images/2017/02/11/world/11whales-5/11whales-5-thumbStandard.jpg", + "section": "national", + "title": "Officiis reprehenderit dolores maiores veniam iste inventore et quasi." + }, + { + "abstract": "Labore veniam nostrum sit necessitatibus esse ad dolor.", + "byline": "By SAM ROBERTS", + "created_date": "2017-02-09T19:17:18-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10rosling-obit-1/10rosling-obit-1-thumbStandard.jpg", + "section": "world", + "title": "Labore veniam nostrum sit necessitatibus esse ad dolor." + }, + { + "abstract": "Repudiandae eum voluptatem qui ut quo id voluptatibus et.", + "byline": "By DECLAN WALSH and NOUR YOUSSEF", + "created_date": "2017-02-09T17:04:18-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10egypt/10egypt-thumbStandard.jpg", + "section": "opinion", + "title": "Repudiandae eum voluptatem qui ut quo id voluptatibus et." + }, + { + "abstract": "Voluptatem rem voluptatem quasi.", + "byline": "By MOTOKO RICH", + "created_date": "2017-02-09T16:31:03-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/asia/10japan3/10japan3-thumbStandard.jpg", + "section": "business", + "title": "Voluptatem rem voluptatem quasi." + }, + { + "abstract": "Nemo adipisci non veniam.", + "byline": "By AURELIEN BREEDEN", + "created_date": "2017-02-09T15:49:31-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10eiffel1/10eiffel1-thumbStandard.jpg", + "section": "world", + "title": "Nemo adipisci non veniam." + }, + { + "abstract": "Doloremque ut iure.", + "byline": "By NIDA NAJAR", + "created_date": "2017-02-09T15:00:06-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10Maldives/10Maldives-thumbStandard.jpg", + "section": "world", + "title": "Doloremque ut iure." + }, + { + "abstract": "Consequatur omnis omnis.", + "byline": "By ANNE BARNARD", + "created_date": "2017-02-09T14:10:33-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10syria1/10syria1-thumbStandard.jpg", + "section": "business", + "title": "Consequatur omnis omnis." + }, + { + "abstract": "Nesciunt laborum dolor porro quibusdam ea atque impedit.", + "byline": "By NIKI KITSANTONIS", + "created_date": "2017-02-09T13:20:45-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10Migrants1/10Migrants1-thumbStandard.jpg", + "section": "world", + "title": "Nesciunt laborum dolor porro quibusdam ea atque impedit." + }, + { + "abstract": "Exercitationem quae unde molestias molestiae dolorum doloremque ipsam sit.", + "byline": "By FRANCOIS ESSOMBA and DIONNE SEARCEY", + "created_date": "2017-02-09T13:06:28-05:00", + "image": null, + "section": "world", + "title": "Exercitationem quae unde molestias molestiae dolorum doloremque ipsam sit." + }, + { + "abstract": "Et esse nihil adipisci qui necessitatibus maiores sit.", + "byline": "By PETER BAKER and MARK LANDLER", + "created_date": "2017-02-09T12:51:59-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10diplo5-web/10diplo5-web-thumbStandard.jpg", + "section": "world", + "title": "Et esse nihil adipisci qui necessitatibus maiores sit." + }, + { + "abstract": "Reiciendis inventore quam est.", + "byline": "By DAN BILEFSKY", + "created_date": "2017-02-09T12:39:20-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10Dutch1/10Dutch1-thumbStandard.jpg", + "section": "national", + "title": "Reiciendis inventore quam est." + }, + { + "abstract": "Possimus aut sit velit qui recusandae.", + "byline": "By KIMIKO de FREYTAS-TAMURA", + "created_date": "2017-02-09T12:36:49-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/arts/10darcy_xp/10darcy_xp-thumbStandard.jpg", + "section": "world", + "title": "Possimus aut sit velit qui recusandae." + }, + { + "abstract": "Distinctio similique eius quod nihil in magnam.", + "byline": "By MICHAEL R. GORDON", + "created_date": "2017-02-09T12:17:23-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/us/10military/10military-thumbStandard.jpg", + "section": "business", + "title": "Distinctio similique eius quod nihil in magnam." + }, + { + "abstract": "Dolorem aut expedita ut.", + "byline": "By ALISON SMALE", + "created_date": "2017-02-09T11:41:41-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10Germany1/10Germany1-thumbStandard.jpg", + "section": "national", + "title": "Dolorem aut expedita ut." + }, + { + "abstract": "Eius eligendi natus quia ea numquam et ex qui doloremque.", + "byline": "By MARTIN de BOURMONT", + "created_date": "2017-02-09T09:30:23-05:00", + "image": "https://static01.nyt.com/images/2017/01/18/world/18Roma1/18Roma1-thumbStandard.jpg", + "section": "world", + "title": "Eius eligendi natus quia ea numquam et ex qui doloremque." + }, + { + "abstract": "Non est deleniti consectetur.", + "byline": "By THOMAS ERDBRINK", + "created_date": "2017-02-09T08:33:51-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10iran-1486658636911/10iran-1486658636911-thumbStandard.jpg", + "section": "business", + "title": "Non est deleniti consectetur." + }, + { + "abstract": "Labore dicta quis maxime ipsum.", + "byline": "By MARK LANDLER and MICHAEL FORSYTHE", + "created_date": "2017-02-09T05:47:54-05:00", + "image": "https://static01.nyt.com/images/2017/02/10/world/10chinatrump/10chinatrump-thumbStandard.jpg", + "section": "business", + "title": "Labore dicta quis maxime ipsum." + } +] \ No newline at end of file diff --git a/news-site-v/api_server/server/boot/sample-models.js b/news-site-v/api_server/server/boot/sample-models.js new file mode 100755 index 0000000..b52cf82 --- /dev/null +++ b/news-site-v/api_server/server/boot/sample-models.js @@ -0,0 +1,80 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +module.exports = function(app) { + var User = app.models.user; + var Role = app.models.Role; + var RoleMapping = app.models.RoleMapping; + var Team = app.models.Team; + + User.create([ + {username: 'John', email: 'john@doe.com', password: 'opensesame'}, + {username: 'Jane', email: 'jane@doe.com', password: 'opensesame'}, + {username: 'Bob', email: 'bob@projects.com', password: 'opensesame'} + ], function(err, users) { + if (err) throw err; + + // console.log('Created users:', users); + + // create project 1 and make john the owner + users[0].projects.create({ + name: 'project1', + balance: 100 + }, function(err, project) { + if (err) throw err; + + // console.log('Created project:', project); + + // add team members + Team.create([ + {ownerId: project.ownerId, memberId: users[0].id}, + {ownerId: project.ownerId, memberId: users[1].id} + ], function(err, team) { + if (err) throw err; + + // console.log('Created team:', team); + }); + }); + + //create project 2 and make jane the owner + users[1].projects.create({ + name: 'project2', + balance: 100 + }, function(err, project) { + if (err) throw err; + + // console.log('Created project:', project); + + //add team members + Team.create({ + ownerId: project.ownerId, + memberId: users[1].id + }, function(err, team) { + if (err) throw err; + + // console.log('Created team:', team); + }); + }); + + //create the admin role + Role.create({ + name: 'admin' + }, function(err, role) { + if (err) throw err; + + // console.log('Created role:', role); + + //make bob an admin + role.principals.create({ + principalType: RoleMapping.USER, + principalId: users[2].id + }, function(err, principal) { + if (err) throw err; + + // console.log('Created principal:', principal); + }); + }); + }); +}; diff --git a/news-site-v/api_server/server/component-config.json b/news-site-v/api_server/server/component-config.json new file mode 100755 index 0000000..f36959a --- /dev/null +++ b/news-site-v/api_server/server/component-config.json @@ -0,0 +1,5 @@ +{ + "loopback-component-explorer": { + "mountPath": "/explorer" + } +} diff --git a/news-site-v/api_server/server/config.json b/news-site-v/api_server/server/config.json new file mode 100755 index 0000000..2603582 --- /dev/null +++ b/news-site-v/api_server/server/config.json @@ -0,0 +1,23 @@ +{ + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3001, + "legacyExplorer": false, + "remoting": { + "context": false, + "rest": { + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false, + "handleErrors": false + } +} diff --git a/news-site-v/api_server/server/datasources.json b/news-site-v/api_server/server/datasources.json new file mode 100755 index 0000000..d6caf56 --- /dev/null +++ b/news-site-v/api_server/server/datasources.json @@ -0,0 +1,6 @@ +{ + "db": { + "name": "db", + "connector": "memory" + } +} diff --git a/news-site-v/api_server/server/middleware.json b/news-site-v/api_server/server/middleware.json new file mode 100755 index 0000000..6ace3f7 --- /dev/null +++ b/news-site-v/api_server/server/middleware.json @@ -0,0 +1,34 @@ +{ + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + } + }, + "session": { + }, + "auth": { + }, + "parse": { + }, + "routes": { + "loopback#rest": { + "paths": ["${restApiRoot}"] + } + }, + "files": { + }, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "strong-error-handler": {} + } +} diff --git a/news-site-v/api_server/server/model-config.json b/news-site-v/api_server/server/model-config.json new file mode 100755 index 0000000..471d517 --- /dev/null +++ b/news-site-v/api_server/server/model-config.json @@ -0,0 +1,46 @@ +{ + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ] + }, + "User": { + "dataSource": "db", + "public": false + }, + "AccessToken": { + "dataSource": "db", + "public": false + }, + "ACL": { + "dataSource": "db", + "public": false + }, + "RoleMapping": { + "dataSource": "db", + "public": false + }, + "Role": { + "dataSource": "db", + "public": false + }, + "article": { + "dataSource": "db", + "public": true + }, + "user": { + "dataSource": "db", + "public": true + }, + "team": { + "dataSource": "db", + "public": false + }, + "project": { + "dataSource": "db", + "public": true + } +} diff --git a/news-site-v/api_server/server/server.js b/news-site-v/api_server/server/server.js new file mode 100755 index 0000000..9c1cf8e --- /dev/null +++ b/news-site-v/api_server/server/server.js @@ -0,0 +1,44 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +var bodyParser = require('body-parser'); +var boot = require('loopback-boot'); +var loopback = require('loopback'); +var path = require('path'); + +var app = module.exports = loopback(); + +app.middleware('initial', bodyParser.urlencoded({ extended: true })); + +app.set('view engine', 'ejs'); // LoopBack comes with EJS out-of-box +app.set('json spaces', 2); // format json responses for easier viewing + +// must be set to serve views properly when starting the app via `slc run` from +// the project root +app.set('views', path.resolve(__dirname, 'views')); + +app.start = function() { + // retrieve port from config.json + const port = app.get('port'); + // start the web server + return app.listen(port, function() { + app.emit('started'); + var baseUrl = app.get('url').replace(/\/$/, ''); + console.log('Web server listening at: %s', baseUrl); + if (app.get('loopback-component-explorer')) { + var explorerPath = app.get('loopback-component-explorer').mountPath; + console.log('Browse your REST API at %s%s', baseUrl, explorerPath); + } + }); +}; + +// Bootstrap the application, configure models, datasources and middleware. +boot(app, __dirname, function(err) { + if (err) throw err; + + // start the server if `$ node server.js` + if (require.main === module) + app.start(); +}); \ No newline at end of file diff --git a/news-site-v/api_server/server/views/index.ejs b/news-site-v/api_server/server/views/index.ejs new file mode 100755 index 0000000..c0d7542 --- /dev/null +++ b/news-site-v/api_server/server/views/index.ejs @@ -0,0 +1,67 @@ + + + + + LoopBack example access control + + +

Startkicker

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% if (loginFailed) { %> + + + + <% } %> +
LoginUser nameUser typeRole ($ = built-in role)
GuestGuest$unauthenticated, $everyone
+ +
+
+ + +
JohnOwner (of project 1)$owner, teamMember, $authenticated, $everyone
+
+
+
+ +
+
JaneTeam member (of project 1)teamMember, $authenticated, $everyone
+
+
+
+ +
+
BobAdministratoradmin, $authenticated, $everyone
+ Login failed, please try again +
+ + diff --git a/news-site-v/api_server/server/views/projects.ejs b/news-site-v/api_server/server/views/projects.ejs new file mode 100755 index 0000000..22b3bc2 --- /dev/null +++ b/news-site-v/api_server/server/views/projects.ejs @@ -0,0 +1,100 @@ + + + + + LoopBack example access control + + +

Startkicker<% if (typeof username !== 'undefined') { %> - Welcome <%= + username %><% } %>

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FunctionDescriptionEndpointPermissionsRolesInformation
List projectsAnyone can list public project info (no balance property)GET /api/projects/list-projects$everyone, $unauthenticated, $authenticatedGuest, team member, owner, administratorThis endpoint is a remote method with a static ACL set to allow + access for all users (using the built-in role $everyone).
View all + projectsOnly administrators can view all projectsGET /api/projectsadminAdministatorThis REST endpoint is generated from the `slc loopback:model` + command. We create a custom role named "admin" and a role mapping to + set Bob as a "admin". We then apply the ACL to restrict access to only + admins via the `slc loopback:acl` command.
Show balance for + project 1Shows all fields for project 1GET /api/projects/1teamMember, $ownerTeam member, ownerAlso a built-in REST endpoint generated from the `slc + loopback:model` command. We register a custom role resolver here and + use the provided access token to figure out if the request is from a + team member and provide or deny access accordingly.
+
+
Donate money (default $100) to project 1POST /api/projects/donate$authenticatedTeam member, owner, adminA remote method that takes an argument (the amount of money to + donate). We add an ACL that restricts access to any authenticated + user (guests are not allowed to donate).
+
+
Withdraw money (default $100) from project 1POST /api/projects/withdraw$ownerownerAnother remote method similar to donate, but substracts money from + the balance instead of adding to it. Only the project owner is allowed + to withdraw. We enforce this using `slc loopback:acl` to generate an + ACL restricting access to $owner.
Log + out
+ + diff --git a/news-site-v/api_server/temp/ArticlesAPI.t.js b/news-site-v/api_server/temp/ArticlesAPI.t.js new file mode 100644 index 0000000..cfdc2a6 --- /dev/null +++ b/news-site-v/api_server/temp/ArticlesAPI.t.js @@ -0,0 +1,63 @@ +import { + fetchArticleByID, + fetchArticles, + fetchArticlesBySection, + addArticle, +} from './ArticlesAPI'; +import fetchMock from 'fetch-mock'; +require('isomorphic-fetch'); + +afterEach(() => { + fetchMock.restore(); +}) + +it('calls fetchArticleByID(1)', (done) => { + fetchMock.get('http://localhost:3001/api/articles/1', { success: true }); + return fetchArticleByID(1) + .then((json) => { + expect(json.success).toEqual(true); + done(); + }) + .catch((err) => { + throw new Error('Call failed'); + });; +}); + +it('calls fetchArticles()', (done) => { + fetchMock.get('http://localhost:3001/api/articles', { success: true }); + return fetchArticles() + .then((json) => { + expect(json.success).toEqual(true); + done(); + }) + .catch((err) => { + throw new Error('Call failed'); + });; +}); + +it('calls fetchArticlesBySection(\'opinion\')', (done) => { + fetchMock.get('http://localhost:3001/api/articles?filter={"where":{"section":"opinion"}}', { success: true }); + return fetchArticlesBySection('opinion') + .then((json) => { + expect(json.success).toEqual(true); + done(); + }) + .catch((err) => { + throw new Error('Call failed'); + }); +}); + +it('submits an article by calling addArticle()', (done) => { + const request = fetchMock.post('http://localhost:3001/api/articles', { success: true }); + const articleObject = { title: 'test', byline: 'title', abstract: 'adsf' }; + return addArticle(articleObject) + .then((json) => { + const requestBody = request._calls[0][1].body; + expect(JSON.parse(requestBody)).toEqual(articleObject); + expect(json.ok).toEqual(true); + done(); + }) + .catch((err) => { + throw new Error('Call failed'); + }); +}); diff --git a/news-site-v/api_server/temp/article.json b/news-site-v/api_server/temp/article.json new file mode 100644 index 0000000..c75fa2c --- /dev/null +++ b/news-site-v/api_server/temp/article.json @@ -0,0 +1,61 @@ +{ + "name": "article", + "base": "PersistedModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": { + "title": { + "type": "string", + "required": true + }, + "created_date": { + "type": "date", + "required": true, + "default": "$now" + }, + "byline": { + "type": "string" + }, + "abstract": { + "type": "string", + "required": true + }, + "image": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [ + { + "accessType": "*", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "DENY" + }, + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW", + "property": "find" + }, + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW", + "property": "findById" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW", + "property": "create" + } + ], + "methods": {} +} diff --git a/news-site-v/api_server/test/rest_api_test.js b/news-site-v/api_server/test/rest_api_test.js new file mode 100755 index 0000000..4b58a94 --- /dev/null +++ b/news-site-v/api_server/test/rest_api_test.js @@ -0,0 +1,101 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +/* jshint camelcase: false */ +var app = require('../server/server'); +var request = require('supertest'); +var assert = require('assert'); +var loopback = require('loopback'); + +function json(verb, url) { + return request(app)[verb](url) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect('Content-Type', /json/); + } + +describe('REST API request', function() { + before(function(done) { + require('./start-server'); + done(); + }); + + after(function(done) { + app.removeAllListeners('started'); + app.removeAllListeners('loaded'); + done(); + }); + + it('should not allow access without access token', function(done){ + json('get', '/api/projects') + .expect(401, done); + }); + + it('should login non-admin and get the balance', function(done) { + json('post', '/api/users/login') + .send({ + username: 'Jane', + password: 'opensesame' + }) + .expect(200, function(err, res) { + assert(typeof res.body === 'object'); + assert(res.body.id, 'must have an access token'); + assert.equal(res.body.userId, 2); + var accessToken = res.body.id; + json('get', '/api/projects/' + 1 + '?access_token=' + accessToken) + .expect(200, function(err, res){ + var projects = res.body; + assert(typeof res.body === 'object'); + assert(res.body.balance); + assert.equal(res.body.balance, 100); + done(); + }); + }); + }); + + var accessToken; + it('should login the admin user and get all projects', function(done) { + json('post', '/api/users/login') + .send({ + username: 'Bob', + password: 'opensesame' + }) + .expect(200, function(err, res) { + assert(typeof res.body === 'object'); + assert(res.body.id, 'must have an access token'); + assert.equal(res.body.userId, 3); + accessToken = res.body.id; + json('get', '/api/projects?access_token=' + accessToken) + .expect(200, function(err, res){ + var projects = res.body; + assert(Array.isArray(res.body)); + assert.equal(res.body.length, 2); + done(); + }); + }); + }); + + it('should donate money to project1', function(done) { + json('post', '/api/projects/donate?access_token=' + accessToken) + .send({ + id: 2, + amount: 10 + }) + .expect(200, function(err, res){ + assert(typeof res.body === 'object'); + assert(res.body.success); + assert.equal(res.body.success, true); + done(); + }); + }); +}); + +describe('Unexpected Usage', function(){ + it('should not crash the server when posting a bad id', function(done){ + json('post', '/api/users/foobar') + .send({}) + .expect(404, done); + }); +}); diff --git a/news-site-v/api_server/test/start-server.js b/news-site-v/api_server/test/start-server.js new file mode 100755 index 0000000..bbc8296 --- /dev/null +++ b/news-site-v/api_server/test/start-server.js @@ -0,0 +1,18 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-example-access-control +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +var app = require('../server/server'); + +module.exports = function(done) { + if (app.loaded) { + app.once('started', done); + app.start(); + } else { + app.once('loaded', function() { + app.once('started', done); + app.start(); + }); + } +}; diff --git a/news-site-v/package.json b/news-site-v/package.json new file mode 100644 index 0000000..c158aff --- /dev/null +++ b/news-site-v/package.json @@ -0,0 +1,51 @@ +{ + "name": "statechanges", + "version": "0.1.0", + "private": true, + "dependencies": { + "bootstrap": "^4.6.0", + "compression": "^1.7.4", + "concurrently": "^6.0.1", + "cors": "^2.8.5", + "enzyme": "^3.11.0", + "enzyme-to-json": "^3.6.2", + "fetch-mock": "^9.11.0", + "helmet": "^4.4.1", + "isomorphic-fetch": "^3.0.0", + "loopback": "^3.28.0", + "loopback-boot": "^3.3.1", + "loopback-component-explorer": "^6.5.1", + "loopback-connector-postgresql": "^5.3.0", + "loopback-remote-routing": "^1.5.2", + "node-fetch": "^2.6.1", + "react": "^17.0.2", + "react-bootstrap": "^1.5.2", + "react-dom": "^17.0.2", + "react-router-dom": "^5.2.0", + "react-test-renderer": "^17.0.2", + "react-test-utils": "0.0.1", + "reactstrap": "^8.9.0", + "serve-favicon": "^2.5.0", + "sinon": "^10.0.1", + "strong-error-handler": "^4.0.0" + }, + "devDependencies": { + "react-scripts": "^4.0.3" + }, + "scripts": { + "start": "concurrently --kill-others \"npm run start-server\" \"npm run start-client\"", + "start-client": "react-scripts start ", + "start-server": "node ./api_server", + "enable-api-auth": "cp api_server/temp/article.json api_server/common/models/article.json", + "update-tests": "cp api_server/temp/ArticlesAPI.t.js src/api/ArticlesAPI.test.js", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/news-site-v/public/favicon.ico b/news-site-v/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c125de5d897c1ff5692a656485b3216123dcd89 GIT binary patch literal 24838 zcmeI4X^>UL6@VY56)S&I{`6Nu0RscWCdj@GJHx(%?6_-;yKy1n;EEf9f}pr1CW5HA zYt$%U#C=}?jWH&%G@BaHBxsWAoUb3}&6%Ei@4Ii_JRa1`RQ23*yU)_wJ$?H0>6gj0 z${d_I^w5kvTW3xYEc?FvyP3>p$!py@`@T`|dVepIsjbbvR}af%KKy7YuQ%SDC^zmNWPYR^7avI5P-@dKev}UZ^aDAOyci9Nn zwR4qEz~tSvrp|#ACvWzo9`3B;`}^{t18dxaH;?xT7#hmJiKAaI;|O=$yxzXNOHGw~ z^!5pE^SW`av%t_$22LFPsM^l%=PSp!3r`>9w%s+^ZQYnnTQ*Ggd9-1~kj_o$YdW@b ztCkJ(ZGYjusqV5L4{^)R9Gt@gzU1t|?xhE&c^q(|(R#oa*}Sj5c({A$mhrB8*Y@tc zr)K#C{KOp-eHl35ZWJ1&zkmI>9DL%!KJE@_!=W?aH;i?ZDb0O1HPFy6 zcV0Kf)eZ0BHmz9vowF7EA{z*aue9M)iJP&Zd)qYlfJ-c^sS1qY^?>s)!!Ta@x zr@Lz|80r)7<{QVk9Z$}5SDaVtz*Rc?oH5~Wcjoc^eA&EdJ^h@aZ-BvL{K2s_7Cvfr zFL&(R?D&(9OxsS%z_BzI9^Ai^AOF$PUpGk~oO(=OpMc3@Zh&KH1a9>G%%0rC)t@oQ z4d~M`hX+g^Wf8P>A&&qjq|tZe*44Laq7qVPK#QIc)s*Qj34P`NL`Q{xBI`SnR!RC? zlGdTvC%oVZ@0BgcH>}qc!uzul@{i@sH}L0|=eZBJ9qF!HHaw?`s0(_DJj(v`(memI z6jH}=BfGlSlRV4)ouv#h*65yRR>G zo;I#~BVK&l&{+H=_~Nq$d%bFLh7GE5pS&>Fr{RMe>)MM19~z6F1oQo_y>vtlpEZF# zIc82TpMc3z9;{Q)=zG5B#4+96yHCvYy8p4;C%6x`%y$2HccC9|#vGVD)**C0xX|R| z%h)}ze!Tnrvvb@RZ!GX@2lMEq`=`08b`9$%FnN@*zJLo2wD5?MbE&LN)Z>Kty*;m= zt{Cn0>Q3nk)`bR^{dVf!3ECg6Yz4YcskI>$XH*L8E)MsudhnkP0B>+M(XEcErHUBKi~ z1`fEP&WPhp{@Ew?cPlR(ma9iw8NbJWHqp=btCtM*FnP*@ZwwlJ&-Y|LEjgvJzUtPc zz5CrWNBRV8d0-bpWAl<=zM1PU8lJseDxBK^QuuCj2fg{&2#*IG5ezf1B(o%lU+OZx7So4D?yi2*h zFBkr5pG3AJs83uy!~C3mQZLp~ss7-N9oAY>t)!eC#s)CrPukK!(!G*)H?v(~JCoj# zfvgTxMV{4?zL1neQ;ITVBAdFDf`1yG$o{g7^1sR_n{RZ7tnXio?tM%240}(z9xFY0 zlz{^-G*RET;-`7`>e0b{{`!2kM)t7Si9ZqD$~wh*hyGC>z~qs@0T&u*;h}hiKGEga zHkJ;%7aNc^o_0(>Z{Gp069H;TwPTUnvvX0SJ+kGGZ0lFBWocl>kaa)AoiMta+x_-J-?#KHFnJ*! zwD1V?)4s#|?O)DlMBhVv4IgZs?d>b<6%xK3<{o91H?-%8?PK!_fm#3d>{{gQ z?*8`b{G6?bZKdO{_9IVlz{R$PcGjeL|3*|@upby()_Lf^eQ&XQe)CjsbJ3Uolrgt< zweld3GH|fZpn(=1@PencO_a_)v6tU?WV-w8wfXLbOGae0{<*C?Ead$6v+> z|EQKThJTmwXK!c6AOD+FgtDv7i<48{-OPce!KDVkzR+XKOcREPha(;$}iUb!*)f-Fb}Y4@r9z-_{OIg z`xn^T#ZtEPv_T$M*Sr+=Z{q#~8$|7Y{0!*2u${D*Jj%dfOrS~FzpH*_|55J!7kl4w z?LT!7T(!3!632pmZh?dh`n-z$_ts42pn6;c`}hx;TSYd0idsqal5&0uGV=UM{c9xQ z1KK6&TS+a^H|6B_hPo1W3 zh+Dun!`UkP%H3}*@IE18q{7&MH2f3?T6o}Jf+xI@fh=SyUOArw`*w1_-PUlHZTHc@ z--yqIxPtI}IjPRzLIZ8cPv4P=>?A&=E~~0)>&J#V;TwAR*6}`01iu~U$@prtzW6YS ze}E>gUX+0YuF}B+Uhw2x7a7Q+oOzMNFHTNN<)40Rzg#`pABKF18@l}5A>RL`?Ri;Z zC8ExD$)im1@R{N7(wIog8$Yn(6%q$yd9(zKe};OnH%;mWBs7)>ls~T3Wi6!Xqw6+dpJLVS1P| z9qV%io-nE*rYcPxiS31>U_>mbPTXxkC*!?*zefr#2vF|qr8{|4|u^7-pD|f z&OPc->UKu)=iHgIpysp;Lsbyj}GJWoBkufOA={CRTUjr%af zc5pUH9{pg?M5%+)oN`q9yBbBt@+3xHV)qGm8b)Cp-w7~CwEhtBUk0rbjrqM zTb|tQ3-5-pw^cul`T+X&s?O;?V(FD!(Q9Qg@(LTCNz{0-vBM^SX5lti3|GpxFn4;Ax6pGc~t)R!Bo${lYH(* z!F&5X*?S&}YoDCyzwv1H+XI(+rL`;RN9}iLxlfr-r&vGG8OQa@=>+a)+Ij)sd_{wu z1Am(+3-RFr4&N8N6+hqo19S#;SA1-hG>07p3}&*j4CR+rqdV)^6n; z_vFr!(a%-=#=kb{pYmNL@6|DWkw~%E2V2jYl*e1}c{e$fib?(O+hs}eoBLRo&9(;J}YV}0Mi;LZAe{U$(s= zT<-IaV$Z+q-P!~3{HxN>Kbw30jXzM&I(S<6Ksx^}HvU2Vntb!etSsm0>)j}Me^+L5{2yz--)?W`Q?az z!WLG4UNP}+#C+NKH+ZG-Q=E>IPp%LuKLx$$8NAOGr(#~P>!EA zDYlpXDR=xM?Xv5(-qp74Cw3LzBeASHSBY`OezkbOyjP!G%WSymju_C$VBl--z + + + + + + + React App + + +
+ + + diff --git a/news-site-v/src/App.css b/news-site-v/src/App.css new file mode 100755 index 0000000..15adfdc --- /dev/null +++ b/news-site-v/src/App.css @@ -0,0 +1,24 @@ +.App { + text-align: center; +} + +.App-logo { + animation: App-logo-spin infinite 20s linear; + height: 80px; +} + +.App-header { + background-color: #222; + height: 150px; + padding: 20px; + color: white; +} + +.App-intro { + font-size: large; +} + +@keyframes App-logo-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/news-site-v/src/App.js b/news-site-v/src/App.js new file mode 100644 index 0000000..a4df238 --- /dev/null +++ b/news-site-v/src/App.js @@ -0,0 +1,47 @@ +import React, { Component } from 'react'; +import { BrowserRouter as Router, Route } from 'react-router-dom'; +import './App.css'; +import AppNav from './components/AppNav/AppNav.js'; +import HomePage from './pages/HomePage.js'; +import ArticlePage from './pages/ArticlePage.js'; +import SectionPage from './pages/SectionPage.js'; +import AddArticlePage from './pages/AddArticlePage.js'; +import LoginPage from './pages/LoginPage.js'; + +class App extends Component { + render() { + return ( +
+ +
+ + + + + + +
+
+
+ ); + } +} + +export default App; + + +// Functional solution: +// function App() { +// return ( +//
+// +// +//
+// +// +// +//
+//
+//
+// ); +// } diff --git a/news-site-v/src/api/ArticlesAPI.js b/news-site-v/src/api/ArticlesAPI.js new file mode 100644 index 0000000..d4ceae2 --- /dev/null +++ b/news-site-v/src/api/ArticlesAPI.js @@ -0,0 +1,69 @@ +const BASE_URL = 'http://localhost:3001/api/articles'; + +const fetchArticleByID = async (articleID) => { + const response = await fetch(`${BASE_URL}/${articleID}`); + const data = await response.json(); + return data; +}; + +const fetchArticlesBySection = async (section) => { + const response = await fetch(`${BASE_URL}?filter={"where":{"section":"${section}"}}`); + const data = await response.json(); + return data; +}; + +const fetchArticles = async (filters = null) => { + const url = filters ? `${BASE_URL}?filter={"where":${filters}}` : BASE_URL; + const response = await fetch(url); + const data = await response.json(); + return data; +}; + +const searchArticles = async (textToSearchFor) => { + const response = await fetch(`${BASE_URL}?filter={"where":{"title":{"ilike":"${textToSearchFor}"}}}`) + const data = await response.json(); + return data; +} + +const AddArticle = async (articleObject) => { + try{ + let response = await fetch(BASE_URL, { + header: { + 'Content-type': 'application/json' + }, + method: 'POST', + body: JSON.stringify(articleObject) + }) + let data = await response.json(); + console.log(data) + if (data.error) { + return { + 'message': 'There was an error saving your message. Please make sure all fields are filled.', + 'isError': true, + 'statusCode': 200 + } + } + else { + return { + 'message': 'Article Successfully Created', + 'isError': false, + 'statusCode': 200 + } + } + } + catch (err) { + console.error(err) + } +} + + + + + +export { + fetchArticleByID, + fetchArticles, + fetchArticlesBySection, + searchArticles, + AddArticle, +}; diff --git a/news-site-v/src/api/ArticlesAPI.test.js b/news-site-v/src/api/ArticlesAPI.test.js new file mode 100644 index 0000000..a1e4729 --- /dev/null +++ b/news-site-v/src/api/ArticlesAPI.test.js @@ -0,0 +1,44 @@ + +import ArticlesAPI from './ArticlesAPI' +import fetchMock from 'fetch-mock' +require('isomorphic-fetch') + +afterEach(() => { + fetchMock.restore() +}) + +it('calls ArticlesAPI.fetchArticleByID(1)', (done) => { + fetchMock.get('http://localhost:3001/api/articles/1', { success: true }) + return ArticlesAPI.fetchArticleByID(1) + .then((json) => { + expect(json.success).toEqual(true) + done() + }) + .catch((err) => { + throw new Error('Call failed') + }) +}) + +it('calls ArticlesAPI.fetchArticles()', (done) => { + fetchMock.get('http://localhost:3001/api/articles', { success: true }) + return ArticlesAPI.fetchArticles() + .then((json) => { + expect(json.success).toEqual(true) + done() + }) + .catch((err) => { + throw new Error('Call failed') + }) +}) + +it('calls ArticlesAPI.fetchArticlesBySection(\'opinion\')', (done) => { + fetchMock.get('http://localhost:3001/api/articles?filter={"where":{"section":"opinion"}}', { success: true }) + return ArticlesAPI.fetchArticlesBySection('opinion') + .then((json) => { + expect(json.success).toEqual(true) + done() + }) + .catch((err) => { + throw new Error('Call failed') + }) +}) diff --git a/news-site-v/src/components/AppNav/AppNav.js b/news-site-v/src/components/AppNav/AppNav.js new file mode 100644 index 0000000..98e435e --- /dev/null +++ b/news-site-v/src/components/AppNav/AppNav.js @@ -0,0 +1,39 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import { Navbar } from 'reactstrap'; +import navItems from '../../config/Sections.json'; +import HomePage from '../../pages/HomePage'; + +class AppNav extends Component { + render() { + return ( + + + HOME + {navItems.map((navItem) => + + { navItem.label } + + )} + ADD ARTICLE + LOGIN + + ) + } +} + +export default AppNav; + + +// Functional solution: +// function AppNav() { +// return ( +// +// {navItems.map((navItem) => +// +// {navItem.label} +// +// )} +// +// ); +// } diff --git a/news-site-v/src/components/Article/Article.js b/news-site-v/src/components/Article/Article.js new file mode 100644 index 0000000..c7d4d14 --- /dev/null +++ b/news-site-v/src/components/Article/Article.js @@ -0,0 +1,42 @@ +import React, { Component } from 'react'; +import { Media } from 'reactstrap'; +import "./article.css"; + +class Article extends Component { + render() { + const { title, created_date: createdDate, abstract, byline, image } = this.props; + return ( + + + { image && } + + + { title } +

{ createdDate }

+ { byline &&

{ byline }

} +

{ abstract }

+
+
+ ) + } +} + +export default Article; + + +// Functional solution: +// function Article({ title, created_date: createdDate, abstract, byline, image }) { +// return ( +// +// +// {image && } +// +// +// {title} +//

{createdDate}

+// {byline &&

{byline}

} +//

{abstract}

+//
+//
+// ); +// } diff --git a/news-site-v/src/components/Article/article.css b/news-site-v/src/components/Article/article.css new file mode 100644 index 0000000..142eb72 --- /dev/null +++ b/news-site-v/src/components/Article/article.css @@ -0,0 +1,7 @@ +.image { + padding: 20px; +} + +.body { + padding: 20px; +} diff --git a/news-site-v/src/components/ArticleList/ArticleList.js b/news-site-v/src/components/ArticleList/ArticleList.js new file mode 100644 index 0000000..99c5047 --- /dev/null +++ b/news-site-v/src/components/ArticleList/ArticleList.js @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import ArticleTeaser from '../ArticleTeaser/ArticleTeaser.js'; +import { ListGroup, ListGroupItem } from 'reactstrap'; + +class ArticleList extends Component { + render() { + const { articles } = this.props; + return ( + + { articles.map((article, index) => ( + + + + ))} + + ); + } +} + +export default ArticleList; + + +// Functional solution: +// function ArticleList({ articles }) { +// return ( +// +// {articles.map((article, index) => ( +// +// +// +// ))} +// +// ); +// } diff --git a/news-site-v/src/components/ArticleTeaser/ArticleTeaser.js b/news-site-v/src/components/ArticleTeaser/ArticleTeaser.js new file mode 100644 index 0000000..7b337e6 --- /dev/null +++ b/news-site-v/src/components/ArticleTeaser/ArticleTeaser.js @@ -0,0 +1,36 @@ +import React, { Component } from 'react'; +import { ListGroupItemHeading, ListGroupItemText } from 'reactstrap'; +import { Link } from 'react-router-dom'; + +class ArticleTeaser extends Component { + render() { + /* Note: the { created_date: createdDate } syntax in this destructure is + taking the value of created_date from this.props and setting it to + a new variable called createdDate + */ + const { id, title, created_date: createdDate } = this.props; + return ( +
+ + {title} + + { createdDate } +
+ ) + } +} + +export default ArticleTeaser; + + +// Functional solution: +// function ArticleTeaser({ id, title, created_date: createdDate }) { +// return ( +//
+// +// {title} +// +// {createdDate} +//
+// ); +// } diff --git a/news-site-v/src/config/Sections.json b/news-site-v/src/config/Sections.json new file mode 100644 index 0000000..3349e74 --- /dev/null +++ b/news-site-v/src/config/Sections.json @@ -0,0 +1,18 @@ +[ + { + "label": "OPINION", + "value": "opinion" + }, + { + "label": "WORLD", + "value": "world" + }, + { + "label": "NATIONAL", + "value": "national" + }, + { + "label": "BUSINESS", + "value": "business" + } +] \ No newline at end of file diff --git a/news-site-v/src/data/news.json b/news-site-v/src/data/news.json new file mode 100644 index 0000000..f2f880c --- /dev/null +++ b/news-site-v/src/data/news.json @@ -0,0 +1,2893 @@ +[ + { + "abstract": "Expedita delectus voluptas ex rem cumque dolorem voluptatem.", + "byline": "By DAVID ZUCCHINO", + "created_date": "2017-02-10T03:00:25-05:00", + "des_facet": [ + "Refugees and Displaced Persons", + "Muslims and Islam", + "Sunni Muslims" + ], + "geo_facet": [ + "Falluja (Iraq)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "A returning resident of Falluja with police officers.", + "copyright": "David Zucchino for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/07/world/falluja1/falluja1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "A returning resident of Falluja with police officers.", + "copyright": "David Zucchino for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/07/world/falluja1/falluja1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "A returning resident of Falluja with police officers.", + "copyright": "David Zucchino for The New York Times", + "format": "Normal", + "height": 143, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/07/world/falluja1/falluja1-articleInline.jpg", + "width": 190 + }, + { + "caption": "A returning resident of Falluja with police officers.", + "copyright": "David Zucchino for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/07/world/falluja1/falluja1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "A returning resident of Falluja with police officers.", + "copyright": "David Zucchino for The New York Times", + "format": "superJumbo", + "height": 1536, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/07/world/falluja1/falluja1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Islamic State in Iraq and Syria (ISIS)" + ], + "per_facet": [], + "published_date": "2017-02-10T03:00:25-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kaQI2q", + "subsection": "Middle East", + "title": "Expedita delectus voluptas ex rem cumque dolorem voluptatem.", + "updated_date": "2017-02-10T03:00:25-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/middleeast/falluja-iraq-isis.html" + }, + { + "abstract": "Eum qui aspernatur sit non sint.", + "byline": "Photographs and Text by RUKMINI CALLIMACHI", + "created_date": "2017-02-09T17:14:52-05:00", + "des_facet": [ + "vis-dispatch", + "Muslims and Islam", + "Terrorism" + ], + "geo_facet": [ + "Iraq" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "", + "copyright": "Rukmini Callimachi/The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/middleeast/10Mosul-slide-4RH2/10Mosul-slide-4RH2-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "", + "copyright": "Rukmini Callimachi/The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/middleeast/10Mosul-slide-4RH2/10Mosul-slide-4RH2-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "", + "copyright": "Rukmini Callimachi/The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/middleeast/10Mosul-slide-4RH2/10Mosul-slide-4RH2-articleInline.jpg", + "width": 190 + }, + { + "caption": "", + "copyright": "Rukmini Callimachi/The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/middleeast/10Mosul-slide-4RH2/10Mosul-slide-4RH2-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "", + "copyright": "Rukmini Callimachi/The New York Times", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/middleeast/10Mosul-slide-4RH2/10Mosul-slide-4RH2-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Islamic State in Iraq and Syria (ISIS)" + ], + "per_facet": [], + "published_date": "2017-02-09T17:14:52-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kTR0io", + "subsection": "Middle East", + "title": "Eum qui aspernatur sit non sint.", + "updated_date": "2017-02-09T17:14:51-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/middleeast/today-in-mosul-retaken-parts-of-isis-stronghold-return-to-life.html" + }, + { + "abstract": "Eius placeat animi consectetur.", + "byline": "By GAIA PIANIGIANI", + "created_date": "2017-02-10T17:36:51-05:00", + "des_facet": [ + "Children and Childhood", + "Organized Crime", + "Juvenile Delinquency", + "Families and Family Life" + ], + "geo_facet": [ + "Calabria (Italy)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Roberto Di Bella thinks that severing familial links restores the possibility of a normal life to the children of mob families.", + "copyright": "Gianni Cipriano for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/00calabria1/00calabria1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Roberto Di Bella thinks that severing familial links restores the possibility of a normal life to the children of mob families.", + "copyright": "Gianni Cipriano for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/00calabria1/00calabria1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Roberto Di Bella thinks that severing familial links restores the possibility of a normal life to the children of mob families.", + "copyright": "Gianni Cipriano for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/00calabria1/00calabria1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Roberto Di Bella thinks that severing familial links restores the possibility of a normal life to the children of mob families.", + "copyright": "Gianni Cipriano for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/00calabria1/00calabria1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Roberto Di Bella thinks that severing familial links restores the possibility of a normal life to the children of mob families.", + "copyright": "Gianni Cipriano for The New York Times", + "format": "superJumbo", + "height": 1366, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/09/world/00calabria1/00calabria1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "'Ndrangheta" + ], + "per_facet": [], + "published_date": "2017-02-10T17:36:51-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kYdpve", + "subsection": "Europe", + "title": "Eius placeat animi consectetur.", + "updated_date": "2017-02-10T17:36:50-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/europe/breaking-up-the-family-as-a-way-to-break-up-the-mob.html" + }, + { + "abstract": "Dicta veritatis et distinctio.", + "byline": "By ALISON SMALE", + "created_date": "2017-02-10T12:29:46-05:00", + "des_facet": [ + "Comedy and Humor", + "Decisions and Verdicts", + "Poetry and Poets", + "Freedom of Speech and Expression" + ], + "geo_facet": [ + "Germany" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "A German court has upheld a ruling barring the comedian Jan Böhmermann from repeating 18 of the 24 lines of his lewd poem satirizing Turkey’s president, Recep Tayyip Erdogan.", + "copyright": "Rolf Vennenbernd/European Pressphoto Agency", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Germany1/11Germany1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "A German court has upheld a ruling barring the comedian Jan Böhmermann from repeating 18 of the 24 lines of his lewd poem satirizing Turkey’s president, Recep Tayyip Erdogan.", + "copyright": "Rolf Vennenbernd/European Pressphoto Agency", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Germany1/11Germany1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "A German court has upheld a ruling barring the comedian Jan Böhmermann from repeating 18 of the 24 lines of his lewd poem satirizing Turkey’s president, Recep Tayyip Erdogan.", + "copyright": "Rolf Vennenbernd/European Pressphoto Agency", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Germany1/11Germany1-articleInline.jpg", + "width": 190 + }, + { + "caption": "A German court has upheld a ruling barring the comedian Jan Böhmermann from repeating 18 of the 24 lines of his lewd poem satirizing Turkey’s president, Recep Tayyip Erdogan.", + "copyright": "Rolf Vennenbernd/European Pressphoto Agency", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Germany1/11Germany1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "A German court has upheld a ruling barring the comedian Jan Böhmermann from repeating 18 of the 24 lines of his lewd poem satirizing Turkey’s president, Recep Tayyip Erdogan.", + "copyright": "Rolf Vennenbernd/European Pressphoto Agency", + "format": "superJumbo", + "height": 1366, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Germany1/11Germany1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Bohmermann, Jan", + "Erdogan, Recep Tayyip" + ], + "published_date": "2017-02-10T12:29:46-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kXmQem", + "subsection": "Europe", + "title": "Dicta veritatis et distinctio.", + "updated_date": "2017-02-10T12:29:45-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/europe/jan-bohmermann-recep-tayyip-erdogan-poem.html" + }, + { + "abstract": "Sit fugiat aspernatur distinctio voluptatem impedit quibusdam.", + "byline": "By BENOÎT MORENNE", + "created_date": "2017-02-10T07:35:42-05:00", + "des_facet": [ + "Middle East and Africa Migrant Crisis", + "Refugees and Displaced Persons", + "Fines (Penalties)" + ], + "geo_facet": [ + "France" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [], + "org_facet": [], + "per_facet": [ + "Herrou, Cedric" + ], + "published_date": "2017-02-10T07:35:42-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kWsn4F", + "subsection": "Europe", + "title": "Sit fugiat aspernatur distinctio voluptatem impedit quibusdam.", + "updated_date": "2017-02-10T07:35:41-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/europe/cedric-herrou-farmer-france-migrants.html" + }, + { + "abstract": "Dicta necessitatibus tempore temporibus eum.", + "byline": "By MICHAEL FORSYTHE and PAUL MOZUR", + "created_date": "2017-02-10T15:56:43-05:00", + "des_facet": [ + "Politics and Government" + ], + "geo_facet": [ + "Hong Kong", + "China" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Xiao Jianhua in a park in Beijing in an undated photograph. The Chinese billionaire disappeared from the Four Seasons Hotel in Hong Kong last month and is believed to be in police custody in mainland China.", + "copyright": "The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Hongkong1/11Hongkong1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Xiao Jianhua in a park in Beijing in an undated photograph. The Chinese billionaire disappeared from the Four Seasons Hotel in Hong Kong last month and is believed to be in police custody in mainland China.", + "copyright": "The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Hongkong1/11Hongkong1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Xiao Jianhua in a park in Beijing in an undated photograph. The Chinese billionaire disappeared from the Four Seasons Hotel in Hong Kong last month and is believed to be in police custody in mainland China.", + "copyright": "The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Hongkong1/11Hongkong1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Xiao Jianhua in a park in Beijing in an undated photograph. The Chinese billionaire disappeared from the Four Seasons Hotel in Hong Kong last month and is believed to be in police custody in mainland China.", + "copyright": "The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Hongkong1/11Hongkong1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Xiao Jianhua in a park in Beijing in an undated photograph. The Chinese billionaire disappeared from the Four Seasons Hotel in Hong Kong last month and is believed to be in police custody in mainland China.", + "copyright": "The New York Times", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Hongkong1/11Hongkong1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Communist Party of China" + ], + "per_facet": [ + "Xiao Jianhua", + "Xi Jinping" + ], + "published_date": "2017-02-10T15:56:43-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kYcWJb", + "subsection": "Asia Pacific", + "title": "Dicta necessitatibus tempore temporibus eum.", + "updated_date": "2017-02-10T15:56:42-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/asia/xiao-jianhua-hong-kong-disappearance.html" + }, + { + "abstract": "Voluptatem ut voluptatem voluptas alias dolores ad.", + "byline": "By FELIPE VILLAMOR", + "created_date": "2017-02-10T12:49:30-05:00", + "des_facet": [ + "Police Brutality, Misconduct and Shootings", + "Drug Abuse and Traffic" + ], + "geo_facet": [ + "Philippines" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Maria Belen Daa, 61, with a photo of her son Marcelo, 31, in the Philippines last week. He was shot along with four others by the police, who raided his home looking for drugs last August.", + "copyright": "Jes Aznar for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/asia/08PHILIPPINES-1/08PHILIPPINES-1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Maria Belen Daa, 61, with a photo of her son Marcelo, 31, in the Philippines last week. He was shot along with four others by the police, who raided his home looking for drugs last August.", + "copyright": "Jes Aznar for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/asia/08PHILIPPINES-1/08PHILIPPINES-1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Maria Belen Daa, 61, with a photo of her son Marcelo, 31, in the Philippines last week. He was shot along with four others by the police, who raided his home looking for drugs last August.", + "copyright": "Jes Aznar for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/asia/08PHILIPPINES-1/08PHILIPPINES-1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Maria Belen Daa, 61, with a photo of her son Marcelo, 31, in the Philippines last week. He was shot along with four others by the police, who raided his home looking for drugs last August.", + "copyright": "Jes Aznar for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/asia/08PHILIPPINES-1/08PHILIPPINES-1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Maria Belen Daa, 61, with a photo of her son Marcelo, 31, in the Philippines last week. He was shot along with four others by the police, who raided his home looking for drugs last August.", + "copyright": "Jes Aznar for The New York Times", + "format": "superJumbo", + "height": 1367, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/asia/08PHILIPPINES-1/08PHILIPPINES-1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Duterte, Rodrigo" + ], + "published_date": "2017-02-10T12:49:30-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kuvu0f", + "subsection": "Asia Pacific", + "title": "Voluptatem ut voluptatem voluptas alias dolores ad.", + "updated_date": "2017-02-10T12:49:29-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/asia/a-rare-survivor-of-a-philippine-drug-raid-takes-the-police-to-court.html" + }, + { + "abstract": "Voluptate reiciendis quibusdam quo exercitationem illo.", + "byline": "By PAULINA VILLEGAS and ELISABETH MALKIN", + "created_date": "2017-02-10T18:40:00-05:00", + "des_facet": [], + "geo_facet": [ + "Iguala (Mexico)", + "Mexico City (Mexico)", + "Mexico" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Relatives of missing students and other protesters gathered in Guadalajara, Mexico, on Sept. 26, 2016, to commemorate the second anniversary of the students’ disappearance.", + "copyright": "Hector Guerrero/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11mexico1/11mexico1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Relatives of missing students and other protesters gathered in Guadalajara, Mexico, on Sept. 26, 2016, to commemorate the second anniversary of the students’ disappearance.", + "copyright": "Hector Guerrero/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11mexico1/11mexico1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Relatives of missing students and other protesters gathered in Guadalajara, Mexico, on Sept. 26, 2016, to commemorate the second anniversary of the students’ disappearance.", + "copyright": "Hector Guerrero/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11mexico1/11mexico1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Relatives of missing students and other protesters gathered in Guadalajara, Mexico, on Sept. 26, 2016, to commemorate the second anniversary of the students’ disappearance.", + "copyright": "Hector Guerrero/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11mexico1/11mexico1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Relatives of missing students and other protesters gathered in Guadalajara, Mexico, on Sept. 26, 2016, to commemorate the second anniversary of the students’ disappearance.", + "copyright": "Hector Guerrero/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11mexico1/11mexico1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Inter-American Human Rights Commission", + "Guerreros Unidos (Gang)" + ], + "per_facet": [ + "Zeron de Lucio, Tomas" + ], + "published_date": "2017-02-10T18:40:00-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kvlAM0", + "subsection": "Americas", + "title": "Voluptate reiciendis quibusdam quo exercitationem illo.", + "updated_date": "2017-02-10T18:39:59-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/americas/mexico-missing-students-investigation.html" + }, + { + "abstract": "Eos modi nulla velit quis aut.", + "byline": "By SIMON ROMERO", + "created_date": "2017-02-10T09:30:24-05:00", + "des_facet": [ + "Politics and Government" + ], + "geo_facet": [ + "Sao Paulo (Brazil)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Mayor João Doria of São Paulo, Brazil, center in white cap, helped plant a tree in the Vila Carrão neighborhood on Sunday. He took office in January.", + "copyright": "Mauricio Lima for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11brazil1/11brazil1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Mayor João Doria of São Paulo, Brazil, center in white cap, helped plant a tree in the Vila Carrão neighborhood on Sunday. He took office in January.", + "copyright": "Mauricio Lima for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11brazil1/11brazil1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Mayor João Doria of São Paulo, Brazil, center in white cap, helped plant a tree in the Vila Carrão neighborhood on Sunday. He took office in January.", + "copyright": "Mauricio Lima for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11brazil1/11brazil1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Mayor João Doria of São Paulo, Brazil, center in white cap, helped plant a tree in the Vila Carrão neighborhood on Sunday. He took office in January.", + "copyright": "Mauricio Lima for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11brazil1/11brazil1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Mayor João Doria of São Paulo, Brazil, center in white cap, helped plant a tree in the Vila Carrão neighborhood on Sunday. He took office in January.", + "copyright": "Mauricio Lima for The New York Times", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11brazil1/11brazil1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Doria, Joao" + ], + "published_date": "2017-02-10T09:30:24-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kWKfMY", + "subsection": "Americas", + "title": "Eos modi nulla velit quis aut.", + "updated_date": "2017-02-10T09:30:23-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/americas/a-rich-apprentice-host-in-politics-but-this-mayor-says-hes-no-trump.html" + }, + { + "abstract": "Sit aut alias enim quod rerum voluptates.", + "byline": "By DIONNE SEARCEY and FRANCOIS ESSOMBA", + "created_date": "2017-02-10T13:37:22-05:00", + "des_facet": [ + "Computers and the Internet", + "Demonstrations, Protests and Riots", + "Wireless Communications", + "Social Media" + ], + "geo_facet": [ + "Cameroon", + "Africa", + "Gambia" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "People waiting to vote in the presidential election outside a polling station in Banjul, Gambia, in December. Government officials said security concerns prompted internet services to be shut off and international cellphone calls to be blocked.", + "copyright": "Marco Longari/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/00internetshutdown1/00internetshutdown1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "People waiting to vote in the presidential election outside a polling station in Banjul, Gambia, in December. Government officials said security concerns prompted internet services to be shut off and international cellphone calls to be blocked.", + "copyright": "Marco Longari/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/00internetshutdown1/00internetshutdown1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "People waiting to vote in the presidential election outside a polling station in Banjul, Gambia, in December. Government officials said security concerns prompted internet services to be shut off and international cellphone calls to be blocked.", + "copyright": "Marco Longari/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/00internetshutdown1/00internetshutdown1-articleInline.jpg", + "width": 190 + }, + { + "caption": "People waiting to vote in the presidential election outside a polling station in Banjul, Gambia, in December. Government officials said security concerns prompted internet services to be shut off and international cellphone calls to be blocked.", + "copyright": "Marco Longari/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/00internetshutdown1/00internetshutdown1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "People waiting to vote in the presidential election outside a polling station in Banjul, Gambia, in December. Government officials said security concerns prompted internet services to be shut off and international cellphone calls to be blocked.", + "copyright": "Marco Longari/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/08/world/00internetshutdown1/00internetshutdown1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-10T13:37:22-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kuqF78", + "subsection": "Africa", + "title": "Sit aut alias enim quod rerum voluptates.", + "updated_date": "2017-02-10T13:37:21-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/africa/african-nations-increasingly-silence-internet-to-stem-protests.html" + }, + { + "abstract": "Placeat est debitis eligendi id sapiente ipsam iure voluptatibus voluptatibus.", + "byline": "By JEFFREY GETTLEMAN", + "created_date": "2017-02-09T14:43:53-05:00", + "des_facet": [ + "Refugees and Displaced Persons" + ], + "geo_facet": [ + "Dadaab (Kenya)", + "Somalia" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Part of the sprawling Dadaab refugee camp, north of Nairobi, Kenya, in 2015. A Kenyan judge ruled on Thursday that the government’s plan to close the camp was discriminatory.", + "copyright": "Tony Karumba/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/africa/10kenya1/10kenya1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Part of the sprawling Dadaab refugee camp, north of Nairobi, Kenya, in 2015. A Kenyan judge ruled on Thursday that the government’s plan to close the camp was discriminatory.", + "copyright": "Tony Karumba/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/africa/10kenya1/10kenya1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Part of the sprawling Dadaab refugee camp, north of Nairobi, Kenya, in 2015. A Kenyan judge ruled on Thursday that the government’s plan to close the camp was discriminatory.", + "copyright": "Tony Karumba/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 135, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/africa/10kenya1/10kenya1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Part of the sprawling Dadaab refugee camp, north of Nairobi, Kenya, in 2015. A Kenyan judge ruled on Thursday that the government’s plan to close the camp was discriminatory.", + "copyright": "Tony Karumba/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/africa/10kenya1/10kenya1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Part of the sprawling Dadaab refugee camp, north of Nairobi, Kenya, in 2015. A Kenyan judge ruled on Thursday that the government’s plan to close the camp was discriminatory.", + "copyright": "Tony Karumba/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1457, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/africa/10kenya1/10kenya1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-09T14:43:53-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kTrvxV", + "subsection": "Africa", + "title": "Placeat est debitis eligendi id sapiente ipsam iure voluptatibus voluptatibus.", + "updated_date": "2017-02-09T14:43:52-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/africa/kenyan-court-blocks-plan-to-close-dadaab-refugee-camp.html" + }, + { + "abstract": "Qui id qui voluptatem necessitatibus voluptas.", + "byline": "By JOANNA KLEIN", + "created_date": "2017-02-10T18:08:38-05:00", + "des_facet": [ + "Archaeology and Anthropology" + ], + "geo_facet": [ + "Amazon Jungle", + "Brazil" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "New research suggests people were sustainably managing the Amazon rain forest much earlier than was previously thought.", + "copyright": "Jenny Watling", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/science/11tb-amazon01/11tb-amazon01-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "New research suggests people were sustainably managing the Amazon rain forest much earlier than was previously thought.", + "copyright": "Jenny Watling", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/science/11tb-amazon01/11tb-amazon01-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "New research suggests people were sustainably managing the Amazon rain forest much earlier than was previously thought.", + "copyright": "Jenny Watling", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/science/11tb-amazon01/11tb-amazon01-articleInline.jpg", + "width": 190 + }, + { + "caption": "New research suggests people were sustainably managing the Amazon rain forest much earlier than was previously thought.", + "copyright": "Jenny Watling", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/science/11tb-amazon01/11tb-amazon01-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "New research suggests people were sustainably managing the Amazon rain forest much earlier than was previously thought.", + "copyright": "Jenny Watling", + "format": "superJumbo", + "height": 1371, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/science/11tb-amazon01/11tb-amazon01-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Proceedings of the National Academy of Sciences" + ], + "per_facet": [ + "Watling, Jennifer" + ], + "published_date": "2017-02-10T18:08:38-05:00", + "section": "Science", + "short_url": "https://nyti.ms/2kYiOlD", + "subsection": "", + "title": "Qui id qui voluptatem necessitatibus voluptas.", + "updated_date": "2017-02-10T18:08:38-05:00", + "url": "https://www.nytimes.com/2017/02/10/science/amazon-earthworks-geoglyphs-brazil.html" + }, + { + "abstract": "Accusantium occaecati illum dicta officiis.", + "byline": "By THE ASSOCIATED PRESS", + "created_date": "2017-02-10T17:16:00-05:00", + "des_facet": [], + "geo_facet": [], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-10T17:16:00-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kYacMc", + "subsection": "Africa", + "title": "Accusantium occaecati illum dicta officiis.", + "updated_date": "2017-02-10T17:40:06-05:00", + "url": "https://www.nytimes.com/aponline/2017/02/10/world/africa/ap-soc-angola-deadly-stampede.html" + }, + { + "abstract": "Sit nam odio asperiores eaque consectetur nulla aut.", + "byline": "By MICHAEL SCHWIRTZ", + "created_date": "2017-02-10T16:47:41-05:00", + "des_facet": [ + "Politics and Government", + "Embargoes and Sanctions" + ], + "geo_facet": [ + "Avdiivka (Ukraine)", + "Lithuania", + "Russia", + "Ukraine" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Relatives and friends of Elena Volkova, a victim of shelling, at her funeral this week in Avdiivka, Ukraine.", + "copyright": "Evgeniy Maloletka/Associated Press", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Ukraine1/11Ukraine1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Relatives and friends of Elena Volkova, a victim of shelling, at her funeral this week in Avdiivka, Ukraine.", + "copyright": "Evgeniy Maloletka/Associated Press", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Ukraine1/11Ukraine1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Relatives and friends of Elena Volkova, a victim of shelling, at her funeral this week in Avdiivka, Ukraine.", + "copyright": "Evgeniy Maloletka/Associated Press", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Ukraine1/11Ukraine1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Relatives and friends of Elena Volkova, a victim of shelling, at her funeral this week in Avdiivka, Ukraine.", + "copyright": "Evgeniy Maloletka/Associated Press", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Ukraine1/11Ukraine1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Relatives and friends of Elena Volkova, a victim of shelling, at her funeral this week in Avdiivka, Ukraine.", + "copyright": "Evgeniy Maloletka/Associated Press", + "format": "superJumbo", + "height": 1366, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Ukraine1/11Ukraine1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "United Nations" + ], + "per_facet": [ + "Putin, Vladimir V", + "Trump, Donald J" + ], + "published_date": "2017-02-10T16:47:41-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kv3mKh", + "subsection": "Europe", + "title": "Sit nam odio asperiores eaque consectetur nulla aut.", + "updated_date": "2017-02-10T16:47:40-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/europe/as-fighting-escalates-in-ukraine-attention-focuses-on-donald-trump.html" + }, + { + "abstract": "Maiores dolores sit id nihil consequatur ab adipisci.", + "byline": "By CHRISTINE HAUSER", + "created_date": "2017-02-10T15:02:55-05:00", + "des_facet": [ + "Camels" + ], + "geo_facet": [ + "Abu Dhabi (United Arab Emirates)", + "United Arab Emirates" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "The traditional camel race during the Sultan Bin Zayed Heritage Festival, held at the Sweihan racecourse on the outskirts of Abu Dhabi.", + "copyright": "Karim Sahib/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/middleeast/11camel2_xp/11camel2_xp-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "The traditional camel race during the Sultan Bin Zayed Heritage Festival, held at the Sweihan racecourse on the outskirts of Abu Dhabi.", + "copyright": "Karim Sahib/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/middleeast/11camel2_xp/11camel2_xp-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "The traditional camel race during the Sultan Bin Zayed Heritage Festival, held at the Sweihan racecourse on the outskirts of Abu Dhabi.", + "copyright": "Karim Sahib/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/middleeast/11camel2_xp/11camel2_xp-articleInline.jpg", + "width": 190 + }, + { + "caption": "The traditional camel race during the Sultan Bin Zayed Heritage Festival, held at the Sweihan racecourse on the outskirts of Abu Dhabi.", + "copyright": "Karim Sahib/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/middleeast/11camel2_xp/11camel2_xp-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "The traditional camel race during the Sultan Bin Zayed Heritage Festival, held at the Sweihan racecourse on the outskirts of Abu Dhabi.", + "copyright": "Karim Sahib/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1233, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/middleeast/11camel2_xp/11camel2_xp-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Sheikh Sultan Bin Zayed al-Nahyan" + ], + "published_date": "2017-02-10T15:02:55-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kuNnvV", + "subsection": "Middle East", + "title": "Maiores dolores sit id nihil consequatur ab adipisci.", + "updated_date": "2017-02-10T15:02:54-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/middleeast/camel-beauty-contest-abu-dhabi.html" + }, + { + "abstract": "Qui nesciunt dignissimos cumque quas rerum eveniet perferendis.", + "byline": "By PETER BAKER", + "created_date": "2017-02-10T11:44:16-05:00", + "des_facet": [ + "Israeli Settlements", + "United States International Relations" + ], + "geo_facet": [ + "Israel", + "Jerusalem (Israel)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "The Jewish settlement Maale Adumim, in the West Bank.", + "copyright": "Dan Balilty for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Settlements/11Settlements-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "The Jewish settlement Maale Adumim, in the West Bank.", + "copyright": "Dan Balilty for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Settlements/11Settlements-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "The Jewish settlement Maale Adumim, in the West Bank.", + "copyright": "Dan Balilty for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Settlements/11Settlements-articleInline.jpg", + "width": 190 + }, + { + "caption": "The Jewish settlement Maale Adumim, in the West Bank.", + "copyright": "Dan Balilty for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Settlements/11Settlements-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "The Jewish settlement Maale Adumim, in the West Bank.", + "copyright": "Dan Balilty for The New York Times", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Settlements/11Settlements-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Trump, Donald J" + ], + "published_date": "2017-02-10T11:44:16-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kXdJua", + "subsection": "Middle East", + "title": "Qui nesciunt dignissimos cumque quas rerum eveniet perferendis.", + "updated_date": "2017-02-10T11:44:15-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/middleeast/trump-adopts-a-harder-line-on-israeli-settlements.html" + }, + { + "abstract": "Voluptatem dolores repellat deleniti qui porro quia.", + "byline": "By JULIE HIRSCHFELD DAVIS and PETER BAKER", + "created_date": "2017-02-10T09:35:46-05:00", + "des_facet": [ + "United States Politics and Government" + ], + "geo_facet": [], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "", + "copyright": "Doug Mills/The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10abe/10abe-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "", + "copyright": "Doug Mills/The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10abe/10abe-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "", + "copyright": "Doug Mills/The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10abe/10abe-articleInline.jpg", + "width": 190 + }, + { + "caption": "", + "copyright": "Doug Mills/The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10abe/10abe-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "", + "copyright": "Doug Mills/The New York Times", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10abe/10abe-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Abe, Shinzo", + "Trump, Donald J" + ], + "published_date": "2017-02-10T09:35:46-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kucFKP", + "subsection": "Asia Pacific", + "title": "Voluptatem dolores repellat deleniti qui porro quia.", + "updated_date": "2017-02-10T17:36:57-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/asia/trump-shinzo-abe-meeting.html" + }, + { + "abstract": "Fuga totam qui voluptas ut doloremque rerum.", + "byline": "By ALAN WONG", + "created_date": "2017-02-10T09:18:56-05:00", + "des_facet": [ + "Subways" + ], + "geo_facet": [ + "Hong Kong" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [], + "org_facet": [ + "MTR Corp" + ], + "per_facet": [], + "published_date": "2017-02-10T09:18:56-05:00", + "section": "World", + "short_url": "https://nyti.ms/2ktRCrM", + "subsection": "Asia Pacific", + "title": "Fuga totam qui voluptas ut doloremque rerum.", + "updated_date": "2017-02-10T13:23:01-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/asia/hong-kong-subway-fire.html" + }, + { + "abstract": "Esse dolor quam ipsum dolorem minima qui dolor aut.", + "byline": "By JANE PERLEZ", + "created_date": "2017-02-10T07:43:27-05:00", + "des_facet": [ + "United States International Relations" + ], + "geo_facet": [ + "China", + "United States" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "President Trump boarding Air Force One in Florida on Sunday. Mr. Trump’s reversal on Taiwan could mean a tougher negotiating position in Beijing on trade, North Korea and other issues.", + "copyright": "Stephen Crowley/The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11CHINA-1/11CHINA-1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "President Trump boarding Air Force One in Florida on Sunday. Mr. Trump’s reversal on Taiwan could mean a tougher negotiating position in Beijing on trade, North Korea and other issues.", + "copyright": "Stephen Crowley/The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11CHINA-1/11CHINA-1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "President Trump boarding Air Force One in Florida on Sunday. Mr. Trump’s reversal on Taiwan could mean a tougher negotiating position in Beijing on trade, North Korea and other issues.", + "copyright": "Stephen Crowley/The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11CHINA-1/11CHINA-1-articleInline.jpg", + "width": 190 + }, + { + "caption": "President Trump boarding Air Force One in Florida on Sunday. Mr. Trump’s reversal on Taiwan could mean a tougher negotiating position in Beijing on trade, North Korea and other issues.", + "copyright": "Stephen Crowley/The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11CHINA-1/11CHINA-1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "President Trump boarding Air Force One in Florida on Sunday. Mr. Trump’s reversal on Taiwan could mean a tougher negotiating position in Beijing on trade, North Korea and other issues.", + "copyright": "Stephen Crowley/The New York Times", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11CHINA-1/11CHINA-1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Trump, Donald J", + "Xi Jinping" + ], + "published_date": "2017-02-10T07:43:27-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kWjQyA", + "subsection": "Asia Pacific", + "title": "Esse dolor quam ipsum dolorem minima qui dolor aut.", + "updated_date": "2017-02-10T07:43:25-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/asia/trump-one-china-taiwan.html" + }, + { + "abstract": "Commodi fugit libero consectetur nisi dignissimos magni neque laborum beatae.", + "byline": "By THOMAS ERDBRINK", + "created_date": "2017-02-10T07:04:30-05:00", + "des_facet": [ + "Demonstrations, Protests and Riots", + "United States International Relations", + "vis-photo" + ], + "geo_facet": [ + "Iran" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Students waved the Iranian flag during a rally in Tehran on Friday to celebrate the anniversary of the 1979 Islamic Revolution.", + "copyright": "Ebrahim Noroozi/Associated Press", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Iran2/11Iran2-thumbStandard-v2.jpg", + "width": 75 + }, + { + "caption": "Students waved the Iranian flag during a rally in Tehran on Friday to celebrate the anniversary of the 1979 Islamic Revolution.", + "copyright": "Ebrahim Noroozi/Associated Press", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Iran2/11Iran2-thumbLarge-v2.jpg", + "width": 150 + }, + { + "caption": "Students waved the Iranian flag during a rally in Tehran on Friday to celebrate the anniversary of the 1979 Islamic Revolution.", + "copyright": "Ebrahim Noroozi/Associated Press", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Iran2/11Iran2-articleInline-v2.jpg", + "width": 190 + }, + { + "caption": "Students waved the Iranian flag during a rally in Tehran on Friday to celebrate the anniversary of the 1979 Islamic Revolution.", + "copyright": "Ebrahim Noroozi/Associated Press", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Iran2/11Iran2-mediumThreeByTwo210-v2.jpg", + "width": 210 + }, + { + "caption": "Students waved the Iranian flag during a rally in Tehran on Friday to celebrate the anniversary of the 1979 Islamic Revolution.", + "copyright": "Ebrahim Noroozi/Associated Press", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Iran2/11Iran2-superJumbo-v2.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Trump, Donald J" + ], + "published_date": "2017-02-10T07:04:30-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kWk74K", + "subsection": "Middle East", + "title": "Commodi fugit libero consectetur nisi dignissimos magni neque laborum beatae.", + "updated_date": "2017-02-10T07:04:29-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/middleeast/iran-revolution-anniversary.html" + }, + { + "abstract": "Quia ut ab et fuga in sunt.", + "byline": "By AURELIEN BREEDEN", + "created_date": "2017-02-10T07:01:58-05:00", + "des_facet": [ + "Terrorism", + "TATP" + ], + "geo_facet": [ + "Montpellier (France)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "The police patroled the streets in Marseillan, which is southwest of Montpellier, France, on Friday. Four people were arrested by the antiterrorism police in Montpellier on suspicion of planning an attack. Montpellier and Marseillan are on France’s Mediterranean coast.", + "copyright": "Sylvain Thomas/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Montpellier/11Montpellier-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "The police patroled the streets in Marseillan, which is southwest of Montpellier, France, on Friday. Four people were arrested by the antiterrorism police in Montpellier on suspicion of planning an attack. Montpellier and Marseillan are on France’s Mediterranean coast.", + "copyright": "Sylvain Thomas/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Montpellier/11Montpellier-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "The police patroled the streets in Marseillan, which is southwest of Montpellier, France, on Friday. Four people were arrested by the antiterrorism police in Montpellier on suspicion of planning an attack. Montpellier and Marseillan are on France’s Mediterranean coast.", + "copyright": "Sylvain Thomas/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Montpellier/12Montepellier-articleInline.jpg", + "width": 190 + }, + { + "caption": "The police patroled the streets in Marseillan, which is southwest of Montpellier, France, on Friday. Four people were arrested by the antiterrorism police in Montpellier on suspicion of planning an attack. Montpellier and Marseillan are on France’s Mediterranean coast.", + "copyright": "Sylvain Thomas/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Montpellier/11Montpellier-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "The police patroled the streets in Marseillan, which is southwest of Montpellier, France, on Friday. Four people were arrested by the antiterrorism police in Montpellier on suspicion of planning an attack. Montpellier and Marseillan are on France’s Mediterranean coast.", + "copyright": "Sylvain Thomas/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1364, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11Montpellier/12Montepellier-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-10T07:01:58-05:00", + "section": "World", + "short_url": "https://nyti.ms/2ktNiZv", + "subsection": "Europe", + "title": "Quia ut ab et fuga in sunt.", + "updated_date": "2017-02-10T08:29:51-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/europe/france-arrest-montpellier.html" + }, + { + "abstract": "Doloribus dicta qui ad.", + "byline": "By JASON HOROWITZ", + "created_date": "2017-02-10T05:00:35-05:00", + "des_facet": [ + "United States Politics and Government", + "Fringe Groups and Movements" + ], + "geo_facet": [ + "Europe" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Stephen K. Bannon referred to the Italian philosopher Julius Evola in a Vatican speech in 2014.", + "copyright": "Todd Heisler/The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/12/us/21Evola1/10Evola1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Stephen K. Bannon referred to the Italian philosopher Julius Evola in a Vatican speech in 2014.", + "copyright": "Todd Heisler/The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/12/us/21Evola1/10Evola1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Stephen K. Bannon referred to the Italian philosopher Julius Evola in a Vatican speech in 2014.", + "copyright": "Todd Heisler/The New York Times", + "format": "Normal", + "height": 123, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/12/us/21Evola1/10Evola1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Stephen K. Bannon referred to the Italian philosopher Julius Evola in a Vatican speech in 2014.", + "copyright": "Todd Heisler/The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/12/us/21Evola1/10Evola1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Stephen K. Bannon referred to the Italian philosopher Julius Evola in a Vatican speech in 2014.", + "copyright": "Todd Heisler/The New York Times", + "format": "superJumbo", + "height": 1321, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/12/us/21Evola1/10Evola1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Breitbart News Network LLC" + ], + "per_facet": [ + "Evola, Julius (1898-1974)", + "Bannon, Stephen K" + ], + "published_date": "2017-02-10T05:00:35-05:00", + "section": "World", + "short_url": "https://nyti.ms/2ktwFNw", + "subsection": "Europe", + "title": "Doloribus dicta qui ad.", + "updated_date": "2017-02-10T05:00:34-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/europe/bannon-vatican-julius-evola-fascism.html" + }, + { + "abstract": "Pariatur in officia iusto consequatur libero tenetur.", + "byline": "By MEGAN SPECIA and YARA BISHARA", + "created_date": "2017-02-10T03:22:14-05:00", + "des_facet": [ + "Demonstrations, Protests and Riots", + "Corruption (Institutional)" + ], + "geo_facet": [ + "Romania" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-10T03:22:14-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kaRpZu", + "subsection": "Europe", + "title": "Pariatur in officia iusto consequatur libero tenetur.", + "updated_date": "2017-02-10T03:22:14-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/europe/romania-bucharest-corruption-protests.html" + }, + { + "abstract": "Similique possimus corrupti velit et corporis repellendus.", + "byline": "By GERRY MULLANY", + "created_date": "2017-02-10T01:10:33-05:00", + "des_facet": [ + "Whales and Whaling", + "vis-multimedia" + ], + "geo_facet": [ + "South Island (New Zealand)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Volunteers trying to keep alive some of the pilot whales beached at the northern tip of New Zealand’s South Island on Friday. Officials said it was one of the country’s largest whale strandings.", + "copyright": "Ross Wearing/Reuters", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11whales-5/11whales-5-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Volunteers trying to keep alive some of the pilot whales beached at the northern tip of New Zealand’s South Island on Friday. Officials said it was one of the country’s largest whale strandings.", + "copyright": "Ross Wearing/Reuters", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11whales-5/11whales-5-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Volunteers trying to keep alive some of the pilot whales beached at the northern tip of New Zealand’s South Island on Friday. Officials said it was one of the country’s largest whale strandings.", + "copyright": "Ross Wearing/Reuters", + "format": "Normal", + "height": 125, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11whales-5/11whales-5-articleInline.jpg", + "width": 190 + }, + { + "caption": "Volunteers trying to keep alive some of the pilot whales beached at the northern tip of New Zealand’s South Island on Friday. Officials said it was one of the country’s largest whale strandings.", + "copyright": "Ross Wearing/Reuters", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11whales-5/11whales-5-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Volunteers trying to keep alive some of the pilot whales beached at the northern tip of New Zealand’s South Island on Friday. Officials said it was one of the country’s largest whale strandings.", + "copyright": "Ross Wearing/Reuters", + "format": "superJumbo", + "height": 987, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/11/world/11whales-5/11whales-5-superJumbo.jpg", + "width": 1500 + } + ], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-10T01:10:33-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kV14YE", + "subsection": "Australia", + "title": "Similique possimus corrupti velit et corporis repellendus.", + "updated_date": "2017-02-10T01:10:32-05:00", + "url": "https://www.nytimes.com/2017/02/10/world/australia/whales-beach-new-zealand.html" + }, + { + "abstract": "Placeat beatae distinctio id eos dolorem.", + "byline": "By SAM ROBERTS", + "created_date": "2017-02-09T19:17:18-05:00", + "des_facet": [ + "Statistics", + "Population", + "Deaths (Obituaries)" + ], + "geo_facet": [], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Hans Rosling giving a TED Talk in 2010. He was a self-described “edutainer.”", + "copyright": "Katie Orlinsky for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10rosling-obit-1/10rosling-obit-1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Hans Rosling giving a TED Talk in 2010. He was a self-described “edutainer.”", + "copyright": "Katie Orlinsky for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10rosling-obit-1/10rosling-obit-1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Hans Rosling giving a TED Talk in 2010. He was a self-described “edutainer.”", + "copyright": "Katie Orlinsky for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10rosling-obit-1/10rosling-obit-1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Hans Rosling giving a TED Talk in 2010. He was a self-described “edutainer.”", + "copyright": "Katie Orlinsky for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10rosling-obit-1/10rosling-obit-1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Hans Rosling giving a TED Talk in 2010. He was a self-described “edutainer.”", + "copyright": "Katie Orlinsky for The New York Times", + "format": "superJumbo", + "height": 1368, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10rosling-obit-1/10rosling-obit-1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Rosling, Hans (1948-2017)" + ], + "published_date": "2017-02-09T19:17:18-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kUhcJR", + "subsection": "Europe", + "title": "Placeat beatae distinctio id eos dolorem.", + "updated_date": "2017-02-09T19:17:17-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/europe/hans-rosling-dead-statistician.html" + }, + { + "abstract": "Libero ea atque ex mollitia sit voluptatibus ducimus earum esse.", + "byline": "By DECLAN WALSH and NOUR YOUSSEF", + "created_date": "2017-02-09T17:04:18-05:00", + "des_facet": [ + "Human Rights and Human Rights Violations", + "Shutdowns (Institutional)" + ], + "geo_facet": [ + "Egypt", + "Cairo (Egypt)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Magda Adly, right, of Al Nadeem Center for Rehabilitation of Victims of Violence at a news conference in Cairo last year. “I don’t understand how a regime with an army and a police force can be scared of 20 activists,” Ms. Adly said in a phone interview after the Egyptian government shut down the group’s offices.", + "copyright": "Mohamed El Raai/Associated Press", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10egypt/10egypt-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Magda Adly, right, of Al Nadeem Center for Rehabilitation of Victims of Violence at a news conference in Cairo last year. “I don’t understand how a regime with an army and a police force can be scared of 20 activists,” Ms. Adly said in a phone interview after the Egyptian government shut down the group’s offices.", + "copyright": "Mohamed El Raai/Associated Press", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10egypt/10egypt-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Magda Adly, right, of Al Nadeem Center for Rehabilitation of Victims of Violence at a news conference in Cairo last year. “I don’t understand how a regime with an army and a police force can be scared of 20 activists,” Ms. Adly said in a phone interview after the Egyptian government shut down the group’s offices.", + "copyright": "Mohamed El Raai/Associated Press", + "format": "Normal", + "height": 132, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10egypt/10egypt-articleInline.jpg", + "width": 190 + }, + { + "caption": "Magda Adly, right, of Al Nadeem Center for Rehabilitation of Victims of Violence at a news conference in Cairo last year. “I don’t understand how a regime with an army and a police force can be scared of 20 activists,” Ms. Adly said in a phone interview after the Egyptian government shut down the group’s offices.", + "copyright": "Mohamed El Raai/Associated Press", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10egypt/10egypt-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Magda Adly, right, of Al Nadeem Center for Rehabilitation of Victims of Violence at a news conference in Cairo last year. “I don’t understand how a regime with an army and a police force can be scared of 20 activists,” Ms. Adly said in a phone interview after the Egyptian government shut down the group’s offices.", + "copyright": "Mohamed El Raai/Associated Press", + "format": "superJumbo", + "height": 1422, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10egypt/10egypt-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Sisi, Abdel Fattah el-" + ], + "published_date": "2017-02-09T17:04:18-05:00", + "section": "World", + "short_url": "https://nyti.ms/2k9fPCo", + "subsection": "Middle East", + "title": "Libero ea atque ex mollitia sit voluptatibus ducimus earum esse.", + "updated_date": "2017-02-09T17:04:17-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/middleeast/widening-crackdown-egypt-shutters-group-that-treats-torture-victims.html" + }, + { + "abstract": "Voluptatem mollitia doloribus perspiciatis aperiam nisi omnis.", + "byline": "By MOTOKO RICH", + "created_date": "2017-02-09T16:31:03-05:00", + "des_facet": [], + "geo_facet": [ + "Japan" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Prime Minister Shinzo Abe of Japan during a ceremony at a base north of Tokyo last year. He will meet with President Trump on Friday in the Oval Office.", + "copyright": "Eugene Hoshiko/Associated Press", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/asia/10japan3/10japan3-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Prime Minister Shinzo Abe of Japan during a ceremony at a base north of Tokyo last year. He will meet with President Trump on Friday in the Oval Office.", + "copyright": "Eugene Hoshiko/Associated Press", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/asia/10japan3/10japan3-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Prime Minister Shinzo Abe of Japan during a ceremony at a base north of Tokyo last year. He will meet with President Trump on Friday in the Oval Office.", + "copyright": "Eugene Hoshiko/Associated Press", + "format": "Normal", + "height": 130, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/asia/10japan3/10japan3-articleInline.jpg", + "width": 190 + }, + { + "caption": "Prime Minister Shinzo Abe of Japan during a ceremony at a base north of Tokyo last year. He will meet with President Trump on Friday in the Oval Office.", + "copyright": "Eugene Hoshiko/Associated Press", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/asia/10japan3/10japan3-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Prime Minister Shinzo Abe of Japan during a ceremony at a base north of Tokyo last year. He will meet with President Trump on Friday in the Oval Office.", + "copyright": "Eugene Hoshiko/Associated Press", + "format": "superJumbo", + "height": 1405, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/asia/10japan3/10japan3-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Abe, Shinzo", + "Trump, Donald J" + ], + "published_date": "2017-02-09T16:31:03-05:00", + "section": "World", + "short_url": "https://nyti.ms/2k910js", + "subsection": "Asia Pacific", + "title": "Voluptatem mollitia doloribus perspiciatis aperiam nisi omnis.", + "updated_date": "2017-02-09T16:31:02-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/asia/trump-abe-meeting-japan-trade-defense.html" + }, + { + "abstract": "Ut dignissimos corrupti sint est aut qui.", + "byline": "By AURELIEN BREEDEN", + "created_date": "2017-02-09T15:49:31-05:00", + "des_facet": [ + "Eiffel Tower (Paris)", + "Terrorism", + "Travel and Vacations" + ], + "geo_facet": [ + "Paris (France)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Soldiers on patrol at the base of the Eiffel Tower in January 2016.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10eiffel1/10eiffel1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Soldiers on patrol at the base of the Eiffel Tower in January 2016.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10eiffel1/10eiffel1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Soldiers on patrol at the base of the Eiffel Tower in January 2016.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "Normal", + "height": 124, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10eiffel1/10eiffel1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Soldiers on patrol at the base of the Eiffel Tower in January 2016.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10eiffel1/10eiffel1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Soldiers on patrol at the base of the Eiffel Tower in January 2016.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "superJumbo", + "height": 1337, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10eiffel1/10eiffel1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Martins, Jean-Francois" + ], + "published_date": "2017-02-09T15:49:31-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kTL60H", + "subsection": "Europe", + "title": "Ut dignissimos corrupti sint est aut qui.", + "updated_date": "2017-02-09T15:49:30-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/europe/france-eiffel-tower-security-paris.html" + }, + { + "abstract": "Quis accusamus quia consequatur corporis beatae.", + "byline": "By NIDA NAJAR", + "created_date": "2017-02-09T15:00:06-05:00", + "des_facet": [ + "Politics and Government" + ], + "geo_facet": [ + "Maldives" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Former President Mohamed Nasheed of the Maldives in Colombo, Sri Lanka, on Thursday. “I can contest, I am a Maldives national, and I am free — I must be free to contest,” he told reporters.", + "copyright": "Ishara S. Kodikara/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Maldives/10Maldives-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Former President Mohamed Nasheed of the Maldives in Colombo, Sri Lanka, on Thursday. “I can contest, I am a Maldives national, and I am free — I must be free to contest,” he told reporters.", + "copyright": "Ishara S. Kodikara/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Maldives/10Maldives-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Former President Mohamed Nasheed of the Maldives in Colombo, Sri Lanka, on Thursday. “I can contest, I am a Maldives national, and I am free — I must be free to contest,” he told reporters.", + "copyright": "Ishara S. Kodikara/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Maldives/10Maldives-articleInline.jpg", + "width": 190 + }, + { + "caption": "Former President Mohamed Nasheed of the Maldives in Colombo, Sri Lanka, on Thursday. “I can contest, I am a Maldives national, and I am free — I must be free to contest,” he told reporters.", + "copyright": "Ishara S. Kodikara/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Maldives/10Maldives-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Former President Mohamed Nasheed of the Maldives in Colombo, Sri Lanka, on Thursday. “I can contest, I am a Maldives national, and I am free — I must be free to contest,” he told reporters.", + "copyright": "Ishara S. Kodikara/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1211, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Maldives/10Maldives-superJumbo.jpg", + "width": 1817 + } + ], + "org_facet": [], + "per_facet": [ + "Nasheed, Mohamed", + "Yameen, Abdulla" + ], + "published_date": "2017-02-09T15:00:06-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kTpthg", + "subsection": "Asia Pacific", + "title": "Quis accusamus quia consequatur corporis beatae.", + "updated_date": "2017-02-09T15:00:05-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/asia/mohamed-nasheed-maldives.html" + }, + { + "abstract": "Nihil repudiandae tempore est quod iusto.", + "byline": "By ANNE BARNARD", + "created_date": "2017-02-09T14:10:33-05:00", + "des_facet": [], + "geo_facet": [ + "Syria", + "Turkey", + "Russia" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Rebel fighters on the outskirts of the northern Syrian town of Al Bab on Wednesday.", + "copyright": "Khalil Ashawi/Reuters", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10syria1/10syria1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Rebel fighters on the outskirts of the northern Syrian town of Al Bab on Wednesday.", + "copyright": "Khalil Ashawi/Reuters", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10syria1/10syria1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Rebel fighters on the outskirts of the northern Syrian town of Al Bab on Wednesday.", + "copyright": "Khalil Ashawi/Reuters", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10syria1/10syria1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Rebel fighters on the outskirts of the northern Syrian town of Al Bab on Wednesday.", + "copyright": "Khalil Ashawi/Reuters", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10syria1/10syria1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Rebel fighters on the outskirts of the northern Syrian town of Al Bab on Wednesday.", + "copyright": "Khalil Ashawi/Reuters", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10syria1/10syria1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Islamic State in Iraq and Syria (ISIS)" + ], + "per_facet": [ + "Putin, Vladimir V", + "Erdogan, Recep Tayyip" + ], + "published_date": "2017-02-09T14:10:33-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kTpmlK", + "subsection": "Middle East", + "title": "Nihil repudiandae tempore est quod iusto.", + "updated_date": "2017-02-09T14:10:32-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/middleeast/russian-airstrike-kills-3-turkish-soldiers-in-syria-in-friendly-fire.html" + }, + { + "abstract": "Culpa mollitia tempore necessitatibus facere quam ipsam.", + "byline": "By NIKI KITSANTONIS", + "created_date": "2017-02-09T13:20:45-05:00", + "des_facet": [ + "Refugees and Displaced Persons", + "Middle East and Africa Migrant Crisis", + "Immigration and Emigration", + "Human Rights and Human Rights Violations", + "Asylum, Right of", + "Hunger Strikes" + ], + "geo_facet": [ + "Greece", + "Lesbos (Greece)" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "A baseball stadium built for the 2004 Summer Olympics in Athens is now filled with tents for migrants.", + "copyright": "Louisa Gouliamaki/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Migrants1/10Migrants1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "A baseball stadium built for the 2004 Summer Olympics in Athens is now filled with tents for migrants.", + "copyright": "Louisa Gouliamaki/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Migrants1/10Migrants1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "A baseball stadium built for the 2004 Summer Olympics in Athens is now filled with tents for migrants.", + "copyright": "Louisa Gouliamaki/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 126, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Migrants1/10Migrants1-articleInline.jpg", + "width": 190 + }, + { + "caption": "A baseball stadium built for the 2004 Summer Olympics in Athens is now filled with tents for migrants.", + "copyright": "Louisa Gouliamaki/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Migrants1/10Migrants1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "A baseball stadium built for the 2004 Summer Olympics in Athens is now filled with tents for migrants.", + "copyright": "Louisa Gouliamaki/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1363, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Migrants1/10Migrants1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "Amnesty International", + "United Nations High Commission for Refugees" + ], + "per_facet": [], + "published_date": "2017-02-09T13:20:45-05:00", + "section": "World", + "short_url": "https://nyti.ms/2k8pYzp", + "subsection": "Europe", + "title": "Culpa mollitia tempore necessitatibus facere quam ipsam.", + "updated_date": "2017-02-09T13:20:44-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/europe/greece-migrant-crisis.html" + }, + { + "abstract": "Recusandae nulla non odit repellat.", + "byline": "By FRANCOIS ESSOMBA and DIONNE SEARCEY", + "created_date": "2017-02-09T13:06:28-05:00", + "des_facet": [ + "Demonstrations, Protests and Riots", + "Language and Languages", + "English Language" + ], + "geo_facet": [ + "Cameroon" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [], + "org_facet": [ + "Amnesty International" + ], + "per_facet": [], + "published_date": "2017-02-09T13:06:28-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kT8YSj", + "subsection": "Africa", + "title": "Recusandae nulla non odit repellat.", + "updated_date": "2017-02-09T13:06:27-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/africa/a-bilingual-cameroon-teeters-after-english-speakers-protest-treatment.html" + }, + { + "abstract": "Odio rem quas itaque fugiat repudiandae sed molestias.", + "byline": "By PETER BAKER and MARK LANDLER", + "created_date": "2017-02-09T12:51:59-05:00", + "des_facet": [ + "Palestinians", + "United States International Relations", + "United States Politics and Government", + "International Relations" + ], + "geo_facet": [ + "Israel", + "Jordan" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "An Israeli settlement in the West Bank. President Trump initially presented himself as an unstinting supporter of new settlement construction, but he has tempered that position somewhat.", + "copyright": "Dan Balilty for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10diplo5-web/10diplo5-web-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "An Israeli settlement in the West Bank. President Trump initially presented himself as an unstinting supporter of new settlement construction, but he has tempered that position somewhat.", + "copyright": "Dan Balilty for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10diplo5-web/10diplo5-web-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "An Israeli settlement in the West Bank. President Trump initially presented himself as an unstinting supporter of new settlement construction, but he has tempered that position somewhat.", + "copyright": "Dan Balilty for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10diplo5-web/10diplo5-web-articleInline.jpg", + "width": 190 + }, + { + "caption": "An Israeli settlement in the West Bank. President Trump initially presented himself as an unstinting supporter of new settlement construction, but he has tempered that position somewhat.", + "copyright": "Dan Balilty for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10diplo5-web/10diplo5-web-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "An Israeli settlement in the West Bank. President Trump initially presented himself as an unstinting supporter of new settlement construction, but he has tempered that position somewhat.", + "copyright": "Dan Balilty for The New York Times", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10diplo5-web/10diplo5-web-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Trump, Donald J", + "Netanyahu, Benjamin", + "Abdullah II, King of Jordan", + "Kushner, Jared" + ], + "published_date": "2017-02-09T12:51:59-05:00", + "section": "World", + "short_url": "https://nyti.ms/2k8rCkp", + "subsection": "Middle East", + "title": "Odio rem quas itaque fugiat repudiandae sed molestias.", + "updated_date": "2017-02-09T12:51:57-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/middleeast/trump-arabs-palestinians-israel.html" + }, + { + "abstract": "Quibusdam necessitatibus quia aut.", + "byline": "By DAN BILEFSKY", + "created_date": "2017-02-09T12:39:20-05:00", + "des_facet": [ + "Prisons and Prisoners", + "Renting and Leasing (Real Estate)" + ], + "geo_facet": [ + "Netherlands" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "De Koepel prison in Haarlem, the Netherlands, which recently housed migrants.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Dutch1/10Dutch1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "De Koepel prison in Haarlem, the Netherlands, which recently housed migrants.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Dutch1/10Dutch1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "De Koepel prison in Haarlem, the Netherlands, which recently housed migrants.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Dutch1/10Dutch1-articleInline.jpg", + "width": 190 + }, + { + "caption": "De Koepel prison in Haarlem, the Netherlands, which recently housed migrants.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Dutch1/10Dutch1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "De Koepel prison in Haarlem, the Netherlands, which recently housed migrants.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "superJumbo", + "height": 1364, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Dutch1/10Dutch1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Rutte, Mark" + ], + "published_date": "2017-02-09T12:39:20-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kT4BGX", + "subsection": "Europe", + "title": "Quibusdam necessitatibus quia aut.", + "updated_date": "2017-02-10T05:20:10-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/europe/netherlands-prisons-shortage.html" + }, + { + "abstract": "Tenetur quia hic debitis non.", + "byline": "By KIMIKO de FREYTAS-TAMURA", + "created_date": "2017-02-09T12:36:49-05:00", + "des_facet": [ + "Pride and Prejudice (Book)", + "Books and Literature" + ], + "geo_facet": [], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Colin Firth, right, in his turn as Mr. Darcy, and an illustration of what his character may have looked like in the 19th century.", + "copyright": "Nick Hardcastle, left, BBC, right", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/arts/10darcy_xp/10darcy_xp-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Colin Firth, right, in his turn as Mr. Darcy, and an illustration of what his character may have looked like in the 19th century.", + "copyright": "Nick Hardcastle, left, BBC, right", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/arts/10darcy_xp/10darcy_xp-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Colin Firth, right, in his turn as Mr. Darcy, and an illustration of what his character may have looked like in the 19th century.", + "copyright": "Nick Hardcastle, left, BBC, right", + "format": "Normal", + "height": 108, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/arts/10darcy_xp/10darcy_xp-articleInline.jpg", + "width": 190 + }, + { + "caption": "Colin Firth, right, in his turn as Mr. Darcy, and an illustration of what his character may have looked like in the 19th century.", + "copyright": "Nick Hardcastle, left, BBC, right", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/arts/10darcy_xp/10darcy_xp-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Colin Firth, right, in his turn as Mr. Darcy, and an illustration of what his character may have looked like in the 19th century.", + "copyright": "Nick Hardcastle, left, BBC, right", + "format": "superJumbo", + "height": 1163, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/arts/10darcy_xp/10darcy_xp-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Austen, Jane", + "Firth, Colin", + "Sutherland, John (1938- )", + "Vickery, Amanda" + ], + "published_date": "2017-02-09T12:36:49-05:00", + "section": "Books", + "short_url": "https://nyti.ms/2k8g1lG", + "subsection": "", + "title": "Tenetur quia hic debitis non.", + "updated_date": "2017-02-10T11:29:20-05:00", + "url": "https://www.nytimes.com/2017/02/09/books/colin-firth-mr-darcy.html" + }, + { + "abstract": "Possimus ut rem rerum saepe inventore recusandae accusantium consequatur nostrum.", + "byline": "By MICHAEL R. GORDON", + "created_date": "2017-02-09T12:17:23-05:00", + "des_facet": [ + "United States Defense and Military Forces", + "Afghanistan War (2001-14)" + ], + "geo_facet": [], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Gen. John W. Nicholson Jr. testified before the Senate Armed Services Committee on Thursday.", + "copyright": "Alex Wong/Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10military/10military-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Gen. John W. Nicholson Jr. testified before the Senate Armed Services Committee on Thursday.", + "copyright": "Alex Wong/Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10military/10military-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Gen. John W. Nicholson Jr. testified before the Senate Armed Services Committee on Thursday.", + "copyright": "Alex Wong/Getty Images", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10military/10military-articleInline.jpg", + "width": 190 + }, + { + "caption": "Gen. John W. Nicholson Jr. testified before the Senate Armed Services Committee on Thursday.", + "copyright": "Alex Wong/Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10military/10military-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Gen. John W. Nicholson Jr. testified before the Senate Armed Services Committee on Thursday.", + "copyright": "Alex Wong/Getty Images", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/us/10military/10military-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [ + "North Atlantic Treaty Organization" + ], + "per_facet": [ + "Nicholson, John W Jr (1957- )", + "Trump, Donald J" + ], + "published_date": "2017-02-09T12:17:23-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kT4RW4", + "subsection": "Asia Pacific", + "title": "Possimus ut rem rerum saepe inventore recusandae accusantium consequatur nostrum.", + "updated_date": "2017-02-09T19:44:39-05:00", + "url": "https://www.nytimes.com/2017/02/09/us/politics/us-afghanistan-troops.html" + }, + { + "abstract": "Aut magni ipsa sint itaque facere tempore.", + "byline": "By ALISON SMALE", + "created_date": "2017-02-09T11:41:41-05:00", + "des_facet": [ + "Terrorism" + ], + "geo_facet": [ + "Germany", + "Lower Saxony" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Boris Pistorius, the interior minister of the German state of Lower Saxony, said the raids were “a very important success” in the battle against terrorism.", + "copyright": "Focke Strangmann/European Pressphoto Agency", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Germany1/10Germany1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Boris Pistorius, the interior minister of the German state of Lower Saxony, said the raids were “a very important success” in the battle against terrorism.", + "copyright": "Focke Strangmann/European Pressphoto Agency", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Germany1/10Germany1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Boris Pistorius, the interior minister of the German state of Lower Saxony, said the raids were “a very important success” in the battle against terrorism.", + "copyright": "Focke Strangmann/European Pressphoto Agency", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Germany1/10Germany1-articleInline.jpg", + "width": 190 + }, + { + "caption": "Boris Pistorius, the interior minister of the German state of Lower Saxony, said the raids were “a very important success” in the battle against terrorism.", + "copyright": "Focke Strangmann/European Pressphoto Agency", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Germany1/10Germany1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Boris Pistorius, the interior minister of the German state of Lower Saxony, said the raids were “a very important success” in the battle against terrorism.", + "copyright": "Focke Strangmann/European Pressphoto Agency", + "format": "superJumbo", + "height": 1365, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10Germany1/10Germany1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-09T11:41:41-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kSWXwa", + "subsection": "Europe", + "title": "Aut magni ipsa sint itaque facere tempore.", + "updated_date": "2017-02-09T11:41:40-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/europe/germany-arrests.html" + }, + { + "abstract": "Rerum id ea.", + "byline": "By MARTIN de BOURMONT", + "created_date": "2017-02-09T09:30:23-05:00", + "des_facet": [ + "Romani People", + "Education (K-12)", + "Evictions" + ], + "geo_facet": [ + "France" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "A Roma family at a camp near Noisy-le-Grand, outside Paris. While there is consensus that public education could help integrate a Roma population that has long faced systematic discrimination, the obstacles remain formidable.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/01/18/world/18Roma1/18Roma1-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "A Roma family at a camp near Noisy-le-Grand, outside Paris. While there is consensus that public education could help integrate a Roma population that has long faced systematic discrimination, the obstacles remain formidable.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/01/18/world/18Roma1/18Roma1-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "A Roma family at a camp near Noisy-le-Grand, outside Paris. While there is consensus that public education could help integrate a Roma population that has long faced systematic discrimination, the obstacles remain formidable.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/01/18/world/18Roma1/18Roma1-articleInline.jpg", + "width": 190 + }, + { + "caption": "A Roma family at a camp near Noisy-le-Grand, outside Paris. While there is consensus that public education could help integrate a Roma population that has long faced systematic discrimination, the obstacles remain formidable.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/01/18/world/18Roma1/18Roma1-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "A Roma family at a camp near Noisy-le-Grand, outside Paris. While there is consensus that public education could help integrate a Roma population that has long faced systematic discrimination, the obstacles remain formidable.", + "copyright": "Dmitry Kostyukov for The New York Times", + "format": "superJumbo", + "height": 1367, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/01/18/world/18Roma1/18Roma1-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [], + "published_date": "2017-02-09T09:30:23-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kSlwZT", + "subsection": "Europe", + "title": "Rerum id ea.", + "updated_date": "2017-02-09T09:30:22-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/europe/for-roma-in-france-education-is-an-elusive-path-to-integration.html" + }, + { + "abstract": "Voluptate dicta et nam eveniet nobis.", + "byline": "By THOMAS ERDBRINK", + "created_date": "2017-02-09T08:33:51-05:00", + "des_facet": [ + "Executive Orders and Memorandums", + "Demonstrations, Protests and Riots", + "Flags, Emblems and Insignia" + ], + "geo_facet": [ + "Iran", + "United States" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "Iranians burned an American flag outside the former United States Embassy in Tehran in 2011.", + "copyright": "Atta Kenare/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10iran-1486658636911/10iran-1486658636911-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "Iranians burned an American flag outside the former United States Embassy in Tehran in 2011.", + "copyright": "Atta Kenare/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10iran-1486658636911/10iran-1486658636911-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "Iranians burned an American flag outside the former United States Embassy in Tehran in 2011.", + "copyright": "Atta Kenare/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 118, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10iran-1486658636911/10iran-1486658636911-articleInline.jpg", + "width": 190 + }, + { + "caption": "Iranians burned an American flag outside the former United States Embassy in Tehran in 2011.", + "copyright": "Atta Kenare/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10iran-1486658636911/10iran-1486658636911-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "Iranians burned an American flag outside the former United States Embassy in Tehran in 2011.", + "copyright": "Atta Kenare/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1267, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10iran-1486658636911/10iran-1486658636911-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Trump, Donald J" + ], + "published_date": "2017-02-09T08:33:51-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kSpnpV", + "subsection": "Middle East", + "title": "Voluptate dicta et nam eveniet nobis.", + "updated_date": "2017-02-09T15:24:39-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/middleeast/iran-trump-burn-flag.html" + }, + { + "abstract": "Eligendi non aliquam fugiat sed.", + "byline": "By MARK LANDLER and MICHAEL FORSYTHE", + "created_date": "2017-02-09T05:47:54-05:00", + "des_facet": [ + "United States International Relations", + "United States Politics and Government" + ], + "geo_facet": [ + "China" + ], + "item_type": "Article", + "kicker": "", + "material_type_facet": "", + "multimedia": [ + { + "caption": "President Xi Jinping of China in Lima, Peru, last year. The fact that President Trump and Mr. Xi had not talked since Mr. Trump took office in January had drawn increasing scrutiny.", + "copyright": "Cris Bouroncle/Agence France-Presse — Getty Images", + "format": "Standard Thumbnail", + "height": 75, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10chinatrump/10chinatrump-thumbStandard.jpg", + "width": 75 + }, + { + "caption": "President Xi Jinping of China in Lima, Peru, last year. The fact that President Trump and Mr. Xi had not talked since Mr. Trump took office in January had drawn increasing scrutiny.", + "copyright": "Cris Bouroncle/Agence France-Presse — Getty Images", + "format": "thumbLarge", + "height": 150, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10chinatrump/10chinatrump-thumbLarge.jpg", + "width": 150 + }, + { + "caption": "President Xi Jinping of China in Lima, Peru, last year. The fact that President Trump and Mr. Xi had not talked since Mr. Trump took office in January had drawn increasing scrutiny.", + "copyright": "Cris Bouroncle/Agence France-Presse — Getty Images", + "format": "Normal", + "height": 127, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10chinatrump/10chinatrump-articleInline.jpg", + "width": 190 + }, + { + "caption": "President Xi Jinping of China in Lima, Peru, last year. The fact that President Trump and Mr. Xi had not talked since Mr. Trump took office in January had drawn increasing scrutiny.", + "copyright": "Cris Bouroncle/Agence France-Presse — Getty Images", + "format": "mediumThreeByTwo210", + "height": 140, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10chinatrump/10chinatrump-mediumThreeByTwo210.jpg", + "width": 210 + }, + { + "caption": "President Xi Jinping of China in Lima, Peru, last year. The fact that President Trump and Mr. Xi had not talked since Mr. Trump took office in January had drawn increasing scrutiny.", + "copyright": "Cris Bouroncle/Agence France-Presse — Getty Images", + "format": "superJumbo", + "height": 1366, + "subtype": "photo", + "type": "image", + "url": "https://static01.nyt.com/images/2017/02/10/world/10chinatrump/10chinatrump-superJumbo.jpg", + "width": 2048 + } + ], + "org_facet": [], + "per_facet": [ + "Xi Jinping", + "Trump, Donald J" + ], + "published_date": "2017-02-09T05:47:54-05:00", + "section": "World", + "short_url": "https://nyti.ms/2kRYq5F", + "subsection": "Asia Pacific", + "title": "Eligendi non aliquam fugiat sed.", + "updated_date": "2017-02-10T00:42:55-05:00", + "url": "https://www.nytimes.com/2017/02/09/world/asia/donald-trump-china-xi-jinping-letter.html" + } +] diff --git a/news-site-v/src/index.css b/news-site-v/src/index.css new file mode 100644 index 0000000..65dfec8 --- /dev/null +++ b/news-site-v/src/index.css @@ -0,0 +1,4 @@ +body { + margin: 0; + font-family: sans-serif; +} diff --git a/news-site-v/src/index.js b/news-site-v/src/index.js new file mode 100644 index 0000000..9b9d64c --- /dev/null +++ b/news-site-v/src/index.js @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import App from './App' +import './index.css' +import 'bootstrap/dist/css/bootstrap.css' + +ReactDOM.render( + , + document.getElementById('root') +) diff --git a/news-site-v/src/pages/AddArticlePage.js b/news-site-v/src/pages/AddArticlePage.js new file mode 100644 index 0000000..2c60fc2 --- /dev/null +++ b/news-site-v/src/pages/AddArticlePage.js @@ -0,0 +1,56 @@ +import React, { useState} from 'react'; +import { Form, Button, Alert } from 'react-bootstrap'; +import { AddArticle } from '../api/ArticlesAPI'; + + +const AddArticlePage = () => { + const [message, setMessage] = useState(null); + const [isError, setIsError] = useState(false); + + const handleFormSubmit = (event) => { + event.preventDefault(); + // console.log(event) + const articleObject = { + 'title': event.target.title.value, + 'byline': event.target.byline.value, + 'abstract': event.target.abstract.value + } + // AddArticle(articleObject) + AddArticle(articleObject) + .then(resp => { + setMessage(resp.message); + setIsError(resp.isError); + }) + } + + return ( +
+

Add Article Page

+ { + !message + ? +
+ + Title + + + + Byline + + + + Abstract + + + +
+ : + { message } + } +
+ ); + }; + +export default AddArticlePage; \ No newline at end of file diff --git a/news-site-v/src/pages/ArticlePage.js b/news-site-v/src/pages/ArticlePage.js new file mode 100644 index 0000000..a7bc9b2 --- /dev/null +++ b/news-site-v/src/pages/ArticlePage.js @@ -0,0 +1,59 @@ +import React, { Component } from 'react'; +import Article from '../components/Article/Article.js' +import { fetchArticleByID } from '../api/ArticlesAPI'; + +class ArticlePage extends Component { + state = { + article: null + }; + + async componentDidMount() { + try { + const articleJson = await fetchArticleByID(this.props.match.params.articleID); + this.setState({ article: articleJson }); + } catch (e) { + console.error('error fetching article: ', e); + } + } + + render() { + return ( +
+ {this.state.article ?
: + 404: Article Not Found + } +
+ ); + } +} + +export default ArticlePage; + + +// Functional solution: +// function ArticlePage(props) { +// const [ article, setArticle ] = React.useState(null); + +// React.useEffect(() => { +// const fetchArticleAsync = async () => { +// try { +// const articleJson = await fetchArticleByID(props.match.params.articleID); +// setArticle(articleJson); +// } catch (e) { +// console.error('error fetching article: ', e); +// } +// }; + +// if (article === null) { +// fetchArticleAsync(); +// } +// }, [article]); + +// return ( +//
+// {article ?
: +// 404: Article Not Found +// } +//
+// ); +// } diff --git a/news-site-v/src/pages/HomePage.js b/news-site-v/src/pages/HomePage.js new file mode 100644 index 0000000..c237059 --- /dev/null +++ b/news-site-v/src/pages/HomePage.js @@ -0,0 +1,85 @@ +import React, { Component } from 'react'; +import ArticleList from '../components/ArticleList/ArticleList.js' +import { fetchArticles, searchArticles } from '../api/ArticlesAPI'; +import { InputGroup, Input } from 'reactstrap'; + +class HomePage extends Component { + state = { + articles: [] + }; + + async componentDidMount() { + try { + const articlesJson = await fetchArticles(); + this.setState({ articles: articlesJson }); + } catch (e) { + console.error('error fetching articles: ', e); + } + } + + async handleSearch(event) { + const textToSearchFor = event.target.value; + try { + let articlesJson; + if (!textToSearchFor) { + articlesJson = await fetchArticles(); + } else { + articlesJson = await searchArticles(textToSearchFor); + } + this.setState({ articles: articlesJson }); + } catch (e) { + console.error('error searching articles: ', e); + } + } + + render() { + return ( +
+ + this.handleSearch(e)} type="text" placeholder="Search" /> + + +
+ ); + } +} + +export default HomePage; + + +// Functional solution: +// function HomePage(props) { +// const [ articles, setArticles ] = React.useState([]); +// const [ searchText, setSearchText ] = React.useState(''); + +// React.useEffect(() => { +// const fetchArticlesAsync = async () => { +// try { +// let articlesJson; +// if (!searchText) { +// articlesJson = await fetchArticles(); +// } else { +// articlesJson = await searchArticles(searchText); +// } +// setArticles(articlesJson); +// } catch (e) { +// console.error('error fetching articles: ', e); +// } +// }; + +// if (!articles.length || searchText) { +// fetchArticlesAsync(); +// } +// }, [articles, searchText]); + +// const handleSearch = (e) => setSearchText(e.target.value); + +// return ( +//
+// +// +// +// +//
+// ); +// } diff --git a/news-site-v/src/pages/LoginPage.js b/news-site-v/src/pages/LoginPage.js new file mode 100644 index 0000000..d48a532 --- /dev/null +++ b/news-site-v/src/pages/LoginPage.js @@ -0,0 +1,29 @@ +import React, { useState} from 'react'; +import { Form, Button, Alert } from 'react-bootstrap'; + + + +const AddArticlePage = () => { + + return ( +
+

Login Page

+ { +
+ + Email + + + + Password + + + +
+ } +
+ ); +} +export default AddArticlePage; \ No newline at end of file diff --git a/news-site-v/src/pages/SectionPage.js b/news-site-v/src/pages/SectionPage.js new file mode 100644 index 0000000..184a7e7 --- /dev/null +++ b/news-site-v/src/pages/SectionPage.js @@ -0,0 +1,72 @@ +import React, { Component } from 'react'; +import ArticleList from '../components/ArticleList/ArticleList.js' +import { fetchArticlesBySection } from '../api/ArticlesAPI.js' + +class SectionPage extends Component { + state = { + articles: [] + } + + fetchArticles = () => { + fetchArticlesBySection(this.props.match.params.sectionID) + .then((apiResponseJSON) => { + this.setState({ + articles: apiResponseJSON + }); + }) + .catch((e) => { + console.error('error fetching articles: ', e); + }); + } + + componentDidMount() { + this.fetchArticles(); + } + + componentDidUpdate(_prevProps, prevState) { + if (prevState === this.state) { + this.fetchArticles(); + } + } + + render() { + return ( +
+ +
+ ); + } +} + +export default SectionPage; + +// Functional solution: +// function SectionPage(props) { +// const [ articles, setArticles ] = React.useState([]); +// const [ sectionID, setSectionID ] = React.useState(props.match.params.sectionID); + +// React.useEffect(() => { +// const fetchArticles = async () => { +// try { +// const apiResponseJSON = await fetchArticlesBySection(props.match.params.sectionID); +// setArticles(apiResponseJSON); +// } catch (e) { +// console.error('error fetching articles: ', e); +// }; +// }; + +// if (!articles.length) { // when the component first mounts, fetch articles +// fetchArticles(); +// } +// else if (sectionID !== props.match.params.sectionID) { // when the sectionID changes, fetch articles +// setSectionID(props.match.params.sectionID); +// fetchArticles(); +// } +// }, [articles, props.match.params.sectionID]); + +// return ( +//
+// +//
+// ); +// }