├── react-articles ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── src │ ├── App.js │ ├── .gitignore │ ├── index.css │ ├── index.js │ ├── article │ │ ├── Article.js │ │ ├── ArticleList.js │ │ └── ArticleManager.js │ ├── paragraph │ │ ├── Paragraph.js │ │ ├── EditableParagraph.js │ │ ├── ParagraphList.js │ │ └── ParagraphContainer.js │ ├── serviceWorker.js │ └── App.css ├── package.json └── README.md ├── server ├── README.md ├── .eslintrc.js ├── services │ ├── db.js │ ├── paragraph │ │ ├── create.js │ │ ├── delete.js │ │ ├── read.js │ │ └── update.js │ ├── article │ │ ├── create.js │ │ ├── update.js │ │ ├── delete.js │ │ └── read.js │ └── paragraphs │ │ └── update.js ├── models │ ├── article.js │ └── paragraph.js ├── package.json └── serverless.yml ├── .idea ├── misc.xml ├── vcs.xml ├── modules.xml ├── serverless-articles.iml ├── inspectionProfiles │ └── Project_Default.xml └── workspace.xml ├── README.md ├── .gitignore └── package.json /react-articles/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NansD/articles/master/react-articles/public/favicon.ico -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # serverless-articles 2 | 3 | run `npm i` 4 | then run `npm start` 5 | your serverless services are now running on localhost:3000 ! 6 | 🚀 -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb-base", 3 | "env": { 4 | "node": true 5 | }, 6 | "rules": { 7 | "no-console": 0, 8 | "no-underscore-dangle": 0, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /react-articles/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import ArticleManager from "./article/ArticleManager"; 4 | 5 | class App extends Component { 6 | render() { 7 | return ( 8 | 9 | ); 10 | } 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /server/services/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const mongodbUri = 'mongodb://admin:foo1bar@ds129386.mlab.com:29386/articles'; // very secure credentials 4 | mongoose.connect(mongodbUri); 5 | const db = mongoose.connection; 6 | 7 | db.on('error', console.error.bind(console, 'connection error:')); 8 | db.once('open', () => { }); 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # articles 2 | School project, REST api for a enhanced todo-style app, with articles and paragraphs with a has-many relation 3 | 4 | # How to run the app ? 5 | 6 | `git clone https://github.com/NansD/articles.git` 7 | 8 | `cd articles && npm install` 9 | 10 | `npm start` 11 | 12 | :clap: Your app is running on port 3001 :rocket: 13 | -------------------------------------------------------------------------------- /react-articles/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /react-articles/src/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /react-articles/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /.idea/serverless-articles.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /react-articles/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /react-articles/src/article/Article.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Article = (props) => { 4 | //Onclick of an article component = display its paragraphs 5 | return (
6 | 11 | {/*i used to delete article on click*/} 12 | 15 |
) 16 | }; 17 | 18 | export default Article; -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /server/services/paragraph/create.js: -------------------------------------------------------------------------------- 1 | const Paragraph = require('../../models/paragraph'); 2 | const queryString = require('querystring'); 3 | const sanitize = require('mongo-sanitize'); 4 | require('../db'); 5 | 6 | module.exports.create = async (event, context) => { 7 | let statusCode = 200; 8 | let message = 'paragraph create endpoint called, paragraph successfully created'; 9 | const formData = sanitize(queryString.parse(event.body)); 10 | const paragraph = await Paragraph.create(formData); 11 | 12 | return { 13 | statusCode, 14 | body: JSON.stringify({ 15 | message, 16 | paragraph, 17 | }), 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /react-articles/src/article/ArticleList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Article from "./Article"; 3 | 4 | const ArticleList = (props) => { 5 | var options = []; 6 | if (props.results) { 7 | //mapping the article list to display them 8 | options = props.results.map(article => ( 9 |
14 | )) 15 | } 16 | return
{options}
17 | }; 18 | 19 | export default ArticleList; -------------------------------------------------------------------------------- /server/models/article.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | // delete already existing model because of this issue : https://github.com/kriasoft/react-starter-kit/issues/1418 4 | // see this answer : https://github.com/kriasoft/react-starter-kit/issues/1418#issuecomment-334913935 5 | // this line doesn't crash if no model exists 6 | delete mongoose.connection.models.Article; 7 | 8 | // we use embedded Data models because it suits our needs (https://docs.mongodb.com/manual/core/data-model-design/) 9 | const articleSchema = mongoose.Schema({ 10 | title: String, 11 | }); 12 | const Article = mongoose.model('Article', articleSchema); 13 | module.exports = Article; 14 | -------------------------------------------------------------------------------- /server/services/article/create.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Paragraph = require('../../models/paragraph'); 4 | const Article = require('../../models/article'); 5 | require('../db'); 6 | 7 | module.exports.create = async (event, context) => { 8 | const statusCode = 200; 9 | const message = 'article create endpoint called, article successfully created'; 10 | const formData = sanitize(queryString.parse(event.body)); 11 | const article = await Article.create(formData); 12 | 13 | return { 14 | statusCode, 15 | body: JSON.stringify({ 16 | message, 17 | article, 18 | }), 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /react-articles/src/paragraph/Paragraph.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {SortableElement} from 'react-sortable-hoc'; 3 | 4 | const Paragraph = SortableElement((props) => { 5 | return(
6 | {/*giving id in data-id permits the onSortEnd to know the id of its paragraph*/} 7 | 10 |
12 | {props.content} 13 |
14 |
15 | ) 16 | }); 17 | 18 | export default Paragraph; -------------------------------------------------------------------------------- /server/models/paragraph.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | // delete already existing model because of this issue : https://github.com/kriasoft/react-starter-kit/issues/1418 4 | // see this answer : https://github.com/kriasoft/react-starter-kit/issues/1418#issuecomment-334913935 5 | // this line doesn't crash if no model exists 6 | delete mongoose.connection.models.Paragraph; 7 | 8 | // we use embedded Data models because it suits our needs (https://docs.mongodb.com/manual/core/data-model-design/) 9 | const paragraphSchema = mongoose.Schema({ 10 | articleId: String, 11 | order: Number, 12 | content: String, 13 | }); 14 | const Paragraph = mongoose.model('Paragraph', paragraphSchema); 15 | module.exports = Paragraph; 16 | -------------------------------------------------------------------------------- /react-articles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-articles", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "react": "^16.7.0", 8 | "react-dom": "^16.7.0", 9 | "react-scripts": "2.1.1", 10 | "react-sortable": "^2.0.0", 11 | "react-sortable-hoc": "^0.8.4" 12 | }, 13 | "proxy": "http://localhost:3000", 14 | "scripts": { 15 | "start": "PORT=3001 react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /react-articles/src/paragraph/EditableParagraph.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {SortableElement} from 'react-sortable-hoc'; 3 | 4 | const EditableParagraph = SortableElement((props) => { 5 | {/*giving id in data-id permits the different methods using events to know the id of their paragraph during the call of the function*/} 6 | return( 14 | ) 15 | }); 16 | 17 | export default EditableParagraph; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules 3 | 4 | 5 | /server/node_modules 6 | /server/.DS_Store 7 | /server/.idea 8 | /server/package-lock.json 9 | /server/.serverless 10 | 11 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 12 | 13 | # dependencies 14 | /react-articles/node_modules 15 | /react-articles/.pnp 16 | /react-articles/.pnp.js 17 | 18 | # testing 19 | /react-articles/coverage 20 | 21 | # production 22 | /react-articles/build 23 | 24 | # misc 25 | /react-articles/.DS_Store 26 | /react-articles/.env.local 27 | /react-articles/.env.development.local 28 | /react-articles/.env.test.local 29 | /react-articles/.env.production.local 30 | 31 | /react-articles/npm-debug.log* 32 | /react-articles/yarn-debug.log* 33 | /react-articles/yarn-error.log* 34 | /react-articles/package-lock.json 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "articles", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "concurrently --kill-others \"cd server && npm start\" \"cd react-articles && npm start\"", 8 | "install": "concurrently \"cd server && npm install\" \"cd react-articles && npm install\"", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/NansD/serverless-articles.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/NansD/serverless-articles/issues" 19 | }, 20 | "homepage": "https://github.com/NansD/serverless-articles#readme", 21 | "dependencies": { 22 | "concurrently": "^4.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-store", 3 | "version": "1.0.0", 4 | "description": "", 5 | "dependencies": { 6 | "base-64": "^0.1.0", 7 | "mongo-sanitize": "^1.0.0", 8 | "mongoose-unique-validator": "^2.0.1", 9 | "serverless": "^1.32.0", 10 | "mongoose": "^5.4.0", 11 | "qs": "^6.5.2" 12 | }, 13 | "devDependencies": { 14 | "cryptiles": "^4.1.2", 15 | "serverless-offline": "^3.25.11" 16 | }, 17 | "scripts": { 18 | "start": "serverless offline start", 19 | "test": "echo \"Error: no test specified\" && exit 1" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/NansD/serverless-store.git" 24 | }, 25 | "author": "", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/NansD/serverless-store/issues" 29 | }, 30 | "homepage": "https://github.com/NansD/serverless-store#readme" 31 | } 32 | -------------------------------------------------------------------------------- /server/services/paragraph/delete.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Paragraph = require('../../models/paragraph'); 4 | require('../db'); 5 | 6 | module.exports.delete = async (event, context) => { 7 | let statusCode = 200; 8 | let message = 'paragraph delete endpoint called'; 9 | let paragraph; 10 | const _id = (event.pathParameters && event.pathParameters.hasOwnProperty('_id')) 11 | ? sanitize(event.pathParameters._id) 12 | : false; 13 | if (_id) { 14 | paragraph = await Paragraph.findOneAndDelete({ _id }); 15 | message += ' paragraph successfully deleted'; 16 | } else { 17 | statusCode = 400; 18 | message = 'paragraph delete endpoint called, please specify an id'; 19 | } 20 | 21 | 22 | return { 23 | statusCode, 24 | body: JSON.stringify({ 25 | message, 26 | paragraph, 27 | }), 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /server/services/article/update.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Article = require('../../models/article'); 4 | require('../db'); 5 | 6 | module.exports.update = async (event, context) => { 7 | let statusCode = 200; 8 | let article; 9 | let message = 'article update endpoint called, article successfully updated'; 10 | const _id = (event.pathParameters && event.pathParameters.hasOwnProperty('_id')) 11 | ? sanitize(event.pathParameters._id) 12 | : false; 13 | const formData = sanitize(queryString.parse(event.body)); 14 | if (!_id) { 15 | statusCode = 400; 16 | message = 'bad request'; 17 | } else { 18 | article = await Article.findOneAndUpdate({ _id: formData._id }, formData, { new: true }); 19 | } 20 | 21 | return { 22 | statusCode, 23 | body: JSON.stringify({ 24 | message, 25 | article, 26 | }), 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /server/services/article/delete.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Paragraph = require('../../models/paragraph'); 4 | const Article = require('../../models/article'); 5 | require('../db'); 6 | 7 | module.exports.delete = async (event, context) => { 8 | const _id = (event.pathParameters && event.pathParameters.hasOwnProperty('_id')) 9 | ? sanitize(event.pathParameters._id) 10 | : false; 11 | let statusCode = 200; 12 | let message = 'article delete endpoint called'; 13 | let article; 14 | if (!_id) { 15 | statusCode = 400; 16 | message = 'article delete endpoint called, please specify an id'; 17 | } else { 18 | await Paragraph.deleteMany({ articleId: _id }); 19 | article = await Article.findOneAndDelete({ _id }); 20 | } 21 | 22 | return { 23 | statusCode, 24 | body: JSON.stringify({ 25 | message, 26 | article, 27 | }), 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /server/services/paragraph/read.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Paragraph = require('../../models/paragraph'); 4 | require('../db'); 5 | 6 | module.exports.read = async (event, context) => { 7 | const statusCode = 200; 8 | let message = 'paragraph read endpoint called'; 9 | let paragraph; 10 | let paragraphs; 11 | const _id = (event.pathParameters && event.pathParameters.hasOwnProperty('_id')) 12 | ? sanitize(event.pathParameters._id) 13 | : false; 14 | // if no id is specified, send all the paragraphs 15 | // otherwise send the paragraph with the right id 16 | if (!_id) { 17 | paragraphs = await Paragraph.find({}); 18 | } else { 19 | paragraph = await Paragraph.findOne({ _id }); 20 | if (!paragraph) { 21 | message += 'paragraph not found' 22 | } 23 | } 24 | 25 | return { 26 | statusCode, 27 | body: JSON.stringify({ 28 | message, 29 | paragraph, 30 | paragraphs, 31 | }), 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /server/services/paragraphs/update.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Paragraph = require('../../models/paragraph'); 4 | require('../db'); 5 | 6 | /** 7 | * Allows the update of multiple paragraphs at once 8 | */ 9 | module.exports.update = async (event, context) => { 10 | let statusCode = 200; 11 | let message = 'paragraphs endpoint called. Paragraphs successfully updated'; 12 | const formData = sanitize(queryString.parse(event.body)); 13 | const paragraphs = JSON.parse(queryString.parse(event.body).paragraphs); 14 | if (!formData.paragraphs) { 15 | statusCode = 400; 16 | message = 'Wrong parameters'; 17 | } else { 18 | // the frontend sends us an array of paragraphs which are 19 | // ordered accordingly to the interface 20 | // so we update each paragraph with its correct order 21 | for (let i = 0; i < paragraphs.length; i++) { 22 | let paragraph = paragraphs[i]; 23 | paragraph = { 24 | ...paragraph, 25 | order: i, 26 | }; 27 | await Paragraph.findOneAndUpdate({ _id: paragraph._id }, paragraph); 28 | } 29 | } 30 | 31 | 32 | return { 33 | statusCode, 34 | body: JSON.stringify({ 35 | message, 36 | paragraphs: formData.paragraphs, 37 | }), 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /server/services/paragraph/update.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Paragraph = require('../../models/paragraph'); 4 | require('../db'); 5 | 6 | module.exports.update = async (event, context) => { 7 | let statusCode = 200; 8 | let paragraph; 9 | let message = 'paragraph update endpoint called, paragraph successfully updated'; 10 | const formData = sanitize(queryString.parse(event.body)); 11 | const _id = (event.pathParameters && event.pathParameters.hasOwnProperty('_id')) 12 | ? sanitize(event.pathParameters._id) 13 | : false; 14 | if (_id && _id === formData._id) { 15 | // Lorsqu'on update le paragraphe, il a un nouvel ordre 16 | // Il peut s'agir d'un numéro d'ordre qui est déjà utilisé 17 | // On va décaler les ordres des paragraphes existants après 18 | const articleParagraphs = await Paragraph.find({ articleId: formData.articleId }); 19 | for (let i = 0; i < articleParagraphs.length; i++) { 20 | const p = articleParagraphs[i]; 21 | if (p._id != _id && formData.order && p.order >= formData.order) { 22 | p.order += 1; 23 | await Paragraph.findOneAndUpdate({ _id: p._id }, p); 24 | } 25 | } 26 | paragraph = await Paragraph.findOneAndUpdate({ _id }, formData, { new: true }); 27 | } else { 28 | statusCode = 400; 29 | message = 'Bad request'; 30 | } 31 | 32 | if (paragraph == null) { 33 | statusCode = 404; 34 | message = 'Paragraph not found'; 35 | } 36 | 37 | return { 38 | statusCode, 39 | body: JSON.stringify({ 40 | message, 41 | paragraph, 42 | }), 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /react-articles/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | Articles & Paragraphes 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /server/services/article/read.js: -------------------------------------------------------------------------------- 1 | const queryString = require('querystring'); 2 | const sanitize = require('mongo-sanitize'); 3 | const Paragraph = require('../../models/paragraph'); 4 | const Article = require('../../models/article'); 5 | require('../db'); 6 | 7 | /** 8 | * @param event 9 | * @param context 10 | * @returns {Promise<{statusCode: number, body: string}>} 11 | * si pas de filtre, je renvoie tous les articles et leurs paragraphes 12 | si filtre, je renvoie juste les articles avec leur titre 13 | si _id, je renvoie un article et ses paragraphes 14 | */ 15 | 16 | module.exports.read = async (event, context) => { 17 | let statusCode = 200; 18 | let message = 'article read endpoint called'; 19 | const _id = (event.pathParameters && event.pathParameters.hasOwnProperty('_id')) 20 | ? sanitize(event.pathParameters._id) 21 | : false; 22 | 23 | const queryStringParameters = sanitize(event.queryStringParameters); 24 | let article; 25 | let articles; 26 | if (_id) { 27 | article = await Article.findOne({ _id }); 28 | if (article) { 29 | const paragraphs = await Paragraph.find({ articleId: article._id }); 30 | article = { 31 | _id: article._id, 32 | title: article.title, 33 | paragraphs, 34 | }; 35 | } else { 36 | statusCode = 404; 37 | message = 'article not found'; 38 | } 39 | } else { 40 | articles = await Article.find(); 41 | const articlesWithParagraphs = []; 42 | if (!queryStringParameters || !queryStringParameters.filter) { 43 | for (let i = 0; i < articles.length; i++) { 44 | articlesWithParagraphs.push({ 45 | _id: articles[i]._id, 46 | title: articles[i].title, 47 | paragraphs: await Paragraph.find({ articleId: articles[i]._id }), 48 | }); 49 | } 50 | articles = articlesWithParagraphs; 51 | } 52 | } 53 | 54 | 55 | return { 56 | statusCode, 57 | body: JSON.stringify({ 58 | message, 59 | article, 60 | articles, 61 | }), 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /react-articles/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3001](http://localhost:3001) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /react-articles/src/paragraph/ParagraphList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Paragraph from './Paragraph'; 3 | import EditableParagraph from './EditableParagraph' 4 | import {SortableContainer} from 'react-sortable-hoc'; 5 | 6 | const ParagraphList = SortableContainer((props) => { 7 | return ( 8 |
9 | {/*index prop is used for react-sortable-hoc library, but not used in pargraph/editableparagraph component*/} 10 | {/*Two components: Editable and Paragraph to display the edition mode only on the paragraphs clicked*/} 11 | { 12 | props.items.map((paragraph,index) => (paragraph.toEdit ? : 19 | )) 26 | } 27 |
28 | ) 29 | }); 30 | 31 | export default ParagraphList; -------------------------------------------------------------------------------- /react-articles/src/article/ArticleManager.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ArticleList from './ArticleList'; 3 | import axios from 'axios'; 4 | import ParagraphContainer from "../paragraph/ParagraphContainer"; 5 | 6 | class ArticleManager extends Component { 7 | //initalization of state's component 8 | constructor() { 9 | super(); 10 | this.state = { 11 | results: [], 12 | articleToDisplay: '', 13 | inputClasses: 'input', 14 | inputIsError: false, 15 | newArticle: false, 16 | change: { 17 | articleName : '' 18 | } 19 | } 20 | } 21 | 22 | //Change articleToDisplay value which leads to display the paragraph clicked 23 | showParagraph(e) { 24 | this.setState({ 25 | articleToDisplay: e.target.name, 26 | }) 27 | } 28 | 29 | //On mounting: get all the articles 30 | componentDidMount() { 31 | axios.get('/article').then(res => { 32 | this.setState({ 33 | results:res.data.articles 34 | }) 35 | }); 36 | } 37 | 38 | //Adds an article in the db and in the front app 39 | addArticle = () => { 40 | const title = this.state.change.articleName.trim(); 41 | if(title) { 42 | axios.post('/article',"title="+title).then((res) => { 43 | this.setState(prevState => ({ 44 | inputClasses: 'input', 45 | inputIsError: false, 46 | results: [...prevState.results, { 47 | _id: res.data.article._id, 48 | title: title 49 | }], 50 | newArticle: true, 51 | articleToDisplay: res.data.article._id 52 | })); 53 | }); 54 | } else {this.setState({ 55 | inputClasses: 'input is-error', 56 | inputIsError: true, 57 | })} 58 | }; 59 | 60 | //Deletes an article in the db and in the front app 61 | delArticle = (e) => { 62 | const id = e.target.id; 63 | axios.delete('/article/' + id).then(res => { 64 | axios.get('/article').then(res => { 65 | this.setState({ 66 | results:res.data.articles 67 | }) 68 | }) 69 | }); 70 | //Supress the paragraphs of the page if the articled deleted is the one being displayed 71 | if(id === this.state.articleToDisplay) { 72 | this.setState({ 73 | articleToDisplay: null 74 | }) 75 | } 76 | }; 77 | 78 | //Handle input changes (for new article name) 79 | handleChange(e) { 80 | this.setState({ 81 | change : { 82 | ...this.state.change, 83 | [e.target.name] : e.target.value, 84 | }, 85 | }); 86 | }; 87 | 88 | render() { 89 | //Show the paragraphs of an article if one of them is clicked 90 | //Otherwise: nothing 91 | const article = this.state.articleToDisplay ? : null; 93 | return ( 94 |
95 |
96 |
97 | 98 | 99 | {this.state.inputIsError &&
Veuillez donner un nom à votre article.
} 100 |
101 |
102 |
103 |

Liste des Articles

104 | 105 |
106 | {article} 107 |
108 | ); 109 | } 110 | } 111 | 112 | export default ArticleManager; -------------------------------------------------------------------------------- /react-articles/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /server/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Serverless! 2 | # 3 | # This file is the main config file for your service. 4 | # It's very minimal at this point and uses default values. 5 | # You can always add more config options for more control. 6 | # We've included some commented out config examples here. 7 | # Just uncomment any of them to get that config option. 8 | # 9 | # For full config options, check the docs: 10 | # docs.serverless.com 11 | # 12 | # Happy Coding! 13 | 14 | service: my-service # NOTE: update this with your service name 15 | 16 | # You can pin your service to only deploy with a specific Serverless version 17 | # Check out our docs for more details 18 | # frameworkVersion: "=X.X.X" 19 | 20 | provider: 21 | name: aws 22 | runtime: nodejs8.10 23 | iamRoleStatements: 24 | - Effect: Allow 25 | Action: 26 | - lambda:InvokeFunction 27 | Resource: "*" 28 | 29 | # you can overwrite defaults here 30 | # stage: dev 31 | # region: us-east-1 32 | 33 | # you can add statements to the Lambda function's IAM Role here 34 | # iamRoleStatements: 35 | # - Effect: "Allow" 36 | # Action: 37 | # - "s3:ListBucket" 38 | # Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } 39 | # - Effect: "Allow" 40 | # Action: 41 | # - "s3:PutObject" 42 | # Resource: 43 | # Fn::Join: 44 | # - "" 45 | # - - "arn:aws:s3:::" 46 | # - "Ref" : "ServerlessDeploymentBucket" 47 | # - "/*" 48 | 49 | # you can define service wide environment variables here 50 | # environment: 51 | # variable1: value1 52 | 53 | # you can add packaging information here 54 | #package: 55 | # include: 56 | # - include-me.js 57 | # - include-me-dir/** 58 | # exclude: 59 | # - exclude-me.js 60 | # - exclude-me-dir/** 61 | 62 | functions: 63 | paragraphPOST: 64 | handler: services/paragraph/create.create 65 | events: 66 | - http: 67 | path: paragraph 68 | method: POST 69 | paragraphDELETE: 70 | handler: services/paragraph/delete.delete 71 | events: 72 | - http: 73 | path: paragraph/{_id} 74 | method: DELETE 75 | request: 76 | parameters: 77 | paths: 78 | id: true 79 | paragraphREAD: 80 | handler: services/paragraph/read.read 81 | events: 82 | - http: 83 | path: paragraph/{_id} 84 | method: GET 85 | request: 86 | parameters: 87 | paths: 88 | id: true 89 | paragraphsREAD: 90 | handler: services/paragraph/read.read 91 | events: 92 | - http: 93 | path: paragraph 94 | method: GET 95 | paragraphPATCH: 96 | handler: services/paragraph/update.update 97 | events: 98 | - http: 99 | path: paragraph/{_id} 100 | method: PATCH 101 | request: 102 | parameters: 103 | paths: 104 | id: true 105 | paragraphsPATCH: 106 | handler: services/paragraphs/update.update 107 | events: 108 | - http: 109 | path: paragraphs 110 | method: PATCH 111 | articlePOST: 112 | handler: services/article/create.create 113 | events: 114 | - http: 115 | path: article 116 | method: POST 117 | articleDELETE: 118 | handler: services/article/delete.delete 119 | events: 120 | - http: 121 | path: article/{_id} 122 | method: DELETE 123 | request: 124 | parameters: 125 | paths: 126 | id: true 127 | articlesRead: 128 | handler: services/article/read.read 129 | events: 130 | - http: 131 | path: article 132 | method: GET 133 | articleREAD: 134 | handler: services/article/read.read 135 | events: 136 | - http: 137 | path: article/{_id} 138 | method: GET 139 | request: 140 | parameters: 141 | paths: 142 | id: true 143 | articlePATCH: 144 | handler: services/article/update.update 145 | events: 146 | - http: 147 | path: article/{_id} 148 | method: PATCH 149 | request: 150 | parameters: 151 | paths: 152 | id: true 153 | plugins: 154 | - serverless-offline 155 | 156 | # The following are a few example events you can configure 157 | # NOTE: Please make sure to change your handler code to work with those events 158 | # Check the event documentation for details 159 | # events: 160 | # - http: 161 | # path: users/create 162 | # method: get 163 | # - s3: ${env:BUCKET} 164 | # - schedule: rate(10 minutes) 165 | # - sns: greeter-topic 166 | # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 167 | # - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx 168 | # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx 169 | # - iot: 170 | # sql: "SELECT * FROM 'some_topic'" 171 | # - cloudwatchEvent: 172 | # event: 173 | # source: 174 | # - "aws.ec2" 175 | # detail-type: 176 | # - "EC2 Instance State-change Notification" 177 | # detail: 178 | # state: 179 | # - pending 180 | # - cloudwatchLog: '/aws/lambda/hello' 181 | # - cognitoUserPool: 182 | # pool: MyUserPool 183 | # trigger: PreSignUp 184 | 185 | # Define function environment variables here 186 | # environment: 187 | # variable2: value2 188 | 189 | # you can add CloudFormation resource templates here 190 | #resources: 191 | # Resources: 192 | # NewResource: 193 | # Type: AWS::S3::Bucket 194 | # Properties: 195 | # BucketName: my-new-bucket 196 | # Outputs: 197 | # NewOutput: 198 | # Description: "Description for the output" 199 | # Value: "Some output value" 200 | -------------------------------------------------------------------------------- /react-articles/src/paragraph/ParagraphContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ParagraphList from './ParagraphList'; 3 | import axios from "axios"; 4 | import {arrayMove} from 'react-sortable-hoc'; 5 | 6 | 7 | class ParagraphContainer extends Component { 8 | //initialization of component's state 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | paragraphs: [], 14 | id: 0, 15 | name: '', 16 | editionMode: false, 17 | change: { 18 | paragraphContent: '', 19 | }, 20 | } 21 | } 22 | 23 | //display the article on the first click using props 24 | componentDidMount() { 25 | if(this.props.newArticle) { 26 | this.setState({ 27 | editionMode: true 28 | }) 29 | }; 30 | this.updateFrontParagraph(); 31 | } 32 | 33 | // update the displayed paragraphs according to the 34 | // article that will be displayed 35 | componentWillReceiveProps(nextProps) { 36 | if(nextProps.newArticle) { 37 | this.setState({ 38 | editionMode: true 39 | }) 40 | }; 41 | this.updateFrontParagraph(nextProps.articleid); 42 | } 43 | 44 | //Method displaying the paragraphs of an article 45 | updateFrontParagraph = (articleId) => { 46 | const articleIdToQuery = articleId || this.props.articleid 47 | axios.get('/article/' + articleIdToQuery).then(res => { 48 | let paragraphs = res.data.article.paragraphs; 49 | 50 | paragraphs.sort(function (a, b) { 51 | return a.order - b.order; 52 | }); 53 | 54 | paragraphs.map((paragraph,index) => { 55 | paragraphs[index].toEdit = false; 56 | paragraphs[index].previousContent = paragraph.content; 57 | }); 58 | 59 | this.setState({ 60 | name: res.data.article.title, 61 | paragraphs: paragraphs, 62 | }); 63 | }); 64 | }; 65 | 66 | //Add a paragraph to the article 67 | //Add the paragraph to the dtabase using Post request 68 | //Using paragraphs.length as the order to boost performance 69 | addParagraph = () => { 70 | const order = this.state.paragraphs.length + 1; 71 | const content = this.state.change.paragraphContent ? this.state.change.paragraphContent : "Nouveau Paragraphe"; 72 | axios.post('/paragraph',"articleId=" + this.props.articleid + "&order=" + order + "&content=" + content).then( 73 | this.updateFrontParagraph() 74 | ) 75 | }; 76 | 77 | 78 | //Handle inputs change for every inputs in the page 79 | handleChange(e) { 80 | this.setState({ 81 | change : { 82 | ...this.state.change, 83 | [e.target.name] : e.target.value, 84 | }, 85 | }); 86 | }; 87 | 88 | //Toggle if the user can edit the paragraphs or not 89 | toggleEditionMode = () => { 90 | const newEditionMode = !this.state.editionMode; 91 | this.setState({ 92 | editionMode : newEditionMode, 93 | }) 94 | }; 95 | 96 | //Passing toEdit value to true will display a textarea instead of div (permitting edition) 97 | editParagraph = (e) => { 98 | if (this.state.editionMode) { 99 | let paragraphs = [...this.state.paragraphs]; 100 | paragraphs[e.target.dataset.id].toEdit = true; 101 | this.setState({paragraphs}); 102 | } 103 | }; 104 | 105 | //Handle the edition of a paragraph during tapping 106 | handleChangeParagraph = (e) => { 107 | let paragraphs = [...this.state.paragraphs]; 108 | paragraphs[e.target.name].content = e.target.value; 109 | this.setState({paragraphs}); 110 | }; 111 | 112 | //Deletes a pargraph in the database and in the state 113 | delParagraph = (e) => { 114 | const id = e.target.dataset.id; 115 | axios.delete("/paragraph/" + id).then( 116 | this.updateFrontParagraph() 117 | ) 118 | }; 119 | 120 | //Handle key down in the textarea durinf edition of a paragraph 121 | //27 = ESC key -- 13 = Ebter key 122 | handleKeyDown = (e) => { 123 | if (e.keyCode === 27) { 124 | let paragraphs = [...this.state.paragraphs]; 125 | paragraphs[e.target.name].toEdit = false; 126 | paragraphs[e.target.name].content = paragraphs[e.target.name].previousContent; 127 | this.setState({paragraphs}); 128 | } 129 | if (e.keyCode === 13) { 130 | const id = e.target.dataset.id; 131 | let paragraphs = [...this.state.paragraphs]; 132 | axios.patch("/paragraph/" + id,"_id=" + id + "&content=" + paragraphs[e.target.name].content).then( 133 | paragraphs[e.target.name].toEdit = false 134 | ); 135 | paragraphs[e.target.name].previousContent = paragraphs[e.target.name].content; 136 | this.setState({paragraphs}); 137 | e.target.blur(); 138 | } 139 | }; 140 | 141 | //Handle end of sorting (given by the react-sortable-hoc library) 142 | //Editing the paragraphs' orders in the database 143 | onSortEnd = ({oldIndex, newIndex, collection}, e) => { 144 | this.setState({ 145 | paragraphs: arrayMove(this.state.paragraphs, oldIndex, newIndex) 146 | }, () => { 147 | // If we keep the RESTful approach for the backend, we have to do this : 148 | // let id; 149 | // this.state.paragraphs.forEach(function (paragraph, index) { 150 | // id = paragraph._id; 151 | // axios.patch("/paragraph/" + id,"_id=" + id + "&order=" + index); 152 | // }) 153 | // 154 | // but instead we have decided to create a route to allow to update 155 | // several paragraphs at a time : 156 | axios.patch('/paragraphs', "paragraphs="+JSON.stringify(this.state.paragraphs)); 157 | }); 158 | 159 | 160 | }; 161 | 162 | 163 | //Rendering the paragraph container component 164 | render() { 165 | return( 166 |
167 | 168 | {this.state.editionMode &&
169 |
170 | 171 | 172 |
173 |
} 174 |
175 |

{this.state.name}

176 | {/*distance is used for avoiding the mixing of onClick to start edition and the click for sorting elements*/} 177 | 184 |
185 |
186 | ) 187 | } 188 | } 189 | 190 | export default ParagraphContainer; -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 159 | 160 | 161 | 162 | close 163 | console 164 | 165 | 166 | 167 | 169 | 170 | 190 | 191 | 192 | 193 | 194 | true 195 | DEFINITION_ORDER 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | GeneralJavaScript 222 | 223 | 224 | JavaScript 225 | 226 | 227 | 228 | 229 | JSCheckFunctionSignatures 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 |