commit 7e9599118ed2ba3723623f0dab6694fd2564cec7 Author: Julien Gautier Date: Tue Sep 12 21:43:57 2023 +0200 ---|main| Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9b73521 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 0000000..97e22b7 --- /dev/null +++ b/.env @@ -0,0 +1,16 @@ +APP_ENV="development" +NODE_ENV="development" +DEBUG=false +MONGODB_URI="mongodb://localhost:27017/headupdb" +SERVER_PORT="3200" +SECRET="$2b$10$sMuV/fTCGm9CJGamPFvvte" +SENDGRID_API_KEY="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" +SENDGRID_FROM_MAIL="contact@rampeur.fr" +SENDGRID_TO_MAIL="rampeur@gmail.com" +AUTHORIZATION_PREFIX="Api-Key" +GOLDAPI_URL="https://www.goldapi.io/api" +GOLDAPI_TOKEN="goldapi-akasnfrlfiqv0xw-io" +COUCHDB_URL="http://couchdb.unespace.com" +COUCHDB_DATABASE="headupdb" +COUCHDB_DESIGN="headup_app" +COUCHDB_CREDENTIAL="cmFtcGV1cjpES3hwMjRQUw==" diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..52582fa --- /dev/null +++ b/.env.development @@ -0,0 +1,16 @@ +APP_ENV="development" +NODE_ENV="development" +DEBUG=true +MONGODB_URI="mongodb://localhost:27017/headupdb" +SERVER_PORT="3200" +SECRET="$2b$10$7LPCMF6razIF744f3k3dLu" +SENDGRID_API_KEY="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" +SENDGRID_FROM_MAIL="contact@rampeur.fr" +SENDGRID_TO_MAIL="rampeur@gmail.com" +AUTHORIZATION_PREFIX="Api-Key" +GOLDAPI_URL="https://www.goldapi.io/api" +GOLDAPI_TOKEN="goldapi-akasnfrlfiqv0xw-io" +COUCHDB_URL="http://couchdb.unespace.com" +COUCHDB_DATABASE="headupdb" +COUCHDB_DESIGN="headup_app" +COUCHDB_CREDENTIAL="cmFtcGV1cjpES3hwMjRQUw==" diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..5c9f969 --- /dev/null +++ b/.env.local @@ -0,0 +1,16 @@ +APP_ENV="local" +NODE_ENV="development" +DEBUG=false +MONGODB_URI="mongodb://localhost:27017/headupdb" +SERVER_PORT="3200" +SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO" +SENDGRID_API_KEY="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" +SENDGRID_FROM_MAIL="contact@rampeur.fr" +SENDGRID_TO_MAIL="rampeur@gmail.com" +AUTHORIZATION_PREFIX="Api-Key" +GOLDAPI_URL="https://www.goldapi.io/api" +GOLDAPI_TOKEN="goldapi-akasnfrlfiqv0xw-io" +COUCHDB_URL="http://couchdb.unespace.com" +COUCHDB_DATABASE="headupdb" +COUCHDB_DESIGN="headup_app" +COUCHDB_CREDENTIAL="cmFtcGV1cjpES3hwMjRQUw==" diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..0898926 --- /dev/null +++ b/.env.production @@ -0,0 +1,16 @@ +APP_ENV="production" +NODE_ENV="production" +DEBUG=false +MONGODB_URI="mongodb://localhost:27017/headupdb" +SERVER_PORT="3030" +SECRET="$2b$10$t1ma1H6HsGi8tMK4GRH5fu" +SENDGRID_API_KEY="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" +SENDGRID_FROM_MAIL="contact@rampeur.fr" +SENDGRID_TO_MAIL="rampeur@gmail.com" +AUTHORIZATION_PREFIX="Api-Key" +GOLDAPI_URL="https://www.goldapi.io/api" +GOLDAPI_TOKEN="goldapi-akasnfrlfiqv0xw-io" +COUCHDB_URL="http://couchdb.unespace.com" +COUCHDB_DATABASE="headupdb" +COUCHDB_DESIGN="headup_app" +COUCHDB_CREDENTIAL="cmFtcGV1cjpES3hwMjRQUw==" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a812403 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Logs +logs +*.log +.DS_Store + +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +node_modules + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..37f5eca --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# HeadUp_API + +API pour l'application Head Up diff --git a/app.js b/app.js new file mode 100644 index 0000000..0c8b57b --- /dev/null +++ b/app.js @@ -0,0 +1,118 @@ +var appEnv = process.env.APP_ENV; +if (appEnv == undefined) { + appEnv = 'development'; +} +require('dotenv').config({ path: `.env.${appEnv}` }); +const { promisify } = require('util'), + bodyParser = require('body-parser'), + chalk = require('chalk'), + utils = require('./routes/utils'); +var express = require('express'), + session = require('express-session'), + cors = require('cors'), + errorhandler = require('errorhandler'), + mongoose = require('mongoose'), + morgan = require('morgan'); + +var isProduction = appEnv === 'production'; + +// Create global app object +var app = express(); + +app.use(cors()); + +// Normal express config defaults +//app.use(morgan('combined')); +morgan.token('statusColor', (req, res, args) => { + var status = (typeof res.headersSent !== 'boolean' ? Boolean(res.header) : res.headersSent) + ? res.statusCode + : undefined + + return status >= 500 ? chalk.red(status) + : status >= 429 ? chalk.magenta(status) + : status >= 400 ? chalk.yellow(status) + : status >= 300 ? chalk.cyan(status) + : status >= 200 ? chalk.green(status) + : chalk.underline(status); +}); +app.use(morgan('[:date[iso]] ":method :url HTTP/:http-version" :statusColor :res[content-length] ":referrer" ":user-agent" :remote-addr')); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); + +app.use(require('method-override')()); +app.use(express.static(__dirname + '/public')); + +app.use(session({ secret: 'acapisecret', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); + +if (!isProduction) { + app.use(errorhandler()); +} + +if(isProduction){ + mongoose.connect(process.env.MONGODB_URI); +} else { + mongoose.set('strictQuery', true); + mongoose.connect(process.env.MONGODB_URI).then(() => { + console.log(`${utils.getDateTimeISOString()}`, chalk.green('MongoDB connection has been established successfully')); + }).catch(() => { + console.log(`${utils.getDateTimeISOString()}`, chalk.red('Unable to connect to the MongoDB database')); + }); + mongoose.set('debug', process.env.DEBUG); +} +require('./models/mongo/User'); +require('./models/mongo/Application'); +require('./models/mongo/Aeronef'); +require('./models/mongo/Canopy'); +require('./models/mongo/Dropzone'); +require('./models/mongo/Jump'); +/* +require('./models/mongo/Metal'); +require('./models/mongo/Price'); +require('./models/mongo/Product'); +*/ +require('./config/passport'); + +app.use(require('./routes')); + +/// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +/// error handlers + +// development error handler +// will print stacktrace +if (!isProduction) { + app.use(function(err, req, res, next) { + console.log(`${utils.getDateTimeISOString()}`, chalk.red(`error handler : ${err.message} ${err.err}`)); + res.status(err.status || 500); + res.json({errors: { + message: err.message, + error: err.err, + stack: err.stack + }}); + }); +} else { + // production error handler + // no stacktraces leaked to user + app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.json({errors: { + message: err.message, + error: {}, + stack: {} + }}); + }); +} + +const startServer = async () => { + const port = process.env.SERVER_PORT || 3200; + await promisify(app.listen).bind(app)(port); + console.log(`${utils.getDateTimeISOString()}`, chalk.green(`${process.env.APP_ENV} API server listening on port ${port}`)); +} + +// finally, let's start our server... +startServer(); \ No newline at end of file diff --git a/config/db.js b/config/db.js new file mode 100644 index 0000000..be866c7 --- /dev/null +++ b/config/db.js @@ -0,0 +1,24 @@ +module.exports = { + "development": { + "username": 'jgautier', + "password": 'TXhtCj-qJy6CPH-YkHT7p-zHY3nR', + "database": 'barrett_aucoffre', + "host": '51.68.76.171', + "port": 3308, + "dialect": 'mysql' + }, + "test": { + "username": "root", + "password": null, + "database": "database_test", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "production": { + "username": "root", + "password": null, + "database": "database_production", + "host": "127.0.0.1", + "dialect": "mysql" + } +} \ No newline at end of file diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..03a2937 --- /dev/null +++ b/config/index.js @@ -0,0 +1,5 @@ + +const config = { + secret: process.env.SECRET || 'acapisecret' +}; +module.exports = config; diff --git a/config/passport.js b/config/passport.js new file mode 100644 index 0000000..60ea0f4 --- /dev/null +++ b/config/passport.js @@ -0,0 +1,43 @@ +var bcrypt = require('bcrypt'); +const passport = require('passport'); +var LocalStrategy = require('passport-local').Strategy; +var HeaderAPIKeyStrategy = require('passport-headerapikey').HeaderAPIKeyStrategy; +var mongoose = require('mongoose'); +var Application = mongoose.model('Application'); +var User = mongoose.model('User'); + +passport.use('headerapikey', new HeaderAPIKeyStrategy( + { header: 'Authorization', prefix: `${process.env.AUTHORIZATION_PREFIX} ` }, + false, + function (apikey, done) { + const maskedKey = `${apikey.slice(0, 4)}...${apikey.slice(-4)}`; + Application.findOne({ maskedkey: maskedKey }) + .populate('author') + .then(function (application) { + if (!application) { + return done({message: "Application can't be found.", status: 404}, false, false); + //return done(null, false, false); + } + return {app: application, prom: Promise.resolve(bcrypt.compare(apikey, application.apikey))}; + }).then(function (res) { + res.prom.then(function (valid) { + if (!valid) { + return done(null, false, {app: res.app, valid: valid}); + } + return done(null, res.app, valid); + }); + }).catch(done); + } +)); + +passport.use('local', new LocalStrategy({ + usernameField: 'user[email]', + passwordField: 'user[password]' +}, function (email, password, done) { + User.findOne({ email: email }).then(function (user) { + if (!user || !user.validPassword(password)) { + return done(null, false, { errors: { 'email ou mot de passe': 'invalide' } }); + } + return done(null, user); + }).catch(done); +})); diff --git a/models/mongo/Aeronef.js b/models/mongo/Aeronef.js new file mode 100644 index 0000000..1cec999 --- /dev/null +++ b/models/mongo/Aeronef.js @@ -0,0 +1,34 @@ +var mongoose = require('mongoose'); +var User = mongoose.model('User'); +var slug = require('slug'); + +var AeronefSchema = new mongoose.Schema({ + slug: { type: String, lowercase: true, unique: true }, + aeronef: String, + imat: String, + author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } +}, {timestamps: true, toJSON: {virtuals: true}}); + +AeronefSchema.pre('validate', function (next) { + if (!this.slug) { + this.slugify(); + } + next(); +}); + +AeronefSchema.methods.slugify = function () { + this.slug = slug(`${this.aeronef} ${this.imat}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); +}; + +AeronefSchema.methods.toJSONFor = function(user){ + return { + slug: this.slug, + aeronef: this.aeronef, + imat: this.imat, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: this.author.toProfileJSONFor(user) + }; +}; + +mongoose.model('Aeronef', AeronefSchema); diff --git a/models/mongo/Application.js b/models/mongo/Application.js new file mode 100644 index 0000000..794e04c --- /dev/null +++ b/models/mongo/Application.js @@ -0,0 +1,51 @@ +var mongoose = require('mongoose'); +var uniqueValidator = require('mongoose-unique-validator'); +var slug = require('slug'); + +var ApplicationSchema = new mongoose.Schema({ + apikey: String, + maskedkey: String, + slug: { type: String, lowercase: true, unique: true }, + title: String, + description: String, + author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } +}, { timestamps: true, toJSON: { virtuals: true } }); + +ApplicationSchema.plugin(uniqueValidator, { message: 'is already taken' }); + +ApplicationSchema.pre('validate', function (next) { + if (!this.slug) { + this.slugify(); + } + next(); +}); + +ApplicationSchema.methods.slugify = function () { + this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); +}; + +ApplicationSchema.methods.toJSONFor = function (user) { + return { + maskedkey: this.maskedkey, + slug: this.slug, + title: this.title, + description: this.description, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: this.author.toProfileJSONFor(user) + }; +}; + +ApplicationSchema.methods.toAuthJSON = function () { + return { + maskedkey: this.maskedkey, + slug: this.slug, + title: this.title, + description: this.description, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: this.author.toJSONFor() + }; +}; + +mongoose.model('Application', ApplicationSchema); \ No newline at end of file diff --git a/models/mongo/Canopy.js b/models/mongo/Canopy.js new file mode 100644 index 0000000..baf1a0e --- /dev/null +++ b/models/mongo/Canopy.js @@ -0,0 +1,34 @@ +var mongoose = require('mongoose'); +var User = mongoose.model('User'); +var slug = require('slug'); + +var CanopySchema = new mongoose.Schema({ + slug: { type: String, lowercase: true, unique: true }, + voile: String, + taille: Number, + author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } +}, {timestamps: true, toJSON: {virtuals: true}}); + +CanopySchema.pre('validate', function (next) { + if (!this.slug) { + this.slugify(); + } + next(); +}); + +CanopySchema.methods.slugify = function () { + this.slug = slug(`${this.voile} ${this.taille}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); +}; + +CanopySchema.methods.toJSONFor = function(user){ + return { + slug: this.slug, + voile: this.voile, + taille: this.taille, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: this.author.toProfileJSONFor(user) + }; +}; + +mongoose.model('Canopy', CanopySchema); diff --git a/models/mongo/Currency.js b/models/mongo/Currency.js new file mode 100644 index 0000000..327393e --- /dev/null +++ b/models/mongo/Currency.js @@ -0,0 +1,20 @@ +var mongoose = require('mongoose'); +var uniqueValidator = require('mongoose-unique-validator'); + +var CurrencySchema = new mongoose.Schema({ + ordering: Number, + code: { type: String, upercase: true, unique: true }, + title: String +}, {timestamps: true, toJSON: {virtuals: true}}); + +CurrencySchema.plugin(uniqueValidator, { message: 'is already taken' }); + +CurrencySchema.methods.toJSONFor = function(){ + return { + ordering: this.ordering, + code: this.code, + title: this.title + }; +}; + +mongoose.model('Currency', CurrencySchema, 'currencies'); \ No newline at end of file diff --git a/models/mongo/Dropzone.js b/models/mongo/Dropzone.js new file mode 100644 index 0000000..b7bf076 --- /dev/null +++ b/models/mongo/Dropzone.js @@ -0,0 +1,34 @@ +var mongoose = require('mongoose'); +var User = mongoose.model('User'); +var slug = require('slug'); + +var DropzoneSchema = new mongoose.Schema({ + slug: { type: String, lowercase: true, unique: true }, + lieu: String, + oaci: String, + author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } +}, {timestamps: true, toJSON: {virtuals: true}}); + +DropzoneSchema.pre('validate', function (next) { + if (!this.slug) { + this.slugify(); + } + next(); +}); + +DropzoneSchema.methods.slugify = function () { + this.slug = slug(`${this.lieu} ${this.oaci}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); +}; + +DropzoneSchema.methods.toJSONFor = function(user){ + return { + slug: this.slug, + lieu: this.lieu, + oaci: this.oaci, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: this.author.toProfileJSONFor(user) + }; +}; + +mongoose.model('Dropzone', DropzoneSchema); diff --git a/models/mongo/Jump.js b/models/mongo/Jump.js new file mode 100644 index 0000000..4f3262c --- /dev/null +++ b/models/mongo/Jump.js @@ -0,0 +1,69 @@ +var mongoose = require('mongoose'); +var User = mongoose.model('User'); +var uniqueValidator = require('mongoose-unique-validator'); +var slug = require('slug'); + +var JumpSchema = new mongoose.Schema({ + slug: { type: String, lowercase: true, unique: true }, + date: { type: Date, default: Date.now }, + numero: { type: Number, unique: true }, + lieu: String, + oaci: String, + aeronef: String, + imat: String, + hauteur: Number, + voile: String, + taille: Number, + categorie: String, + module: String, + participants: Number, + sautants: [String], + programme: String, + accessoires: String, + zone: String, + dossier: String, + video: String, + author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } +}, {timestamps: true, toJSON: {virtuals: true}}); + +JumpSchema.plugin(uniqueValidator, { message: 'is already taken' }); + +JumpSchema.pre('validate', function (next) { + if (!this.slug) { + this.slugify(); + } + next(); +}); + +JumpSchema.methods.slugify = function () { + this.slug = slug(`${this.numero} ${this.lieu}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); +}; + +JumpSchema.methods.toJSONFor = function(user){ + return { + slug: this.slug, + date: this.date, + numero: this.numero, + lieu: this.lieu, + oaci: this.oaci, + aeronef: this.aeronef, + imat: this.imat, + hauteur: this.hauteur, + voile: this.voile, + taille: this.taille, + categorie: this.categorie, + module: this.module, + participants: this.participants, + sautants: this.sautants, + programme: this.programme, + accessoires: this.accessoires, + zone: this.zone, + dossier: this.dossier, + video: this.video, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: this.author.toProfileJSONFor(user) + }; +}; + +mongoose.model('Jump', JumpSchema); diff --git a/models/mongo/Metal.js b/models/mongo/Metal.js new file mode 100644 index 0000000..e4c9067 --- /dev/null +++ b/models/mongo/Metal.js @@ -0,0 +1,24 @@ +var mongoose = require('mongoose'); +var uniqueValidator = require('mongoose-unique-validator'); + +var MetalSchema = new mongoose.Schema({ + ordering: Number, + code: { type: String, upercase: true, unique: true }, + title: String, + color: String, + image: String +}, {timestamps: true, toJSON: {virtuals: true}}); + +MetalSchema.plugin(uniqueValidator, { message: 'is already taken' }); + +MetalSchema.methods.toJSONFor = function(){ + return { + ordering: this.ordering, + code: this.code, + title: this.title, + color: this.color, + image: this.image + }; +}; + +mongoose.model('Metal', MetalSchema); \ No newline at end of file diff --git a/models/mongo/Price.js b/models/mongo/Price.js new file mode 100644 index 0000000..685cd5e --- /dev/null +++ b/models/mongo/Price.js @@ -0,0 +1,87 @@ +var mongoose = require('mongoose'); +var uniqueValidator = require('mongoose-unique-validator'); + +var PriceSchema = new mongoose.Schema({ + timestamp: { type: Number, default: 0 }, + metal: { type: String, upercase: true, default: 'ØØØ' }, + currency: { type: String, upercase: true, default: 'ØØØ' }, + exchange: { type: String, upercase: true, default: '' }, + symbol: { type: String, upercase: true, default: '' }, + prev_close_price: { type: Number, default: 0 }, + open_price: { type: Number, default: 0 }, + low_price: { type: Number, default: 0 }, + high_price: { type: Number, default: 0 }, + open_time: { type: Number, default: 0 }, + price: { type: Number, default: 0 }, + ch: { type: Number, default: 0 }, + chp: { type: Number, default: 0 }, + ask: { type: Number, default: 0 }, + bid: { type: Number, default: 0 }, + price_gram_24k: { type: Number, default: 0 }, + price_gram_22k: { type: Number, default: 0 }, + price_gram_21k: { type: Number, default: 0 }, + price_gram_20k: { type: Number, default: 0 }, + price_gram_18k: { type: Number, default: 0 }, + price_gram_16k: { type: Number, default: 0 }, + price_gram_14k: { type: Number, default: 0 }, + price_gram_10k: { type: Number, default: 0 } +}, { timestamps: true, toJSON: { virtuals: true } }); + +PriceSchema.plugin(uniqueValidator, { message: 'is already taken' }); + +PriceSchema.methods.toJSONFor = function () { + /* + let a = { + timestamp: 1689944788, + metal: "XAU", + currency: "EUR", + exchange: "FOREXCOM", + symbol: "FOREXCOM:XAUEUR", + prev_close_price: 0.00, + open_price: 0.00, + low_price: 0.00, + high_price: 0.00, + open_time: 1689897600, + price: 0.00, + ch: 0.00, + chp: 0.00, + ask: 0.00, + bid: 0.00, + price_gram_24k: 0.00, + price_gram_22k: 0.00, + price_gram_21k: 0.00, + price_gram_20k: 0.00, + price_gram_18k: 0.00, + price_gram_16k: 0.00, + price_gram_14k: 0.00, + price_gram_10k: 0.00 + }; + */ + return { + timestamp: this.timestamp, + metal: this.metal, + currency: this.currency, + exchange: this.exchange, + symbol: this.symbol, + prev_close_price: this.prev_close_price, + open_price: this.open_price, + low_price: this.low_price, + high_price: this.high_price, + open_time: this.open_time, + price: this.price, + ch: this.ch, + chp: this.chp, + ask: this.ask, + bid: this.bid, + price_gram_24k: this.price_gram_24k, + price_gram_22k: this.price_gram_22k, + price_gram_21k: this.price_gram_21k, + price_gram_20k: this.price_gram_20k, + price_gram_18k: this.price_gram_18k, + price_gram_16k: this.price_gram_16k, + price_gram_14k: this.price_gram_14k, + price_gram_10k: this.price_gram_10k + }; +}; + +mongoose.model('Price', PriceSchema); \ No newline at end of file diff --git a/models/mongo/Product.js b/models/mongo/Product.js new file mode 100644 index 0000000..846da2c --- /dev/null +++ b/models/mongo/Product.js @@ -0,0 +1,58 @@ +var mongoose = require('mongoose'); +var uniqueValidator = require('mongoose-unique-validator'); +var slug = require('slug'); +var User = mongoose.model('User'); + +var ProductSchema = new mongoose.Schema({ + ordering: Number, + title: String, + slug: { type: String, lowercase: true, unique: true }, + prime_achat: Number, + prime_vente: Number, + metal: { type: mongoose.Schema.Types.ObjectId, ref: 'Metal' }, + poids: Number, + author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, + favoritesCount: { type: Number, default: 0 } +}, {timestamps: true, toJSON: {virtuals: true}}); + +ProductSchema.plugin(uniqueValidator, { message: 'is already taken' }); + +ProductSchema.pre('validate', function (next) { + if (!this.slug) { + this.slugify(); + } + next(); +}); + +ProductSchema.methods.slugify = function () { + this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); +}; + +ProductSchema.methods.updateFavoriteCount = function () { + var product = this; + + return User.count({ favorites: { $in: [product._id] } }).then(function (count) { + product.favoritesCount = count; + + return product.save(); + }); +}; + +ProductSchema.methods.toJSONFor = function(user){ + return { + ordering: this.ordering, + title: this.title, + slug: this.slug, + prime_achat: this.prime_achat, + prime_vente: this.prime_vente, + metal: this.metal.toJSONFor(), + poids: this.poids, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + favorited: user ? user.isFavorite(this._id) : false, + favoritesCount: this.favoritesCount, + author: this.author.toProfileJSONFor(user) + }; +}; + +mongoose.model('Product', ProductSchema); diff --git a/models/mongo/User.js b/models/mongo/User.js new file mode 100644 index 0000000..52f17d9 --- /dev/null +++ b/models/mongo/User.js @@ -0,0 +1,128 @@ +var mongoose = require('mongoose'); +var uniqueValidator = require('mongoose-unique-validator'); +var crypto = require('crypto'); +var jwt = require('jsonwebtoken'); +var secret = require('../../config').secret; + +var UserSchema = new mongoose.Schema({ + email: { type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/\S+@\S+\.\S+/, 'is invalid'], index: true }, + username: { type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/^[a-zA-Z0-9_]+$/, 'is invalid'], index: true }, + role: String, + firstname: { type: String, required: [true, "can't be blank"] }, + lastname: { type: String, required: [true, "can't be blank"] }, + phone: { type: String, required: [true, "can't be blank"] }, + licence: Number, + image: String, + bg_image: String, + favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Product' }], + following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], + hash: String, + salt: String +}, { timestamps: true, toJSON: { virtuals: true } }); + +UserSchema.plugin(uniqueValidator, { message: 'is already taken.' }); + +UserSchema.methods.validPassword = function (password) { + var hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); + return this.hash === hash; +}; + +UserSchema.methods.setPassword = function (password) { + this.salt = crypto.randomBytes(16).toString('hex'); + this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); +}; + +UserSchema.methods.generateJWT = function () { + var today = new Date(); + var exp = new Date(today); + exp.setDate(today.getDate() + 60); + + return jwt.sign({ + id: this._id, + username: this.username, + exp: parseInt(exp.getTime() / 1000) + }, secret, { algorithm: 'HS256' }); +}; + +UserSchema.methods.toAuthJSON = function () { + return { + email: this.email, + role: this.role, + token: this.generateJWT(), + firstname: this.firstname, + lastname: this.lastname, + phone: this.phone, + licence: this.licence, + username: this.username, + image: this.image || '/assets/images/users/user.jpg', + bg_image: this.bg_image || '/assets/images/users/bg_user.jpg' + }; +}; + +UserSchema.methods.toJSONFor = function () { + return { + email: this.email, + role: this.role, + firstname: this.firstname, + lastname: this.lastname, + phone: this.phone, + licence: this.licence, + username: this.username, + image: this.image || '/assets/images/users/user.jpg', + bg_image: this.bg_image || '/assets/images/users/bg_user.jpg' + }; +}; + +UserSchema.methods.toProfileJSONFor = function (user) { + return { + email: this.email, + firstname: this.firstname, + lastname: this.lastname, + phone: this.phone, + licence: this.licence, + username: this.username, + image: this.image || '/assets/images/users/user.jpg', + bg_image: this.bg_image || '/assets/images/users/bg_user.jpg', + following: user ? user.isFollowing(this._id) : false + }; +}; + +UserSchema.methods.favorite = function (id) { + if (this.favorites.indexOf(id) === -1) { + this.favorites.push(id); + } + + return this.save(); +}; + +UserSchema.methods.unfavorite = function (id) { + this.favorites.remove(id); + return this.save(); +}; + +UserSchema.methods.isFavorite = function (id) { + return this.favorites.some(function (favoriteId) { + return favoriteId.toString() === id.toString(); + }); +}; + +UserSchema.methods.follow = function (id) { + if (this.following.indexOf(id) === -1) { + this.following.push(id); + } + + return this.save(); +}; + +UserSchema.methods.unfollow = function (id) { + this.following.remove(id); + return this.save(); +}; + +UserSchema.methods.isFollowing = function (id) { + return this.following.some(function (followId) { + return followId.toString() === id.toString(); + }); +}; + +mongoose.model('User', UserSchema); diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..fa1140d --- /dev/null +++ b/nodemon.json @@ -0,0 +1,20 @@ +{ + "env" : { + "APP_ENV": "local", + "NODE_ENV" : "development", + "DEBUG": false, + "MONGODB_URI": "mongodb://localhost:27017/headupdb", + "SERVER_PORT": "3200", + "SECRET": "$2b$10$aEsAUG7Dy8dcTORi1vaQle", + "SENDGRID_API_KEY": "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", + "SENDGRID_FROM_MAIL": "contact@rampeur.fr", + "SENDGRID_TO_MAIL": "rampeur@gmail.com", + "AUTHORIZATION_PREFIX": "Api-Key", + "GOLDAPI_URL": "https://www.goldapi.io/api", + "GOLDAPI_TOKEN": "goldapi-akasnfrlfiqv0xw-io", + "COUCHDB_URL": "http://couchdb.unespace.com", + "COUCHDB_DATABASE": "headupdb", + "COUCHDB_DESIGN": "headup_app", + "COUCHDB_CREDENTIAL": "cmFtcGV1cjpES3hwMjRQUw==" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6341ea4 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "hu-api", + "version": "1.0.0", + "description": "Head Up API", + "main": "app.js", + "author": "Solide Apps ", + "contributors": [ + "Julien Gautier " + ], + "scripts": { + "mongo:start": "docker run --name hu-mongo -p 27017:27017 mongo & sleep 5", + "start": "APP_ENV=production node ./app.js", + "dev": "APP_ENV=development node ./app.js", + "local": "APP_ENV=local nodemon ./app.js", + "test": "newman run ./tests/api-tests.postman.json -e ./tests/env-api-tests.postman.json", + "stop": "lsof -ti :3200 | xargs kill", + "mongo:stop": "docker stop hu-mongo && docker rm hu-mongo" + }, + "engines": { + "node": "^16.20.1 || ^18.16.1 || ^20.3.1" + }, + "license": "UNLICENSED", + "private": true, + "dependencies": { + "@sendgrid/mail": "^7.7.0", + "bcrypt": "^5.1.0", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "ejs": "^3.1.9", + "errorhandler": "^1.5.1", + "express": "^4.18.2", + "express-jwt": "^8.4.1", + "express-session": "^1.17.3", + "jsonwebtoken": "^9.0.0", + "method-override": "3.0.0", + "methods": "1.1.2", + "mongoose": "^6.11.2", + "mongoose-unique-validator": "^3.1.0", + "morgan": "^1.10.0", + "node-fetch": "^3.3.2", + "passport": "^0.6.0", + "passport-headerapikey": "^1.2.2", + "passport-local": "^1.0.0", + "request": "^2.88.2", + "slug": "^8.2.2", + "underscore": "^1.13.6", + "uuid": "^9.0.0" + }, + "devDependencies": { + "newman": "^5.3.2", + "nodemon": "^2.0.22" + } +} \ No newline at end of file diff --git a/public/.keep b/public/.keep new file mode 100644 index 0000000..e69de29 diff --git a/qcm.json b/qcm.json new file mode 100644 index 0000000..a1e71a1 --- /dev/null +++ b/qcm.json @@ -0,0 +1,3563 @@ +{ + "qcm": [ + { + "name": "bpa", + "categories": [ + { + "num": 1, + "name": "Aéronefs", + "questions": [ + { + "num": 1, + "libelle": "La position correcte du trim avant le décollage à pleine charge est :", + "answers": [ + {"index": "A", "libelle": "Extrémité vers le haut", "correct": false}, + {"index": "B", "libelle": "Extrémité vers le bas", "correct": true} + ] + }, + { + "num": 2, + "libelle": "A bord de l’avion :", + "answers": [ + {"index": "A", "libelle": "La répartition de masse a une influence directe sur le centrage et la stabilité de l’avion.", "correct": true}, + {"index": "B", "libelle": "La répartition de masse n’a pas d’importance pour le vol.", "correct": false} + ] + }, + { + "num": 3, + "libelle": "Peut-on se déplacer à bord de l’avion pendant le décollage ?", + "answers": [ + {"index": "A", "libelle": "Non car cela modifie le centrage de l’avion.", "correct": true}, + {"index": "B", "libelle": "Oui sans problème.", "correct": false} + ] + }, + { + "num": 4, + "libelle": "Lors du décollage, si les parachutistes se déplacent inconsidérément à bord de l’avion, celui ci peut décrocher.", + "answers": [ + {"index": "A", "libelle": "Vrai", "correct": true}, + {"index": "B", "libelle": "Faux", "correct": false} + ] + }, + { + "num": 5, + "libelle": "Un vent de travers est dangereux pour l’avion.", + "answers": [ + {"index": "A", "libelle": "Au décollage et à l’atterrissage si on dépasse la limite de vent recommandée pour l’avion.", "correct": true}, + {"index": "B", "libelle": "N’est pas dangereux si l’avion n’est pas en surcharge.", "correct": false} + ] + }, + { + "num": 6, + "libelle": "Durant les manœuvres de décollage et d’atterrissage, parler au pilote présente une gène, voir un risque.", + "answers": [ + {"index": "A", "libelle": "Faux", "correct": false}, + {"index": "B", "libelle": "Vrai", "correct": true} + ] + }, + { + "num": 7, + "libelle": "Une vitesse de largage trop faible peut entraîner :", + "answers": [ + {"index": "A", "libelle": "Un risque de percuter le plan fixe.", "correct": false}, + {"index": "B", "libelle": "Un risque de décrochage.", "correct": true}, + {"index": "C", "libelle": "Un meilleur confort pour la sortie.", "correct": false} + ] + }, + { + "num": 8, + "libelle": "Pour l’atterrissage et le décollage, la sortie des dispositifs hypersustentateurs (volets) est pour le pilote :", + "answers": [ + {"index": "A", "libelle": "Fonction de paramètres tels que vent, longueur de piste, etc.", "correct": true}, + {"index": "B", "libelle": "Obligatoire quel que soit le cas.", "correct": false}, + {"index": "C", "libelle": "Interdit quel que soit le cas.", "correct": false} + ] + }, + { + "num": 9, + "libelle": "A bord de l’avion, pendant la montée, vous remarquez un écoulement de liquide.", + "answers": [ + {"index": "A", "libelle": "Vous prévenez immédiatement le pilote.", "correct": true}, + {"index": "B", "libelle": "Vous sautez normalement et en arrivant au sol vous prévenez le directeur technique.", "correct": false}, + {"index": "C", "libelle": "Vous ne dites rien à personne.", "correct": false} + ] + }, + { + "num": 10, + "libelle": "En montant à bord de l’avion, vous remarquez une arrête coupante dans l’encadrement de porte.", + "answers": [ + {"index": "A", "libelle": "Vous sautez et vous prévenez le directeur technique une fois arrivé au sol.", "correct": false}, + {"index": "B", "libelle": "Vous ne dites rien et vous sautez en faisant attention en sortant.", "correct": false}, + {"index": "C", "libelle": "Vous prévenez immédiatement le pilote et les parachutistes.", "correct": true} + ] + }, + { + "num": 11, + "libelle": "En montant à bord de l’avion avec 9 parachutistes, vous remarquez que le trim du Pilatus est en position « plein cabré » avant le décollage.", + "answers": [ + {"index": "A", "libelle": "Vous ne dites rien car c’est une position de trim normale au décollage en pleine charge pour un Pilatus.", "correct": false}, + {"index": "B", "libelle": "Vous ne dites rien car cette position n’a pas d’influence sur le décollage, quelle que soit la charge et le centrage du Pilatus.", "correct": false}, + {"index": "C", "libelle": "Vous en informez immédiatement le pilote.", "correct": true} + ] + }, + { + "num": 12, + "libelle": "La vitesse de largage d’un avion est définie par le manuel de vol.", + "answers": [ + {"index": "A", "libelle": "Vrai", "correct": false}, + {"index": "B", "libelle": "Faux", "correct": true} + ] + }, + { + "num": 13, + "libelle": "Au moment de monter à bord de l’avion, vous remarquer une fuite de carburant venant d’un réservoir.", + "answers": [ + {"index": "A", "libelle": "Vous attendez d’avoir effectué le saut pour prévenir le directeur technique.", "correct": false}, + {"index": "B", "libelle": "C’est anormal, vous prévenez immédiatement le pilote.", "correct": true}, + {"index": "C", "libelle": "C’est normal à cause de la dilatation ; vous ne dites rien.", "correct": false} + ] + }, + { + "num": 14, + "libelle": "Les avions ayant une issue de saut à l’arrière de la carlingue sont très sensibles aux problèmes de centrage.", + "answers": [ + {"index": "A", "libelle": "Vrai", "correct": true}, + {"index": "B", "libelle": "Faux", "correct": false} + ] + }, + { + "num": 15, + "libelle": "Le centrage peut limiter le nombre de parachutistes pouvant effectuer une sortie de groupe.", + "answers": [ + {"index": "A", "libelle": "Vrai", "correct": true}, + {"index": "B", "libelle": "Faux", "correct": false} + ] + }, + { + "num": 16, + "libelle": "Un nombre trop important de parachutistes effectuant une sortie de groupe sur un avion ayant une issue de saut située à l’arrière peut induire :", + "answers": [ + {"index": "A", "libelle": "Uniquement un décrochage.", "correct": false}, + {"index": "B", "libelle": "Uniquement un départ en tonneaux.", "correct": false}, + {"index": "C", "libelle": "Uniquement une vrille à plat.", "correct": false}, + {"index": "D", "libelle": "Un des points précédents et éventuellement une combinaison des trois propositions précédentes.", "correct": true} + ] + }, + { + "num": 17, + "libelle": "Au moment d’embarquer, vous remarquez d’importantes traînées d’huile sous la carlingue de l’avion.", + "answers": [ + {"index": "A", "libelle": "C’est normal car il y a toujours des fuites.", "correct": false}, + {"index": "B", "libelle": "Vous prévenez immédiatement le pilote.", "correct": true} + ] + }, + { + "num": 18, + "libelle": "La vitesse de largage est définie par :", + "answers": [ + {"index": "A", "libelle": "Le constructeur et consignée dans le manuel de vol.", "correct": true}, + {"index": "B", "libelle": "Le pilote.", "correct": false}, + {"index": "C", "libelle": "Les parachutistes en fonction du type de saut.", "correct": false} + ] + }, + { + "num": 19, + "libelle": "Au moment d’embarquer, vous remarquez une crique (fissure) sur le plan fixe de l’avion.", + "answers": [ + {"index": "A", "libelle": "Vous attendez d’avoir effectué votre saut pour avertir le directeur technique.", "correct": false}, + {"index": "B", "libelle": "Vous avertissez immédiatement le pilote.", "correct": true}, + {"index": "C", "libelle": "Vous ne dites rien, le pilote ayant effectué une visite pré-vol sait ce qu’il fait.", "correct": false} + ] + }, + { + "num": 20, + "libelle": "La vitesse de largage d’un Pilatus est d’environ :", + "answers": [ + {"index": "A", "libelle": "110 kts.", "correct": false}, + {"index": "B", "libelle": "70 kts.", "correct": true}, + {"index": "C", "libelle": "45 kts.", "correct": false} + ] + }, + { + "num": 21, + "libelle": "La vitesse de largage d’un Cessna (206 ; 207) est d ‘environ :", + "answers": [ + {"index": "A", "libelle": "50 kts.", "correct": false}, + {"index": "B", "libelle": "80 kts.", "correct": true}, + {"index": "C", "libelle": "105 kts.", "correct": false} + ] + }, + { + "num": 22, + "libelle": "La vitesse de largage d’un Cessna Caravan est d ‘environ :", + "answers": [ + {"index": "A", "libelle": "100 kts.", "correct": false}, + {"index": "B", "libelle": "80 kts.", "correct": true}, + {"index": "C", "libelle": "70 kts.", "correct": false} + ] + }, + { + "num": 23, + "libelle": "La vitesse de largage d’un gros porteur Type Hercules est d’environ :", + "answers": [ + {"index": "A", "libelle": "200 kts.", "correct": false}, + {"index": "B", "libelle": "120 kts.", "correct": true}, + {"index": "C", "libelle": "70 kts.", "correct": false} + ] + }, + { + "num": 24, + "libelle": "Une vitesse de largage trop importante pour un avion léger de type CESSNA peut entraîner une collision d’un chuteur avec le plan fixe.", + "answers": [ + {"index": "A", "libelle": "Faux", "correct": false}, + {"index": "B", "libelle": "Vrai", "correct": true} + ] + } + ] + }, + { + "num": 2, + "name": "Altimètre", + "questions": [ + { + "num": 25, + "libelle": "Plus on monte en altitude.", + "answers": [ + {"index": "A", "libelle": "Plus un altimètre est précis.", "correct": false}, + {"index": "B", "libelle": "Moins un altimètre est précis.", "correct": true} + ] + }, + { + "num": 26, + "libelle": "A 1000 mètres, une précision de + ou – 200 mètres sur un altimètre est-elle satisfaisante ?", + "answers": [ + {"index": "A", "libelle": "C’est à la limite de l’acceptable.", "correct": false}, + {"index": "B", "libelle": "Non.", "correct": true}, + {"index": "C", "libelle": "Oui.", "correct": false} + ] + }, + { + "num": 27, + "libelle": "« Tapoter » sur un altimètre pour faire se déplacer l’aiguille :", + "answers": [ + {"index": "A", "libelle": "N’a aucune conséquence.", "correct": false}, + {"index": "B", "libelle": "Est indispensable pour remettre l’aiguille à zéro.", "correct": false}, + {"index": "C", "libelle": "Augmente le jeu normal du fonctionnement et n’apporte qu’un vieillissement prématuré de l’appareil.", "correct": true} + ] + }, + { + "num": 28, + "libelle": "Les « altisons » ont-ils tous une alarme sonore de non fonctionnement ou de mauvais fonctionnement ?", + "answers": [ + {"index": "A", "libelle": "Oui.", "correct": false}, + {"index": "B", "libelle": "Non.", "correct": true} + ] + } + ] + }, + { + "num": 3, + "name": "Déclencheurs et matériels", + "questions": [ + { + "num": 29, + "libelle": "La FXC 12000 :", + "answers": [ + {"index": "A", "libelle": "Déclenche à une hauteur fixe à partir d’une vitesse supérieure à 12 m/s.", "correct": false}, + {"index": "B", "libelle": "Déclenche à une hauteur que l’on règle avant chaque saut à partir d’une vitesse supérieure à 0,5 m/s.", "correct": false}, + {"index": "C", "libelle": "Déclenche à une hauteur fixe à partir d’une vitesse supérieure à 0,5 m/s.", "correct": false}, + {"index": "D", "libelle": "Déclenche à une hauteur que l’on règle avant chaque saut à partir d’une vitesse supérieure à 12 m/s 0", "correct": true} + ] + }, + { + "num": 30, + "libelle": "Sur une FXC 12000 réglé avant le saut si l’aiguille disparaît pendant la montée en avion :", + "answers": [ + {"index": "A", "libelle": "Elle est en panne, il ne faut pas sauter.", "correct": false}, + {"index": "B", "libelle": "Il faut le signaler à un moniteur après le saut.", "correct": false}, + {"index": "C", "libelle": "C’est normal, l’aiguille disparaît à la hauteur de réglage.", "correct": true} + ] + }, + { + "num": 31, + "libelle": "Un déclencheur FXC 12000 dont l’aiguille de réglage est sur le 1 signifie :", + "answers": [ + {"index": "A", "libelle": "Réglage à 100 m.", "correct": false}, + {"index": "B", "libelle": "Réglage à 1000 m.", "correct": false}, + {"index": "C", "libelle": "Réglage à 1000 pieds (soit sensiblement 300 m).", "correct": true} + ] + }, + { + "num": 32, + "libelle": "Le saut est annulé, l’avion redescend rapidement, les déclencheurs de sécurité de type FXC 12000 peuvent fonctionner dans l’avion.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 33, + "libelle": "Les déclencheurs FXC 12000 réglés sur 1000 pieds peuvent déclencher à partir de 400 m.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 34, + "libelle": "Le saut est annulé, l’avion redescend à pleine charge, les déclencheurs FXC 12000 doivent être neutralisés en plaçant la molette sur off (couleur verte).", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 35, + "libelle": "Si un Cypres 2 est mouillé suite à une courte immersion à moins de 1,5 m de profondeur :", + "answers": [ + {"index": "A", "libelle": "Il faut faire changer le filtre de l’appareil.", "correct": true}, + {"index": "B", "libelle": "Il n’y a pas d’opération particulière à faire effectuer.", "correct": false} + ] + }, + { + "num": 36, + "libelle": "Si un Cypres affiche le code 0, cela signifie :", + "answers": [ + {"index": "A", "libelle": "Qu’il faut faire changer les piles.", "correct": false}, + {"index": "B", "libelle": "Qu’il faut l’éteindre et le remettre en fonction.", "correct": false}, + {"index": "C", "libelle": "Que le déclencheur est prêt à être utilisé.", "correct": true} + ] + }, + { + "num": 37, + "libelle": "Lors d’un posé hors zone, si l’atterrissage se fait à une hauteur de ± 10 mètres par rapport à la zone de saut habituelle, il faut éteindre le Cypres et le remettre en route le avant le prochain saut.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Faux. Seulement si la différence de hauteur excède ± 30 mètres.", "correct": false}, + {"index": "C", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 38, + "libelle": "Au bout de combien de temps un Cypres doit-il être envoyé en révision ?", + "answers": [ + {"index": "A", "libelle": "Tous les ans.", "correct": false}, + {"index": "B", "libelle": "Il n’y a pas besoin de le faire réviser, il faut simplement changer les piles tous les deux ans.", "correct": true}, + {"index": "C", "libelle": "Il faut le faire réviser quand on change les piles.", "correct": false}, + {"index": "D", "libelle": "Il faut le faire réviser tous les quatre ans.", "correct": false} + ] + }, + { + "num": 39, + "libelle": "Le Cypres expert :", + "answers": [ + {"index": "A", "libelle": "Déclenche à une hauteur fixe à partir d’une vitesse supérieure à 35 m/s.", "correct": true}, + {"index": "B", "libelle": "Déclenche à une hauteur qu’il faut régler avant chaque saut à partir d’une vitesse supérieure à 13 m/s.", "correct": false}, + {"index": "C", "libelle": "Déclenche à une hauteur qu’il faut régler avant chaque saut à partir d’une vitesse supérieure à 35 m/s.", "correct": false}, + {"index": "D", "libelle": "Déclenche à une hauteur fixe à partir d’une vitesse supérieure à 13 m/s.", "correct": false} + ] + }, + { + "num": 40, + "libelle": "Le Cypres école :", + "answers": [ + {"index": "A", "libelle": "Déclenche à 300m à partir d’une vitesse supérieure à 35 m/s.", "correct": false}, + {"index": "B", "libelle": "Déclenche à 225m une à partir d’une vitesse supérieure à 13 m/s.", "correct": false}, + {"index": "C", "libelle": "Déclenche à une hauteur qu’il faut régler avant chaque saut à partir d’une vitesse supérieure à 35 m/s.", "correct": false}, + {"index": "D", "libelle": "Déclenche à 300m à partir d’une vitesse supérieure à 13 m/s, et à 225m à partir d’une vitesse supérieure à 35m/s.", "correct": true} + ] + }, + { + "num": 41, + "libelle": "Si la vitesse est suffisante, un déclencheur Cypres confirmé déclenche à une hauteur de :", + "answers": [ + {"index": "A", "libelle": "225 m.", "correct": true}, + {"index": "B", "libelle": "315 m.", "correct": false}, + {"index": "C", "libelle": "175 m.", "correct": false} + ] + }, + { + "num": 42, + "libelle": "En cas de descente rapide avec l’avion, un déclencheur Cypres confirmé peut-il déclencher ?", + "answers": [ + {"index": "A", "libelle": "Non.", "correct": false}, + {"index": "B", "libelle": "Oui.", "correct": true} + ] + }, + { + "num": 43, + "libelle": "La vitesse de chute minimum nécessaire pour déclencher un Cypres confirmé est supérieure ou égale à :", + "answers": [ + {"index": "A", "libelle": "30 m/s.", "correct": false}, + {"index": "B", "libelle": "35 m/s.", "correct": true}, + {"index": "C", "libelle": "25 m/s.", "correct": false} + ] + }, + { + "num": 44, + "libelle": "La vitesse de chute minimum nécessaire pour déclencher un Cypres élève est supérieure ou égale à :", + "answers": [ + {"index": "A", "libelle": "35 m/s à partir de 300m.", "correct": false}, + {"index": "B", "libelle": "13 m/s à partir de 225 m.", "correct": false}, + {"index": "C", "libelle": "13 m/s à partir de 300m.", "correct": true} + ] + }, + { + "num": 45, + "libelle": "En cas d’action du Cypres, il y a :", + "answers": [ + {"index": "A", "libelle": "Sectionnement de la bouclette de fermeture (loop) du conteneur secours.", "correct": true}, + {"index": "B", "libelle": "Extraction de l’aiguille du conteneur secours.", "correct": false}, + {"index": "C", "libelle": "Libération de la voilure principale.", "correct": false} + ] + }, + { + "num": 46, + "libelle": "Le Cypres 2 doit être renvoyé en révision :", + "answers": [ + {"index": "A", "libelle": "Tous les 4 ans.", "correct": true}, + {"index": "B", "libelle": "Tous les 2 ans ou tous les 800 sauts.", "correct": false}, + {"index": "C", "libelle": "Tous les 2 ans.", "correct": false} + ] + }, + { + "num": 47, + "libelle": "Les batteries d’un Cypres 2 sont changées :", + "answers": [ + {"index": "A", "libelle": "Tous les 2 ans ou tous les 800 sauts.", "correct": false}, + {"index": "B", "libelle": "Tous les 4 ans.", "correct": true}, + {"index": "C", "libelle": "Après chaque déclenchement.", "correct": false}, + {"index": "D", "libelle": "A chaque contrôle périodique du parachute.", "correct": false} + ] + }, + { + "num": 48, + "libelle": "Le boîtier de contrôle d’un Cypres élève en fonction indique :", + "answers": [ + {"index": "A", "libelle": "Student.", "correct": false}, + {"index": "B", "libelle": "Jump.", "correct": false}, + {"index": "C", "libelle": "Rien d’écrit.", "correct": false}, + {"index": "D", "libelle": "0.", "correct": true} + ] + }, + { + "num": 49, + "libelle": "Le Cypres doit être « réarmé » tous les matins même s’il est encore en marche.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 50, + "libelle": "Le Cypres s’éteint automatiquement 14 heures après sa mise en route.", + "answers": [ + {"index": "A", "libelle": "Oui.", "correct": true}, + {"index": "B", "libelle": "Sauf si l’on resaute entre temps.", "correct": false}, + {"index": "C", "libelle": "Sauf si les batteries sont faibles.", "correct": false} + ] + }, + { + "num": 51, + "libelle": "En cas d’évacuation à 300m pendant la montée, le Cypres (confirmé et élève) peut déclencher.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": false}, + {"index": "B", "libelle": "Faux car il ne s’active que s’il a dépassé une hauteur de 450m.", "correct": true}, + {"index": "C", "libelle": "Faux car il ne s’active que s’il a dépassé une hauteur de 900m.", "correct": false} + ] + }, + { + "num": 52, + "libelle": "Le Vigile possède trois modes d’utilisation.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Cela dépend des modèles.", "correct": false}, + {"index": "C", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 53, + "libelle": "La révision du vigil doit être effectuée :", + "answers": [ + {"index": "A", "libelle": "Tous les quatre ans.", "correct": false}, + {"index": "B", "libelle": "Uniquement dans le cas d’un message d’erreur.", "correct": true}, + {"index": "C", "libelle": "Tous les 700 sauts.", "correct": false} + ] + }, + { + "num": 54, + "libelle": "Le changement des piles sur un vigil s’effectue :", + "answers": [ + {"index": "A", "libelle": "A l’apparition du message « bat low ».", "correct": true}, + {"index": "B", "libelle": "Tous les deux ans.", "correct": false}, + {"index": "C", "libelle": "Jamais.", "correct": false} + ] + }, + { + "num": 55, + "libelle": "Pour les versions confirmées, les hauteurs de déclenchement du Vigil et du Cypres sont sensiblement identiques.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 56, + "libelle": "La vitesse de déclenchement du vigil en mode « pro » est de :", + "answers": [ + {"index": "A", "libelle": "20 m/s.", "correct": false}, + {"index": "B", "libelle": "12 m/s.", "correct": false}, + {"index": "C", "libelle": "35 m/s.", "correct": true} + ] + }, + { + "num": 57, + "libelle": "La hauteur de déclenchement du vigil en mode « pro » est de :", + "answers": [ + {"index": "A", "libelle": "256 mètres.", "correct": true}, + {"index": "B", "libelle": "216 mètres.", "correct": false}, + {"index": "C", "libelle": "225 mètres.", "correct": false} + ] + }, + { + "num": 58, + "libelle": "La vitesse de déclenchement du vigil en mode « student » est de :", + "answers": [ + {"index": "A", "libelle": "12 m/s.", "correct": false}, + {"index": "B", "libelle": "20 m/s.", "correct": true}, + {"index": "C", "libelle": "35 m/s.", "correct": false} + ] + }, + { + "num": 59, + "libelle": "Qu’est-ce qu’un système de rétraction ?", + "answers": [ + {"index": "A", "libelle": "C’est le système qui permet de déventer l’extracteur après l’ouverture.", "correct": false}, + {"index": "B", "libelle": "C’est l’action de tirer sur les élévateurs.", "correct": true} + ] + }, + { + "num": 60, + "libelle": "Lors du repliage du parachute de secours :", + "answers": [ + {"index": "A", "libelle": "Le plieur qualifié contrôle la voilure dans tous les cas mais ne contrôle rien d’autre.", "correct": false}, + {"index": "B", "libelle": "Le plieur qualifié ne contrôle la voilure que si elle a été ouverte en vol.", "correct": false}, + {"index": "C", "libelle": "Le plieur qualifié fait un contrôle détaillé de l’ensemble de l’équipement.", "correct": true} + ] + }, + { + "num": 61, + "libelle": "Le contrôle périodique d’un parachute doit être fait :", + "answers": [ + {"index": "A", "libelle": "Avec la même périodicité que le pliage du secours.", "correct": true}, + {"index": "B", "libelle": "Le contrôle périodique d’un parachute n’est pas réglementé, seul le pliage du secours l’est.", "correct": false}, + {"index": "C", "libelle": "Uniquement après une ouverture en vol.", "correct": false} + ] + }, + { + "num": 62, + "libelle": "Un contrôle détaillé et un entretien du système de libération de la voilure principale doit être effectué régulièrement entre deux opérations de contrôle périodique.", + "answers": [ + {"index": "A", "libelle": "Non.", "correct": false}, + {"index": "B", "libelle": "Oui.", "correct": true} + ] + }, + { + "num": 63, + "libelle": "Quels sont les contrôles et l’entretien à réaliser sur le système de libération trois anneaux ?", + "answers": [ + {"index": "A", "libelle": "Aucun contrôle particulier, le système étant visible en permanence.", "correct": false}, + {"index": "B", "libelle": "Manipuler les sangles et nettoyer les gaines de câbles et les joncs de la poignée de libération.", "correct": true}, + {"index": "C", "libelle": "Nettoyer les gaines de câbles et les joncs avec de l’essence avion uniquement.", "correct": false} + ] + }, + { + "num": 64, + "libelle": "Sur une sangle principale de harnais, une dégradation de 5 mm peut entraîner une perte de résistance de 50 %.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 65, + "libelle": "Rallonger la longueur de la bouclette de verrouillage (loop) d’un parachute pour permettre une fermeture très aisée génère des risques d’ouverture intempestive.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 66, + "libelle": "Utiliser une suspente d’un diamètre trop important pour la confection d’une bouclette de verrouillage (loop) peut générer une non ouverture du conteneur.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 67, + "libelle": "Les loops de fermeture :", + "answers": [ + {"index": "A", "libelle": "Doivent être très courts pour éviter une ouverture intempestive.", "correct": false}, + {"index": "B", "libelle": "Doivent être tendus normalement pour éviter à la fois les blocages et les ouvertures intempestives.", "correct": true}, + {"index": "C", "libelle": "Doivent être relâché pour faciliter l’ouverture.", "correct": false} + ] + }, + { + "num": 68, + "libelle": "Quelle peut être la conséquence de l’utilisation d’un loop de fermeture trop long ?", + "answers": [ + {"index": "A", "libelle": "Un retard à l’ouverture.", "correct": false}, + {"index": "B", "libelle": "Une ouverture intempestive.", "correct": true} + ] + }, + { + "num": 69, + "libelle": "Quelle peut être la conséquence de l’utilisation d’un loop trop court ?", + "answers": [ + {"index": "A", "libelle": "Une ouverture intempestive.", "correct": false}, + {"index": "B", "libelle": "Un blocage du système d’ouverture (poignée, hand deploy, pull out).", "correct": true} + ] + }, + { + "num": 70, + "libelle": "Un parachute doit être stocké :", + "answers": [ + {"index": "A", "libelle": "Au sec et à l’abri du soleil.", "correct": true}, + {"index": "B", "libelle": "Les conditions de stockage n’ont pas d’importance.", "correct": false} + ] + }, + { + "num": 71, + "libelle": "L’exposition au soleil :", + "answers": [ + {"index": "A", "libelle": "Est un facteur de vieillissement des matières textiles.", "correct": true}, + {"index": "B", "libelle": "N’induit pas de vieillissement anormal.", "correct": false} + ] + }, + { + "num": 72, + "libelle": "Des expositions prolongées et répétées au soleil peuvent réduire la durée de vie d’une voilure de manière importante.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 73, + "libelle": "Quels rayons, générés par le soleil, induisent un vieillissement prématuré des voilures ?", + "answers": [ + {"index": "A", "libelle": "Les rayons ultra-violets.", "correct": true}, + {"index": "B", "libelle": "Les rayons électromagnétiques.", "correct": false}, + {"index": "C", "libelle": "Les rayons gamma.", "correct": false} + ] + }, + { + "num": 74, + "libelle": "Vous remarquez un début de décousure entre deux sangles d’un sac harnais :", + "answers": [ + {"index": "A", "libelle": "Vous ne laissez pas sauter le parachute et vous consultez un moniteur ou un plieur réparateur.", "correct": true}, + {"index": "B", "libelle": "Cela n’est pas gênant si elle fait moins de 5 mm.", "correct": false}, + {"index": "C", "libelle": "Cela n’est pas gênant si elle fait moins de 8 mm.", "correct": false} + ] + }, + { + "num": 75, + "libelle": "Une déchirure dans un sac de déploiement (POD) :", + "answers": [ + {"index": "A", "libelle": "Nécessite un contrôle par une personne qualifiée quelque soit sa longueur.", "correct": true}, + {"index": "B", "libelle": "Ne nécessite pas de réparation si elle fait moins de 5 cm.", "correct": false}, + {"index": "C", "libelle": "Ne nécessite pas de réparation si elle fait moins de 2 cm.", "correct": false}, + {"index": "D", "libelle": "Ne nécessite pas de réparation si elle fait moins de 8 cm.", "correct": false} + ] + }, + { + "num": 76, + "libelle": "Une suspente présentant une amorce de rupture, même légère, peut voir sa résistance diminuée de façon importante.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 77, + "libelle": "Une déchirure sur une voilure principale :", + "answers": [ + {"index": "A", "libelle": "Nécessite un contrôle par une personne qualifiée quelque soit sa longueur.", "correct": true}, + {"index": "B", "libelle": "Ne nécessite pas de réparation si elle fait moins de 8 cm.", "correct": false}, + {"index": "C", "libelle": "Ne nécessite pas de réparation si elle fait moins de 2 cm.", "correct": false}, + {"index": "D", "libelle": "Ne nécessite pas de réparation si elle fait moins de 5 cm.", "correct": false} + ] + } + ] + }, + { + "num": 4, + "name": "Largage", + "questions": [ + { + "num": 78, + "libelle": "Durant la montée en altitude, vers 1200 m, le moteur de l’avion s’arrête.", + "answers": [ + {"index": "A", "libelle": "Vous attendez les consignes du pilote pour ouvrir la porte et procéder à l’évacuation.", "correct": true}, + {"index": "B", "libelle": "Vous ouvrez la porte et vous sautez en enjoignant aux autres de vous suivre.", "correct": false} + ] + }, + { + "num": 79, + "libelle": "Lors de l’équipement pour un saut à 4000 mètres, le vent au sol est de 5 m/s. Au décollage le vent est de 7 m/s. A 2500 mètres le pilote vous annonce que le vent au sol est de 9 m/s avec rafales à 13 m/s.", + "answers": [ + {"index": "A", "libelle": "Il faut annuler le largage et demander au pilote de redescendre.", "correct": true}, + {"index": "B", "libelle": "Vous larguez tout de suite avant que le vent n’augmente encore.", "correct": false}, + {"index": "C", "libelle": "Vous poursuivez la montée et larguez à 4000 m.", "correct": false} + ] + }, + { + "num": 80, + "libelle": "L’avion est bloqué à 800 m par des nuages :", + "answers": [ + {"index": "A", "libelle": "Vous sautez et ouvrez immédiatement.", "correct": false}, + {"index": "B", "libelle": "Vous redescendez avec l’avion.", "correct": true}, + {"index": "C", "libelle": "Seuls les parachutistes titulaires du brevet C ou D peuvent sauter s’ils le désirent.", "correct": false} + ] + }, + { + "num": 81, + "libelle": "A 1500 m de hauteur, le pilote annonce que le largage est annulé et qu’il va redescendre et se poser à pleine charge.", + "answers": [ + {"index": "A", "libelle": "Il faut faire cela uniquement si l’avion vole porte ouverte.", "correct": false}, + {"index": "B", "libelle": "Il faut faire cela uniquement en dessous de 500 m.", "correct": false}, + {"index": "C", "libelle": "Il faut désarmer les déclencheurs de sécurité ou demander au pilote de respecter une vitesse de descente fonction du type de déclencheur.", "correct": true} + ] + }, + { + "num": 82, + "libelle": "A 1800 m de hauteur, le pilote réduit la puissance et se met à descendre.", + "answers": [ + {"index": "A", "libelle": "Ne sachant exactement ce qui se passe, vous sautez immédiatement.", "correct": false}, + {"index": "B", "libelle": "Il faut couper les déclencheurs de sécurité ou demander au pilote de respecter une vitesse de descente fonction du type de déclencheur.", "correct": true}, + {"index": "C", "libelle": "Vous désarmez les déclencheurs uniquement en dessous de 500 m.", "correct": false} + ] + }, + { + "num": 83, + "libelle": "Au moment du largage :", + "answers": [ + {"index": "A", "libelle": "Il ne faut pas sauter sans l’accord du pilote.", "correct": true}, + {"index": "B", "libelle": "C’est sans importance.", "correct": false} + ] + }, + { + "num": 84, + "libelle": "Pour embarquer dans l’avion.", + "answers": [ + {"index": "A", "libelle": "C’est sans importance.", "correct": false}, + {"index": "B", "libelle": "Il ne faut pas passer derrière l’avion.", "correct": false}, + {"index": "C", "libelle": "Il ne faut jamais passer devant l’hélice.", "correct": true} + ] + }, + { + "num": 85, + "libelle": "A 3000 m, vous remarquez que votre altimètre indique 3800 m.", + "answers": [ + {"index": "A", "libelle": "C’est normal si la température est supérieure à la normale.", "correct": false}, + {"index": "B", "libelle": "Vous le recalez à 3000 m.", "correct": false}, + {"index": "C", "libelle": "C’est normal s’il y a des inversions de pression atmosphérique.", "correct": false}, + {"index": "D", "libelle": "Vous ne sautez pas.", "correct": true} + ] + }, + { + "num": 86, + "libelle": "A bord de l’avion, vous remarquez lors de la vérification, que le système trois anneaux d’un parachutiste est mal monté.", + "answers": [ + {"index": "A", "libelle": "Le parachutiste ne doit pas sauter.", "correct": true}, + {"index": "B", "libelle": "La probabilité d’une procédure de secours étant très faible, vous ne dites rien pour ne pas l’inquiéter.", "correct": false}, + {"index": "C", "libelle": "Vous démonter et remonter l’élévateur en demandant au pilote de refaire un tour.", "correct": false} + ] + }, + { + "num": 87, + "libelle": "Lors de l’embarquement, vous constatez que les S.O.A. d’un largage précédant sont toujours accrochées.", + "answers": [ + {"index": "A", "libelle": "Vous pouvez les laisser accrochées.", "correct": false}, + {"index": "B", "libelle": "Vous devez les décrocher et les ranger avant d’embarquer.", "correct": true}, + {"index": "C", "libelle": "Vous les décrochez et les rangez après avoir embarqué.", "correct": false} + ] + }, + { + "num": 88, + "libelle": "Un G.P.S. dans l’avion :", + "answers": [ + {"index": "A", "libelle": "Indique que l’espace aérien est dégagé.", "correct": false}, + {"index": "B", "libelle": "Permet au pilote de prendre l’axe plus facilement.", "correct": true}, + {"index": "C", "libelle": "Calcule le point de largage.", "correct": false} + ] + }, + { + "num": 89, + "libelle": "La vitesse de vol et la trajectoire de l’avion par rapport au sol :", + "answers": [ + {"index": "A", "libelle": "Ne donne pas d’indications sur le vent en altitude.", "correct": false}, + {"index": "B", "libelle": "Donne des indications sur le vent en altitude.", "correct": true} + ] + }, + { + "num": 90, + "libelle": "Ceux qui ouvrent le plus bas :", + "answers": [ + {"index": "A", "libelle": "Partent généralement en premier de l’avion.", "correct": true}, + {"index": "B", "libelle": "Partent généralement en dernier de l’avion.", "correct": false}, + {"index": "C", "libelle": "La hauteur d’ouverture n’a pas d’importance pour l’ordre des départs.", "correct": false} + ] + }, + { + "num": 91, + "libelle": "Ceux qui ouvrent le plus haut :", + "answers": [ + {"index": "A", "libelle": "La hauteur d’ouverture n’a pas d’importance pour l’ordre des départs.", "correct": false}, + {"index": "B", "libelle": "Partent généralement en dernier de l’avion.", "correct": true}, + {"index": "C", "libelle": "Partent généralement en premier de l’avion.", "correct": false} + ] + }, + { + "num": 92, + "libelle": "Sur un même passage, les grands groupes partent généralement :", + "answers": [ + {"index": "A", "libelle": "En premier.", "correct": true}, + {"index": "B", "libelle": "En dernier.", "correct": false}, + {"index": "C", "libelle": "C’est sans importance.", "correct": false} + ] + }, + { + "num": 93, + "libelle": "Au moment du largage, il faut :", + "answers": [ + {"index": "A", "libelle": "Se contenter de vérifier que l’on saute au dessus du terrain et assurer l’espacement entre les départs.", "correct": false}, + {"index": "B", "libelle": "Contrôler l’axe, le point de largage et assurer l’espacement entre les départs.", "correct": true} + ] + }, + { + "num": 94, + "libelle": "Au moment du largage, s‘assurer que l’espace aérien est dégagé en dessous :", + "answers": [ + {"index": "A", "libelle": "Est de la responsabilité du pilote, du directeur de séance et du largueur.", "correct": true}, + {"index": "B", "libelle": "Est de la responsabilité du directeur de séance uniquement.", "correct": false}, + {"index": "C", "libelle": "Est de la responsabilité du pilote uniquement.", "correct": false} + ] + }, + { + "num": 95, + "libelle": "Si au moment du largage, on constate qu’il y a des voiles ouvertes en dessous de l’axe et assez haut.", + "answers": [ + {"index": "A", "libelle": "Chacun assure sa sécurité une fois en chute.", "correct": false}, + {"index": "B", "libelle": "On refait un passage ou/et on décale suffisamment l’axe de largage.", "correct": true} + ] + }, + { + "num": 96, + "libelle": "Quand on largue, si l’avion est en montée :", + "answers": [ + {"index": "A", "libelle": "Le fait que l’avion monte n’influence pas la visualisation du point de largage.", "correct": false}, + {"index": "B", "libelle": "On a tendance à larguer trop court (trop tôt).", "correct": true}, + {"index": "C", "libelle": "On a tendance à larguer trop long (trop loin).", "correct": false} + ] + }, + { + "num": 97, + "libelle": "Quand on largue, si l’avion est en descente :", + "answers": [ + {"index": "A", "libelle": "On a tendance à larguer trop long (trop loin).", "correct": true}, + {"index": "B", "libelle": "On a tendance à larguer trop court (trop tôt).", "correct": false}, + {"index": "C", "libelle": "Le fait que l’avion descende n’influence pas la visualisation du point de largage.", "correct": false} + ] + }, + { + "num": 98, + "libelle": "Quand on large, si l’avion est incliné à droite :", + "answers": [ + {"index": "A", "libelle": "Le fait que l’avion soit incliné n’influence pas la visualisation du point de largage.", "correct": false}, + {"index": "B", "libelle": "On a tendance à larguer trop à gauche.", "correct": true}, + {"index": "C", "libelle": "On a tendance à larguer trop à droite.", "correct": false} + ] + }, + { + "num": 99, + "libelle": "Si vous larguez au cap 270° :", + "answers": [ + {"index": "A", "libelle": "Vous larguez en direction du sud.", "correct": false}, + {"index": "B", "libelle": "Vous larguez en direction de l’est.", "correct": false}, + {"index": "C", "libelle": "Vous larguez en direction du nord.", "correct": false}, + {"index": "D", "libelle": "Vous larguez en direction de l’ouest.", "correct": true} + ] + }, + { + "num": 100, + "libelle": "Si vous larguez au cap 180° :", + "answers": [ + {"index": "A", "libelle": "Vous larguez en direction de l'ouest.", "correct": false}, + {"index": "B", "libelle": "Vous larguez en direction du sud.", "correct": true}, + {"index": "C", "libelle": "Vous larguez en direction de l'est.", "correct": false}, + {"index": "D", "libelle": "Vous larguez en direction du nord.", "correct": false} + ] + }, + { + "num": 101, + "libelle": "Si vous larguez au cap 225° :", + "answers": [ + {"index": "A", "libelle": "Vous larguez en direction du nord-est.", "correct": false}, + {"index": "B", "libelle": "Vous larguez en direction de sud-ouest.", "correct": true}, + {"index": "C", "libelle": "Vous larguez en direction du nord-ouest.", "correct": false}, + {"index": "D", "libelle": "Vous larguez en direction du sud-est.", "correct": false} + ] + }, + { + "num": 102, + "libelle": "En cas d’ouverture intempestive d’un parachute dans l’avion durant la montée :", + "answers": [ + {"index": "A", "libelle": "Il faut placer le parachutiste au fond de l’avion avant le largage.", "correct": false}, + {"index": "B", "libelle": "Il faut placer le parachutiste à un endroit où il ne gênera pas le largage.", "correct": false}, + {"index": "C", "libelle": "Il faut placer le parachutiste à coté du pilote avant le largage.", "correct": false}, + {"index": "D", "libelle": "Il faut suspendre la montée et se reposer.", "correct": true} + ] + }, + { + "num": 103, + "libelle": "En cas d’ouverture intempestive d’un parachute dans l’avion durant la montée :", + "answers": [ + {"index": "A", "libelle": "Il faut placer le parachutiste à un endroit où il ne gênera pas le largage.", "correct": false}, + {"index": "B", "libelle": "Il faut essayer de refermer le parachute avant le largage.", "correct": false}, + {"index": "C", "libelle": "On peut poursuivre la montée et effectuer le largage.", "correct": false}, + {"index": "D", "libelle": "Il faut suspendre la montée et se reposer.", "correct": true} + ] + }, + { + "num": 104, + "libelle": "Avec du vent, ceux qui chutent le plus vite, par exemple le « free-fly » :", + "answers": [ + {"index": "A", "libelle": "Subissent une dérive moins importante que ceux qui chutent à plat.", "correct": true}, + {"index": "B", "libelle": "Subissent une dérive plus importante que ceux qui chutent à plat.", "correct": false} + ] + }, + { + "num": 105, + "libelle": "Ceux qui ouvrent haut :", + "answers": [ + {"index": "A", "libelle": "Subissent une dérive sous voile moins grande que ceux qui ouvrent plus bas.", "correct": false}, + {"index": "B", "libelle": "Subissent une dérive sous voile plus grande que ceux qui ouvrent plus bas.", "correct": true} + ] + }, + { + "num": 106, + "libelle": "De quoi dépend la distance de séparation horizontale entre deux parachutistes qui ne sautent pas ensemble ?", + "answers": [ + {"index": "A", "libelle": "De la vitesse de l’avion uniquement.", "correct": false}, + {"index": "B", "libelle": "Du temps laissé entre deux départs uniquement.", "correct": false}, + {"index": "C", "libelle": "De la vitesse de l’avion et du temps entre deux départs.", "correct": true} + ] + }, + { + "num": 107, + "libelle": "Qu’est-ce que la projection ?", + "answers": [ + {"index": "A", "libelle": "C’est l’impulsion donnée en sortie d’avion.", "correct": false}, + {"index": "B", "libelle": "C’est la distance horizontale parcourue pendant les dix premières secondes de chute.", "correct": true}, + {"index": "C", "libelle": "C’est le temps mis pour atteindre la vitesse maximale de chute.", "correct": false} + ] + }, + { + "num": 108, + "libelle": "Qu’est- ce que la dérive totale due au vent au cours d’un saut ?", + "answers": [ + {"index": "A", "libelle": "C’est la dérive en chute / par la dérive parachute ouvert.", "correct": false}, + {"index": "B", "libelle": "C’est la dérive en chute – la dérive parachute ouvert.", "correct": false}, + {"index": "C", "libelle": "C’est la dérive en chute + la dérive parachute ouvert.", "correct": true} + ] + }, + { + "num": 109, + "libelle": "On appelle dérive totale :", + "answers": [ + {"index": "A", "libelle": "Le temps de chute + le temps de descente parachute ouvert.", "correct": false}, + {"index": "B", "libelle": "Le produit (temps de chute + temps de descente parachute ouvert) x vitesse du vent.", "correct": true}, + {"index": "C", "libelle": "La vitesse du vent.", "correct": false} + ] + }, + { + "num": 110, + "libelle": "En chute libre avec du vent :", + "answers": [ + {"index": "A", "libelle": "On subit une dérive égale à la distance parcourue multiplié par la vitesse du vent.", "correct": false}, + {"index": "B", "libelle": "On subit une dérive égale au temps de chute multiplié par la vitesse du vent.", "correct": true}, + {"index": "C", "libelle": "On ne subit aucune dérive.", "correct": false} + ] + }, + { + "num": 111, + "libelle": "Parachute ouvert :", + "answers": [ + {"index": "A", "libelle": "On subit une dérive égale au temps de descente multiplié par la vitesse du vent.", "correct": true}, + {"index": "B", "libelle": "On ne subit aucune dérive.", "correct": false}, + {"index": "C", "libelle": "On subit une dérive égale à la distance parcourue multiplié par la vitesse du vent.", "correct": false} + ] + }, + { + "num": 112, + "libelle": "Sans vent, sur un avion de type Pilatus, le temps entre deux départ doit être de :", + "answers": [ + {"index": "A", "libelle": "8 à 10 secondes.", "correct": true}, + {"index": "B", "libelle": "5 à 6 secondes.", "correct": false}, + {"index": "C", "libelle": "3 à 4 secondes.", "correct": false}, + {"index": "D", "libelle": "12 à 14 secondes.", "correct": false} + ] + }, + { + "num": 113, + "libelle": "Sans vent, sur un avion de type Pilatus, 5 départs successifs, en respectant les espacements recommandés implique une distance de largage de :", + "answers": [ + {"index": "A", "libelle": "Environ 800 m.", "correct": false}, + {"index": "B", "libelle": "Environ 300 m.", "correct": false}, + {"index": "C", "libelle": "Environ 500 m.", "correct": false}, + {"index": "D", "libelle": "Environ 1150 m.", "correct": true} + ] + }, + { + "num": 114, + "libelle": "Avec un avion gros porteur, comparativement à un avion de type Pilatus ou Cessna :", + "answers": [ + {"index": "A", "libelle": "La vitesse de largage est plus élevée, il faut diminuer l’espacement entre les départs.", "correct": true}, + {"index": "B", "libelle": "La vitesse de largage est plus élevée, il faut augmenter l’espacement entre les départs.", "correct": false}, + {"index": "C", "libelle": "La vitesse de largage est moins élevée, il faut diminuer l’espacement entre les départs.", "correct": false}, + {"index": "D", "libelle": "La vitesse de largage est moins élevée, il faut augmenter l’espacement entre les départs.", "correct": false} + ] + }, + { + "num": 115, + "libelle": "La météorologie vous donne un vent de 35 kts de 1000 à 4000 mètres. La dérive en chute de 4000 à 1000 mètres pour un parachutiste qui chute à plat sera de :", + "answers": [ + {"index": "A", "libelle": "Environ 600 m.", "correct": false}, + {"index": "B", "libelle": "Environ 300 m.", "correct": false}, + {"index": "C", "libelle": "Environ 1100 m.", "correct": true} + ] + }, + { + "num": 116, + "libelle": "L’avion avance lentement par rapport au sol :", + "answers": [ + {"index": "A", "libelle": "Il vole face à un vent fort. Je conserve l’espacement standard entre les départs, le vent en altitude n’ayant que peu d’influence.", "correct": false}, + {"index": "B", "libelle": "Il vole face à un vent fort. Il faut diminuer le temps entre les départs.", "correct": false}, + {"index": "C", "libelle": "Il vole face à un vent fort. Il faut augmenter le temps entre les départs.", "correct": true} + ] + }, + { + "num": 117, + "libelle": "Pour déterminer le point de largage, il faut observer :", + "answers": [ + {"index": "A", "libelle": "Le vent au sol et le vent en altitude.", "correct": true}, + {"index": "B", "libelle": "Surtout le vent au sol.", "correct": false}, + {"index": "C", "libelle": "Uniquement le vent en altitude.", "correct": false} + ] + }, + { + "num": 118, + "libelle": "Par vent fort, il faut larguer :", + "answers": [ + {"index": "A", "libelle": "Au vent du terrain, d’autant plus loin que le vent est fort.", "correct": true}, + {"index": "B", "libelle": "Au vent du terrain, d’autant moins loin que le vent est fort.", "correct": false}, + {"index": "C", "libelle": "Sous le vent du terrain, d’autant moins loin que le vent est fort.", "correct": false}, + {"index": "D", "libelle": "Sous le vent du terrain, d’autant plus loin que le vent est fort.", "correct": false} + ] + }, + { + "num": 119, + "libelle": "Par vent nul, il faut larguer :", + "answers": [ + {"index": "A", "libelle": "A la verticale du terrain.", "correct": true}, + {"index": "B", "libelle": "Loin de la verticale du terrain.", "correct": false} + ] + }, + { + "num": 120, + "libelle": "Si on largue vent arrière avec plusieurs départs au même passage, il faut :", + "answers": [ + {"index": "A", "libelle": "Anticiper le départ des premiers et espacer les départs au moins autant que vent de face.", "correct": true}, + {"index": "B", "libelle": "Espacer un peu moins les départs pour compenser la vitesse sol de l’avion.", "correct": false}, + {"index": "C", "libelle": "Larguer au même endroit que vent de face.", "correct": false} + ] + }, + { + "num": 121, + "libelle": "Par vent fort en larguant face au vent, il faut :", + "answers": [ + {"index": "A", "libelle": "On ne tient jamais compte du vent pour déterminer l’espacement des départs.", "correct": false}, + {"index": "B", "libelle": "Espacer davantage les départs que par vent faible.", "correct": true}, + {"index": "C", "libelle": "Espacer un peu moins les départs que par vent faible.", "correct": false} + ] + }, + { + "num": 122, + "libelle": "Quand le vent est fort entre 4000 m et 1000 m", + "answers": [ + {"index": "A", "libelle": "Il vaut mieux faire partir le groupe de FF avant le groupe de VR.", "correct": false}, + {"index": "B", "libelle": "Il vaut mieux faire partir le groupe de VR avant le groupe de FF.", "correct": true}, + {"index": "C", "libelle": "C’est sans importance.", "correct": false} + ] + }, + { + "num": 123, + "libelle": "Quand le vent est très faible entre 4000 m et 1000 m :", + "answers": [ + {"index": "A", "libelle": "Il vaut mieux faire partir le groupe de VR avant le groupe de FF", "correct": false}, + {"index": "B", "libelle": "Il vaut mieux faire partir le groupe de FF avant le groupe de VR", "correct": true}, + {"index": "C", "libelle": "C’est sans importance.", "correct": false} + ] + }, + { + "num": 124, + "libelle": "Quand on constate sur l’axe de largage que l’avion vole doucement par rapport au sol.", + "answers": [ + {"index": "A", "libelle": "On vole face à un vent fort, il faut partir loin et espacer davantage les départs.", "correct": true}, + {"index": "B", "libelle": "On vole face à un vent fort, il n’y a pas de précautions particulières à prendre.", "correct": false}, + {"index": "C", "libelle": "Cela ne signifie rien de particulier.", "correct": false} + ] + }, + { + "num": 125, + "libelle": "Après avoir vérifié son ouverture :", + "answers": [ + {"index": "A", "libelle": "Il faut s’orienter face au terrain dès que possible.", "correct": false}, + {"index": "B", "libelle": "Il faut s’orienter face au vent dès que possible.", "correct": false}, + {"index": "C", "libelle": "Il faut s’orienter perpendiculairement à l’axe de largage dès que possible.", "correct": true}, + {"index": "D", "libelle": "L’orientation n’a pas d’importance.", "correct": false} + ] + }, + { + "num": 126, + "libelle": "Parachute ouvert, ceux qui ont ouvert le plus haut :", + "answers": [ + {"index": "A", "libelle": "Ils n’ont pas à s’occuper des autres.", "correct": false}, + {"index": "B", "libelle": "Doivent essayer de ne pas descendre trop vite pour ne pas rattraper les autres.", "correct": true}, + {"index": "C", "libelle": "Doivent descendre le plus vite possible pour rattraper les autres.", "correct": false} + ] + }, + { + "num": 127, + "libelle": "Parachute ouvert :", + "answers": [ + {"index": "A", "libelle": "C’est possible si l’on passe juste derrière l’autre voilure.", "correct": false}, + {"index": "B", "libelle": "Il faut éviter de suivre des trajectoires convergentes avec une autre voilure.", "correct": true} + ] + }, + { + "num": 128, + "libelle": "Si deux parachutistes volent en direction l’un de l’autre, face à face :", + "answers": [ + {"index": "A", "libelle": "L’un des deux doit dégager sur sa gauche et l’autre sur sa droite.", "correct": false}, + {"index": "B", "libelle": "Les deux doivent dégager sur leur gauche.", "correct": false}, + {"index": "C", "libelle": "Les deux doivent dégager sur leur droite.", "correct": true} + ] + }, + { + "num": 129, + "libelle": "Si deux parachutistes volent en direction l’un de l’autre, face à face :", + "answers": [ + {"index": "A", "libelle": "Elle est égale à la somme des vitesses des deux voilures.", "correct": true}, + {"index": "B", "libelle": "Elle est égale à celle de la plus lente des deux voilures.", "correct": false}, + {"index": "C", "libelle": "Leur vitesse de rapprochement est égale à celle de la plus rapide des deux voilures.", "correct": false} + ] + }, + { + "num": 130, + "libelle": "Combien de temps mettront pour se rejoindre deux voilures dont les vitesses horizontales respective sont de 10 mètres par seconde, si elles sont ouvertes face à face à 100 mètres de distance ?", + "answers": [ + {"index": "A", "libelle": "20 secondes.", "correct": false}, + {"index": "B", "libelle": "15 secondes.", "correct": false}, + {"index": "C", "libelle": "10 secondes.", "correct": false}, + {"index": "D", "libelle": "5 secondes.", "correct": true} + ] + }, + { + "num": 131, + "libelle": "Deux parachutistes partent à 4000 mètres à 8 secondes d’intervalle d’un avion volant à la vitesse de 70 nœuds (vitesse sol). Ils chutent sans déplacement horizontal dans la masse d’air. Le vent est nul. Quelle est environ leur distance horizontale de séparation à l’ouverture ?", + "answers": [ + {"index": "A", "libelle": "330 m.", "correct": false}, + {"index": "B", "libelle": "280 m.", "correct": true}, + {"index": "C", "libelle": "200 m.", "correct": false}, + {"index": "D", "libelle": "160 m.", "correct": false} + ] + }, + { + "num": 132, + "libelle": "Avec une vitesse de largage de 70 kts et un vent de face de 40 kts de 1000 à 4000 mètres, quel est l’espacement souhaitable entre les départs lors d’un largage à 4000 mètres afin d’obtenir un espacement horizontal de 300 mètres entre chaque départ ?", + "answers": [ + {"index": "A", "libelle": "20 secondes.", "correct": true}, + {"index": "B", "libelle": "15 secondes.", "correct": false}, + {"index": "C", "libelle": "12 secondes.", "correct": false}, + {"index": "D", "libelle": "10 secondes.", "correct": false} + ] + }, + { + "num": 133, + "libelle": "Si un parachutiste évolue devant vous et un peu au-dessus :", + "answers": [ + {"index": "A", "libelle": "Il a la priorité parce qu’il ne vous voit pas.", "correct": true}, + {"index": "B", "libelle": "Vous avez la priorité parce que vous êtes le plus bas.", "correct": false} + ] + }, + { + "num": 134, + "libelle": "Si un parachutiste se trouve au-dessus d’un obstacle en bordure de la zone de poser, un peu au-dessus de vous :", + "answers": [ + {"index": "A", "libelle": "Vous avez la priorité car vous êtes en dessous.", "correct": false}, + {"index": "B", "libelle": "Il a la priorité et vous devez lui laisser la place pour rentrer la zone de poser.", "correct": true} + ] + }, + { + "num": 135, + "libelle": "Un élève débutant et un parachutiste confirmé se présentent en même temps dans le circuit d’atterrissage :", + "answers": [ + {"index": "A", "libelle": "C’est le plus expérimenté qui a la priorité.", "correct": false}, + {"index": "B", "libelle": "C’est le moins expérimenté qui a la priorité.", "correct": true} + ] + }, + { + "num": 136, + "libelle": "Entre un tandem et un élève débutant :", + "answers": [ + {"index": "A", "libelle": "C’est le tandem qui est prioritaire car il est moins manœuvrant.", "correct": false}, + {"index": "B", "libelle": "C’est l’élève qui est prioritaire car il est moins expérimenté.", "correct": true} + ] + } + ] + }, + { + "num": 5, + "name": "Règlementation", + "questions": [ + { + "num": 137, + "libelle": "Le pilote traverse une fine couche nuageuse à 2000 mètres pour monter larguer à 4000 mètres. Personne ne voit le sol au moment du largage. Celui-ci s’effectue à l’aide au GPS.", + "answers": [ + {"index": "A", "libelle": "C’est une infraction.", "correct": true}, + {"index": "B", "libelle": "Cela ne constitue pas une infraction si le pilote est qualifié IFR.", "correct": false}, + {"index": "C", "libelle": "Si le pilote est qualifié IFR et titulaire d’une licence professionnelle ce n’est pas une infraction.", "correct": false}, + {"index": "D", "libelle": "Cela ne constitue pas une infraction car l’avion est équipé d’un GPS.", "correct": false} + ] + }, + { + "num": 138, + "libelle": "Une couche de nuageuse soudée est à 3000 mètres. Vous demandez au pilote de la traverser et de larguer à l’aide du GPS.", + "answers": [ + {"index": "A", "libelle": "Si le pilote est qualifié IFR et titulaire d’une licence professionnelle ce n’est pas une infraction.", "correct": false}, + {"index": "B", "libelle": "Cela ne constitue pas une infraction car l’avion est équipé d’un GPS", "correct": false}, + {"index": "C", "libelle": "Cela ne constitue pas une infraction si le pilote est qualifié IFR.", "correct": false}, + {"index": "D", "libelle": "C’est une infraction.", "correct": true} + ] + }, + { + "num": 139, + "libelle": "En VFR, la traversée d’une couche nuageuse, même mince, est interdite :", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 140, + "libelle": "Un pilote titulaire d’une licence professionnelle ou privée en conditions de vol VMC peut se voir retirer sa licence temporairement ou à vie s’il traverse une couche nuageuse ou s’il pénètre dans un nuage.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 141, + "libelle": "Dans l’avion, la présence d’un « coupe sangle » est obligatoire.", + "answers": [ + {"index": "A", "libelle": "Uniquement si l’avion est équipé pour le largage des élèves en ouverture automatique.", "correct": false}, + {"index": "B", "libelle": "Uniquement lors du largage d’élèves en ouverture automatique.", "correct": false}, + {"index": "C", "libelle": "Dans tous les cas quelque soit le type de largage effectué.", "correct": true} + ] + }, + { + "num": 142, + "libelle": "Quel est le brevet minimum nécessaire afin d’effectuer des sauts de nuit ?", + "answers": [ + {"index": "A", "libelle": "Le brevet B.", "correct": false}, + {"index": "B", "libelle": "Le BPA.", "correct": true}, + {"index": "C", "libelle": "Le brevet C ou D.", "correct": false} + ] + }, + { + "num": 143, + "libelle": "Quel est le brevet qui donne l’autorisation d’effectuer des sauts à haute altitude ?", + "answers": [ + {"index": "A", "libelle": "Le BPA.", "correct": true}, + {"index": "B", "libelle": "Le brevet A.", "correct": false}, + {"index": "C", "libelle": "Le brevet B.", "correct": false} + ] + }, + { + "num": 144, + "libelle": "Quel est le brevet qui donne l’autorisation d’effectuer des sauts de ballon ?", + "answers": [ + {"index": "A", "libelle": "Le brevet A.", "correct": false}, + {"index": "B", "libelle": "Le brevet B.", "correct": false}, + {"index": "C", "libelle": "Le BPA.", "correct": false}, + {"index": "D", "libelle": "Le brevet C ou D.", "correct": true} + ] + }, + { + "num": 145, + "libelle": "Quel est le brevet qui donne la possibilité de participer à des compétitions ?", + "answers": [ + {"index": "A", "libelle": "Le brevet A.", "correct": false}, + {"index": "B", "libelle": "Le brevet B dans la spécialité correspondante.", "correct": true}, + {"index": "C", "libelle": "Le brevet C.", "correct": false} + ] + }, + { + "num": 146, + "libelle": "La durée de la validité du contrôle périodique d’un parachute est de :", + "answers": [ + {"index": "A", "libelle": "24 mois.", "correct": false}, + {"index": "B", "libelle": "12 mois.", "correct": true}, + {"index": "C", "libelle": "6 mois.", "correct": false}, + {"index": "D", "libelle": "3 mois.", "correct": false} + ] + }, + { + "num": 147, + "libelle": "La vitesse de vent maximale autorisée au sol pour des parachutistes titulaires uniquement du brevet A est de :", + "answers": [ + {"index": "A", "libelle": "12 m/s.", "correct": false}, + {"index": "B", "libelle": "11 m/s.", "correct": true}, + {"index": "C", "libelle": "9 m/s.", "correct": false}, + {"index": "D", "libelle": "7 m/s.", "correct": false} + ] + }, + { + "num": 148, + "libelle": "La vitesse de vent maximale autorisée au sol pour des parachutistes titulaires uniquement du brevet B est de :", + "answers": [ + {"index": "A", "libelle": "12 m/s.", "correct": false}, + {"index": "B", "libelle": "11 m/s.", "correct": true}, + {"index": "C", "libelle": "9 m/s.", "correct": false}, + {"index": "D", "libelle": "7 m/s.", "correct": false} + ] + }, + { + "num": 149, + "libelle": "La vitesse de vent maximale autorisée au sol pour des sauts en tandem est de :", + "answers": [ + {"index": "A", "libelle": "13 m/s.", "correct": false}, + {"index": "B", "libelle": "15 m/s.", "correct": false}, + {"index": "C", "libelle": "12 m/s.", "correct": false}, + {"index": "D", "libelle": "11 m/s.", "correct": true} + ] + }, + { + "num": 150, + "libelle": "La vitesse de vent maximale autorisée au sol pour des élèves non titulaires du brevet A est de :", + "answers": [ + {"index": "A", "libelle": "5 m/s.", "correct": false}, + {"index": "B", "libelle": "7 m/s.", "correct": true}, + {"index": "C", "libelle": "8 m/s.", "correct": false}, + {"index": "D", "libelle": "6 m/s.", "correct": false} + ] + }, + { + "num": 151, + "libelle": "Un parachutiste titulaire uniquement du BPA et totalisant 300 sauts peut-il sauter avec une voile de la surface de son choix ?", + "answers": [ + {"index": "A", "libelle": "Non.", "correct": true}, + {"index": "B", "libelle": "Oui.", "correct": false} + ] + }, + { + "num": 152, + "libelle": "Un parachutiste titulaire uniquement du BPA et totalisant 700 sauts peut-il sauter avec une voile de la surface de son choix ?", + "answers": [ + {"index": "A", "libelle": "Non.", "correct": true}, + {"index": "B", "libelle": "Oui.", "correct": false} + ] + }, + { + "num": 153, + "libelle": "Peut-on poursuivre une séance de sauts si le vent au sol est constant à 10 m/s avec des rafales à 13 m/s ?", + "answers": [ + {"index": "A", "libelle": "Uniquement pour les sauts en tandem.", "correct": false}, + {"index": "B", "libelle": "Non.", "correct": true}, + {"index": "C", "libelle": "Oui.", "correct": false}, + {"index": "D", "libelle": "Uniquement pour les parachutistes titulaires du BPA.", "correct": false} + ] + }, + { + "num": 154, + "libelle": "Quel est l’encadrement minimum nécessaire au sol lors d’une séance de pratique autonome ?", + "answers": [ + {"index": "A", "libelle": "Un parachutiste titulaire du Brevet C.", "correct": true}, + {"index": "B", "libelle": "Un moniteur titulaire au minimum du monitorat fédéral.", "correct": false}, + {"index": "C", "libelle": "Un parachutiste titulaire du Brevet BPA.", "correct": false}, + {"index": "D", "libelle": "Un parachutiste titulaire du Brevet D.", "correct": false} + ] + }, + { + "num": 155, + "libelle": "Une assurance responsabilité civile est-elle obligatoire pour les parachutistes qui pratiquent dans les structures de la FFP ?", + "answers": [ + {"index": "A", "libelle": "Uniquement pour les moniteurs.", "correct": false}, + {"index": "B", "libelle": "Oui.", "correct": true}, + {"index": "C", "libelle": "Uniquement pour les compétiteurs.", "correct": false}, + {"index": "D", "libelle": "Non.", "correct": false} + ] + }, + { + "num": 156, + "libelle": "La hauteur minimale d’ouverture imposé par la réglementation fédérale pour les parachutistes titulaire du BPA est de :", + "answers": [ + {"index": "A", "libelle": "850 m.", "correct": true}, + {"index": "B", "libelle": "1200 m.", "correct": false}, + {"index": "C", "libelle": "650 m.", "correct": false}, + {"index": "D", "libelle": "1000 m.", "correct": false} + ] + }, + { + "num": 157, + "libelle": "Quel est le document attestant l’affiliation à la FFP ?", + "answers": [ + {"index": "A", "libelle": "L’assurance responsabilité civile.", "correct": false}, + {"index": "B", "libelle": "Le livret de progression et un des brevets fédéraux.", "correct": false}, + {"index": "C", "libelle": "La licence assurance de l’année en cours", "correct": true} + ] + }, + { + "num": 158, + "libelle": "Les documents de saut qu’un parachutiste doit présenter pour sauter dans les structures fédérales sont :", + "answers": [ + {"index": "A", "libelle": "Le livret de parachute uniquement.", "correct": false}, + {"index": "B", "libelle": "La licence assurance uniquement.", "correct": false}, + {"index": "C", "libelle": "Le carnet de sauts uniquement.", "correct": false}, + {"index": "D", "libelle": "L’ensemble de ces documents.", "correct": true} + ] + }, + { + "num": 159, + "libelle": "L’assurance en responsabilité civile :", + "answers": [ + {"index": "A", "libelle": "Garantie la réparation des dommages causés aux tiers.", "correct": true}, + {"index": "B", "libelle": "Garantie le remboursement des frais médicaux et chirurgicaux de l’assuré.", "correct": false} + ] + }, + { + "num": 160, + "libelle": "L’assurance individuelle :", + "answers": [ + {"index": "A", "libelle": "Garantie la réparation des dommages causés aux tiers.", "correct": false}, + {"index": "B", "libelle": "Garantie le remboursement des frais médicaux et chirurgicaux de l’assuré.", "correct": true} + ] + }, + { + "num": 161, + "libelle": "Le titulaire du BPA peut effectuer tous les sauts spéciaux.", + "answers": [ + {"index": "A", "libelle": "Uniquement les sauts de nuit et les sauts à haute altitude avec l’accord du directeur technique.", "correct": false}, + {"index": "B", "libelle": "Faux.", "correct": true}, + {"index": "C", "libelle": "Vrai.", "correct": false} + ] + }, + { + "num": 162, + "libelle": "Le titulaire du brevet B peut-il effectuer des sauts spéciaux ?", + "answers": [ + {"index": "A", "libelle": "Oui.", "correct": false}, + {"index": "B", "libelle": "Non.", "correct": true}, + {"index": "C", "libelle": "Uniquement les sauts de nuit et les sauts à haute altitude avec l’accord du directeur technique.", "correct": false} + ] + }, + { + "num": 163, + "libelle": "Le titulaire du BPA peut-il effectuer des sauts de démonstration hors école de parachutisme ?", + "answers": [ + {"index": "A", "libelle": "Oui.", "correct": false}, + {"index": "B", "libelle": "Non.", "correct": true}, + {"index": "C", "libelle": "Oui si les sauts ne se déroulent pas lors d’une manifestation aérienne.", "correct": false} + ] + } + ] + }, + { + "num": 6, + "name": "Pilotage et mécanique de vol", + "questions": [ + { + "num": 164, + "libelle": "Plus je fais un mouvement brusque et ample pour tourner :", + "answers": [ + {"index": "A", "libelle": "L’inclinaison de la voile est indépendante de la manœuvre de mise en virage.", "correct": false}, + {"index": "B", "libelle": "Moins l’inclinaison de voile augmente.", "correct": false}, + {"index": "C", "libelle": "Plus l’inclinaison de la voile augmente.", "correct": true} + ] + }, + { + "num": 165, + "libelle": "Plus la vitesse initiale est élevée avant le virage :", + "answers": [ + {"index": "A", "libelle": "Moins le rayon de virage augmente.", "correct": false}, + {"index": "B", "libelle": "Plus le rayon de virage augmente.", "correct": true}, + {"index": "C", "libelle": "Le rayon de virage est indépendant de la vitesse initiale.", "correct": false} + ] + }, + { + "num": 166, + "libelle": "Plus la voile s’incline en virage :", + "answers": [ + {"index": "A", "libelle": "Plus la vitesse verticale diminue.", "correct": false}, + {"index": "B", "libelle": "Plus la vitesse verticale augmente.", "correct": true}, + {"index": "C", "libelle": "La vitesse verticale est indépendante de l’inclinaison de la voile.", "correct": false} + ] + }, + { + "num": 167, + "libelle": "Pour faire un virage ‘’à plat’’ :", + "answers": [ + {"index": "A", "libelle": "Il faut manœuvrer très rapidement à pleine vitesse.", "correct": false}, + {"index": "B", "libelle": "Il faut manœuvrer doucement et en ½ frein.", "correct": true} + ] + }, + { + "num": 168, + "libelle": "Qu’appelle-t-on vitesse sur trajectoire d’une aile ?", + "answers": [ + {"index": "A", "libelle": "La vitesse qui résulte de la somme vectoriel de la vitesse horizontale et de la vitesse verticale.", "correct": true}, + {"index": "B", "libelle": "La vitesse verticale.", "correct": false}, + {"index": "C", "libelle": "La vitesse horizontale.", "correct": false} + ] + }, + { + "num": 169, + "libelle": "La vitesse propre d’une aile, dans la masse d’air :", + "answers": [ + {"index": "A", "libelle": "Est plus grande vent arrière que vent de face.", "correct": false}, + {"index": "B", "libelle": "Est plus grande vent de face que vent arrière.", "correct": false}, + {"index": "C", "libelle": "Est indépendante du vent.", "correct": true} + ] + }, + { + "num": 170, + "libelle": "La vitesse sol d’une aile :", + "answers": [ + {"index": "A", "libelle": "Est plus grande vent arrière que vent de face.", "correct": true}, + {"index": "B", "libelle": "Est plus grande vent de face que vent arrière.", "correct": false}, + {"index": "C", "libelle": "Est indépendante du vent.", "correct": false} + ] + }, + { + "num": 171, + "libelle": "En faisant un virage rapide :", + "answers": [ + {"index": "A", "libelle": "La vitesse verticale diminue.", "correct": false}, + {"index": "B", "libelle": "La vitesse verticale augmente beaucoup.", "correct": true}, + {"index": "C", "libelle": "La vitesse verticale augmente peu.", "correct": false} + ] + }, + { + "num": 172, + "libelle": "Pour augmenter la pénétration dans l’air par vent fort :", + "answers": [ + {"index": "A", "libelle": "Il faut faire une légère traction sur les élévateurs avants.", "correct": true}, + {"index": "B", "libelle": "Il faut faire une légère traction sur les élévateurs arrières.", "correct": false}, + {"index": "C", "libelle": "On ne peut pas améliorer la pénétration par vent fort.", "correct": false} + ] + }, + { + "num": 173, + "libelle": "Avec un vent de face égal à la vitesse horizontale de la voile, si on fait une traction sur les élévateurs avants :", + "answers": [ + {"index": "A", "libelle": "La finesse sol augmente.", "correct": true}, + {"index": "B", "libelle": "La finesse sol diminue.", "correct": false}, + {"index": "C", "libelle": "La finesse sol reste la même.", "correct": false} + ] + }, + { + "num": 174, + "libelle": "Par vent nul, si l’on tire sur les élévateurs avant :", + "answers": [ + {"index": "A", "libelle": "La vitesse diminue et la finesse augmente.", "correct": false}, + {"index": "B", "libelle": "La vitesse augmente et la finesse diminue.", "correct": true}, + {"index": "C", "libelle": "La vitesse et la finesse diminuent.", "correct": false}, + {"index": "D", "libelle": "La vitesse et la finesse augmentent.", "correct": false} + ] + }, + { + "num": 175, + "libelle": "Tirer sur les élévateurs avant peut être utile :", + "answers": [ + {"index": "A", "libelle": "Pour revenir sur le terrain par vent nul ou vent arrière, si l’on est loin.", "correct": false}, + {"index": "B", "libelle": "Pour contrer un fort vent de face.", "correct": true}, + {"index": "C", "libelle": "En aucune circonstance.", "correct": false} + ] + }, + { + "num": 176, + "libelle": "Une traction sur un élévateur avant provoque.", + "answers": [ + {"index": "A", "libelle": "Un virage lent à petit rayon et enfoncement important.", "correct": false}, + {"index": "B", "libelle": "Un virage lent à grand rayon et faible enfoncement.", "correct": false}, + {"index": "C", "libelle": "Un virage rapide à petit rayon et enfoncement important.", "correct": true}, + {"index": "D", "libelle": "Un virage rapide à grand rayon et faible enfoncement.", "correct": false} + ] + }, + { + "num": 177, + "libelle": "Par vent nul, si l’on tire modérément sur les élévateurs arrière :", + "answers": [ + {"index": "A", "libelle": "La vitesse augmente et la finesse diminue.", "correct": false}, + {"index": "B", "libelle": "La vitesse et la finesse diminuent.", "correct": false}, + {"index": "C", "libelle": "La vitesse diminue et la finesse augmente.", "correct": true}, + {"index": "D", "libelle": "La vitesse et la finesse augmentent.", "correct": false} + ] + }, + { + "num": 178, + "libelle": "Tirer sur les élévateurs arrière peut être utile :", + "answers": [ + {"index": "A", "libelle": "Pour revenir sur le terrain par vent nul ou vent arrière, si l’on est loin.", "correct": true}, + {"index": "B", "libelle": "Pour contrer un fort vent de face.", "correct": false}, + {"index": "C", "libelle": "En aucune circonstance.", "correct": false} + ] + }, + { + "num": 179, + "libelle": "Une traction modérée sur un élévateur arrière provoque :", + "answers": [ + {"index": "A", "libelle": "Un virage rapide à grand rayon et faible enfoncement.", "correct": false}, + {"index": "B", "libelle": "Un virage lent à grand rayon et faible enfoncement.", "correct": true}, + {"index": "C", "libelle": "Un virage rapide à petit rayon et enfoncement important.", "correct": false}, + {"index": "D", "libelle": "Un virage lent à petit rayon et enfoncement important.", "correct": false} + ] + }, + { + "num": 180, + "libelle": "Pour piloter avec les élévateurs :", + "answers": [ + {"index": "A", "libelle": "L’effort à fournir est le même que pour piloter avec les commandes.", "correct": false}, + {"index": "B", "libelle": "L’effort à fournir est plus faible que pour piloter avec les commandes.", "correct": false}, + {"index": "C", "libelle": "L’effort à fournir est plus important que pour piloter avec les commandes.", "correct": true} + ] + }, + { + "num": 181, + "libelle": "Quand on pilote avec les élévateurs :", + "answers": [ + {"index": "A", "libelle": "Il est plus bas.", "correct": false}, + {"index": "B", "libelle": "Il est plus haut.", "correct": true}, + {"index": "C", "libelle": "Le point de décrochage est au même niveau que quand on pilote avec les commandes.", "correct": false} + ] + }, + { + "num": 182, + "libelle": "Qu’appelle-t-on finesse ?", + "answers": [ + {"index": "A", "libelle": "Le rapport entre la surface et la masse du parachutiste équipé.", "correct": false}, + {"index": "B", "libelle": "L’épaisseur du profil d’une aile.", "correct": false}, + {"index": "C", "libelle": "Le rapport entre la vitesse horizontale et la vitesse verticale de la voilure.", "correct": true} + ] + }, + { + "num": 183, + "libelle": "On appelle finesse air :", + "answers": [ + {"index": "A", "libelle": "Le rapport vitesse sur trajectoire / vitesse verticale.", "correct": false}, + {"index": "B", "libelle": "Le rapport vitesse verticale / vitesse horizontale.", "correct": false}, + {"index": "C", "libelle": "Le rapport vitesse horizontale / vitesse verticale.", "correct": true} + ] + }, + { + "num": 184, + "libelle": "Dans la masse d’air, la finesse air est égale :", + "answers": [ + {"index": "A", "libelle": "Au rapport perte de hauteur / distance parcourue.", "correct": false}, + {"index": "B", "libelle": "Au rapport distance parcourue / perte de hauteur.", "correct": true} + ] + }, + { + "num": 185, + "libelle": "La finesse sol est égale à :", + "answers": [ + {"index": "A", "libelle": "Vitesse sur trajectoire par rapport au sol / vitesse verticale par rapport au sol.", "correct": false}, + {"index": "B", "libelle": "Vitesse sur trajectoire dans la masse d’air / vitesse verticale dans la masse d’air.", "correct": false}, + {"index": "C", "libelle": "Vitesse horizontale par rapport au sol / vitesse verticale par rapport au sol.", "correct": true}, + {"index": "D", "libelle": "Vitesse horizontale dans la masse d’air / vitesse verticale dans la masse d’air.", "correct": false} + ] + }, + { + "num": 186, + "libelle": "La finesse sol :", + "answers": [ + {"index": "A", "libelle": "Augmente avec un vent de face.", "correct": false}, + {"index": "B", "libelle": "Diminue avec un vent de face.", "correct": true}, + {"index": "C", "libelle": "Reste constante quelque soit le vent.", "correct": false} + ] + }, + { + "num": 187, + "libelle": "Par vent nul, avec une perte de hauteur de 1000 mètres et une finesse de 2,5 quelle est la distance parcourue?", + "answers": [ + {"index": "A", "libelle": "500 m", "correct": false}, + {"index": "B", "libelle": "2500 m", "correct": true}, + {"index": "C", "libelle": "1500 m", "correct": false} + ] + }, + { + "num": 188, + "libelle": "Comment se nomme la force aérodynamique qui est parallèle à la trajectoire ?", + "answers": [ + {"index": "A", "libelle": "La traînée.", "correct": true}, + {"index": "B", "libelle": "La résultante aérodynamique.", "correct": false}, + {"index": "C", "libelle": "La portance.", "correct": false} + ] + }, + { + "num": 189, + "libelle": "Comment se nomme la force aérodynamique qui est perpendiculaire à la trajectoire ?", + "answers": [ + {"index": "A", "libelle": "La portance.", "correct": true}, + {"index": "B", "libelle": "La traînée.", "correct": false}, + {"index": "C", "libelle": "La résultante aérodynamique.", "correct": false} + ] + }, + { + "num": 190, + "libelle": "En vol stable, quelle est la force qui équilibre le poids ?", + "answers": [ + {"index": "A", "libelle": "La portance.", "correct": false}, + {"index": "B", "libelle": "La traînée.", "correct": false}, + {"index": "C", "libelle": "La résultante aérodynamique.", "correct": true} + ] + }, + { + "num": 191, + "libelle": "Qu‘appelle-t-on angle d’incidence ?", + "answers": [ + {"index": "A", "libelle": "L’angle entre l’aile et l’axe du cône de suspension.", "correct": false}, + {"index": "B", "libelle": "L’angle entre la trajectoire et l’horizontale.", "correct": false}, + {"index": "C", "libelle": "L’angle entre la trajectoire et la corde de profil de l’aile.", "correct": true} + ] + }, + { + "num": 192, + "libelle": "La charge alaire :", + "answers": [ + {"index": "A", "libelle": "Est la masse du parachutiste équipé.", "correct": false}, + {"index": "B", "libelle": "Est la masse du parachute.", "correct": false}, + {"index": "C", "libelle": "Est la masse de la voilure.", "correct": false}, + {"index": "D", "libelle": "Est le rapport entre la masse du parachutiste équipé et la surface de voile.", "correct": true} + ] + }, + { + "num": 193, + "libelle": "On appelle charge alaire :", + "answers": [ + {"index": "A", "libelle": "Le rapport surface de voile / masse du parachutiste équipé.", "correct": false}, + {"index": "B", "libelle": "Le rapport masse du parachutiste équipé / surface de voile.", "correct": true}, + {"index": "C", "libelle": "La résistance du tissu de voile.", "correct": false} + ] + }, + { + "num": 194, + "libelle": "La charge alaire :", + "answers": [ + {"index": "A", "libelle": "Est un paramètre important.", "correct": true}, + {"index": "B", "libelle": "Est un paramètre négligeable.", "correct": false} + ] + }, + { + "num": 195, + "libelle": "Une finesse de 2.5 signifie :", + "answers": [ + {"index": "A", "libelle": "Que pour une perte de hauteur de 1000 m. La distance horizontale parcourue sera de 2500 m.", "correct": true}, + {"index": "B", "libelle": "Que pour une perte de hauteur de 2500 m. La distance horizontale parcourue sera de 1000 m.", "correct": false} + ] + }, + { + "num": 196, + "libelle": "Lors d’un virage, la vitesse verticale :", + "answers": [ + {"index": "A", "libelle": "Diminue.", "correct": false}, + {"index": "B", "libelle": "Augmente.", "correct": true}, + {"index": "C", "libelle": "Ne change pas.", "correct": false} + ] + }, + { + "num": 197, + "libelle": "Lors d’un décrochage, la vitesse verticale d’une voilure de type aile :", + "answers": [ + {"index": "A", "libelle": "Ne change pas.", "correct": false}, + {"index": "B", "libelle": "Augmente un petit peu.", "correct": false}, + {"index": "C", "libelle": "Augmente beaucoup.", "correct": true} + ] + }, + { + "num": 198, + "libelle": "Le décrochage :", + "answers": [ + {"index": "A", "libelle": "Survient uniquement suite à une action sur les commandes de manœuvre.", "correct": false}, + {"index": "B", "libelle": "Peut survenir quand on passe dans une zone de fortes turbulences.", "correct": true} + ] + }, + { + "num": 199, + "libelle": "Une voilure de type aile peut décrocher même si l’on n’a pas atteint les 100% de frein.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 200, + "libelle": "Dans un écoulement laminaire, le point de décrochage :", + "answers": [ + {"index": "A", "libelle": "Varie en fonction de l’orientation et de la vitesse du vent.", "correct": false}, + {"index": "B", "libelle": "Est indépendant du vent.", "correct": true} + ] + }, + { + "num": 201, + "libelle": "Un décrochage dynamique :", + "answers": [ + {"index": "A", "libelle": "Peut survenir lors d’un freinage brusque.", "correct": true}, + {"index": "B", "libelle": "N’est possible qu’en condition de vent laminaire.", "correct": false}, + {"index": "C", "libelle": "N’est possible qu’en conditions turbulentes.", "correct": false} + ] + }, + { + "num": 202, + "libelle": "Si on augmente la masse sous un parachute, les vitesses verticales et horizontales :", + "answers": [ + {"index": "A", "libelle": "Diminuent.", "correct": false}, + {"index": "B", "libelle": "Augmentent.", "correct": true}, + {"index": "C", "libelle": "Ne changent pas.", "correct": false} + ] + }, + { + "num": 203, + "libelle": "Avec une voilure donnée, la vitesse sur trajectoire :", + "answers": [ + {"index": "A", "libelle": "C’est uniquement la vitesse verticale qui augmente.", "correct": false}, + {"index": "B", "libelle": "Ne varie pas quand la masse du parachutiste augmente.", "correct": false}, + {"index": "C", "libelle": "Augmente quand la masse du parachutiste augmente.", "correct": true} + ] + }, + { + "num": 204, + "libelle": "L’avant d’une voilure de type aile s’appelle :", + "answers": [ + {"index": "A", "libelle": "Le bord d’attaque.", "correct": true}, + {"index": "B", "libelle": "Le bord de fuite.", "correct": false}, + {"index": "C", "libelle": "Le saumon.", "correct": false} + ] + }, + { + "num": 205, + "libelle": "L’arrière d’une voilure de type aile s’appelle :", + "answers": [ + {"index": "A", "libelle": "L’intrados.", "correct": false}, + {"index": "B", "libelle": "Le bord d’attaque.", "correct": false}, + {"index": "C", "libelle": "Le bord de fuite.", "correct": true} + ] + }, + { + "num": 206, + "libelle": "Lors d’un décrochage, la vitesse horizontale :", + "answers": [ + {"index": "A", "libelle": "Augmente.", "correct": false}, + {"index": "B", "libelle": "Ne change pas.", "correct": false}, + {"index": "C", "libelle": "Diminue.", "correct": true} + ] + }, + { + "num": 207, + "libelle": "Si votre voilure décroche lors d’un freinage excessif :", + "answers": [ + {"index": "A", "libelle": "Il faut relâcher doucement les commandes de manœuvre.", "correct": true}, + {"index": "B", "libelle": "Il faut attendre que la voilure se remette en pression toute seule.", "correct": false}, + {"index": "C", "libelle": "Il faut agir sur les commandes par tractions répétées.", "correct": false} + ] + }, + { + "num": 208, + "libelle": "Le point de décrochage :", + "answers": [ + {"index": "A", "libelle": "Dépend des caractéristiques de l’aile et du réglage des commandes de manœuvre.", "correct": true}, + {"index": "B", "libelle": "Ne dépend que des caractéristiques de l’aile.", "correct": false} + ] + }, + { + "num": 209, + "libelle": "Le point de décrochage :", + "answers": [ + {"index": "A", "libelle": "Est toujours au même niveau.", "correct": false}, + {"index": "B", "libelle": "Peut varier en fonction des conditions aérologiques et des manœuvres effectuées.", "correct": true} + ] + }, + { + "num": 210, + "libelle": "Le décrochage survient :", + "answers": [ + {"index": "A", "libelle": "Eventuellement avant le point de décrochage dans une zone de turbulences.", "correct": true}, + {"index": "B", "libelle": "Uniquement quand on freine au-delà du point de décrochage.", "correct": false} + ] + }, + { + "num": 211, + "libelle": "En cas de décrochage :", + "answers": [ + {"index": "A", "libelle": "Il faut maintenir la voilure en freins.", "correct": false}, + {"index": "B", "libelle": "Il faut relâcher progressivement les commandes de manœuvre.", "correct": true}, + {"index": "C", "libelle": "Il faut relâcher le plus vite possible les commandes de manœuvre.", "correct": false} + ] + }, + { + "num": 212, + "libelle": "Lors d’un freinage, si on relâche les commandes brusquement :", + "answers": [ + {"index": "A", "libelle": "La voile reprend progressivement sa trajectoire initiale de vol.", "correct": false}, + {"index": "B", "libelle": "La voile subit un balancement et une accélération.", "correct": true} + ] + }, + { + "num": 213, + "libelle": "En cas de décrochage près du sol :", + "answers": [ + {"index": "A", "libelle": "Il faut relâcher un tout petit peu les commandes de manœuvre, très doucement.", "correct": true}, + {"index": "B", "libelle": "Il faut relâcher le plus vite possible les commandes de manœuvre.", "correct": false}, + {"index": "C", "libelle": "Il faut maintenir les commandes enfoncées.", "correct": false} + ] + }, + { + "num": 214, + "libelle": "En décrochage :", + "answers": [ + {"index": "A", "libelle": "Il est toujours possible de contrôler la voile avec les commandes de manœuvre.", "correct": false}, + {"index": "B", "libelle": "L’aile n’est plus pilotable normalement.", "correct": true} + ] + }, + { + "num": 215, + "libelle": "Après un décrochage, il faut plusieurs secondes pour que la voilure reprenne sa ligne de vol.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 216, + "libelle": "Après un décrochage, la perte de hauteur avant le retour à un vol normal peut être supérieur à:", + "answers": [ + {"index": "A", "libelle": "Pas de perte de hauteur.", "correct": false}, + {"index": "B", "libelle": "20 m.", "correct": false}, + {"index": "C", "libelle": "100 m.", "correct": true} + ] + }, + { + "num": 217, + "libelle": "La vitesse de descente d’une voilure pour une masse et un pourcentage de frein donné :", + "answers": [ + {"index": "A", "libelle": "Dépend du gradient turbulo-laminaire de vent", "correct": false}, + {"index": "B", "libelle": "Est supérieure dans le vent.", "correct": false}, + {"index": "C", "libelle": "Est identique quel que soit le vent.", "correct": true}, + {"index": "D", "libelle": "Est supérieure face au vent.", "correct": false} + ] + }, + { + "num": 218, + "libelle": "Si il y a du vent, une voilure de type aile se met naturellement dans le vent.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": false}, + {"index": "B", "libelle": "Faux.", "correct": true}, + {"index": "C", "libelle": "Uniquement si le vent est fort.", "correct": false} + ] + }, + { + "num": 219, + "libelle": "Si il y a du vent, une voilure de type aile se met naturellement contre le vent.", + "answers": [ + {"index": "A", "libelle": "Uniquement si le vent est fort.", "correct": false}, + {"index": "B", "libelle": "Faux.", "correct": true}, + {"index": "C", "libelle": "Vrai.", "correct": false} + ] + } + ] + }, + { + "num": 7, + "name": "Physiologie", + "questions": [ + { + "num": 220, + "libelle": "Avec l’altitude, les problèmes lié à l’hypoxie sont dus à :", + "answers": [ + {"index": "A", "libelle": "Une augmentation de la pression partielle d’oxygène.", "correct": false}, + {"index": "B", "libelle": "Une diminution de la pression partielle d’oxygène.", "correct": true} + ] + }, + { + "num": 221, + "libelle": "Est-il important d’être en bonne condition physique pour sauter en parachute ?", + "answers": [ + {"index": "A", "libelle": "Uniquement pour les compétiteurs.", "correct": false}, + {"index": "B", "libelle": "Oui car elle atténue les conséquences de microtraumatismes répétés.", "correct": true}, + {"index": "C", "libelle": "Non car le saut ne requiert pas beaucoup d’efforts.", "correct": false} + ] + }, + { + "num": 222, + "libelle": "Avant de débuter une journée de sauts :", + "answers": [ + {"index": "A", "libelle": "Il faut s’alimenter.", "correct": true}, + {"index": "B", "libelle": "Mieux vaut ne rien manger.", "correct": false} + ] + }, + { + "num": 223, + "libelle": "L’absorption d’alcool pendant une journée de saut :", + "answers": [ + {"index": "A", "libelle": "Est sans importance tant que l’on reste dans des limites raisonnables.", "correct": false}, + {"index": "B", "libelle": "Est à proscrire absolument.", "correct": true}, + {"index": "C", "libelle": "Donne un peu de courage.", "correct": false} + ] + }, + { + "num": 224, + "libelle": "Comment appelle t’ont le phénomène physiologique dû à une insuffisance en oxygène ?", + "answers": [ + {"index": "A", "libelle": "L’hypoxie.", "correct": true}, + {"index": "B", "libelle": "L’hypoglycémie.", "correct": false}, + {"index": "C", "libelle": "L’hyperventilation.", "correct": false} + ] + }, + { + "num": 225, + "libelle": "Quelle est la partie du corps qui, souffrant du manque d’oxygène, est la plus problématique pour le parachutisme ?", + "answers": [ + {"index": "A", "libelle": "Le cerveau.", "correct": true}, + {"index": "B", "libelle": "L’estomac.", "correct": false}, + {"index": "C", "libelle": "Les poumons.", "correct": false}, + {"index": "D", "libelle": "Les oreilles.", "correct": false} + ] + }, + { + "num": 226, + "libelle": "A 6000 mètres, ne pas avoir d’oxygène à bord présente un risque de troubles physiologiques importants pouvant aller jusqu’à la perte de connaissance.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + }, + { + "num": 227, + "libelle": "En cas d’hypoxie, le froid est un facteur aggravant vis à vis des problèmes rencontrés.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux, les deux sont complètement indépendants.", "correct": false} + ] + }, + { + "num": 228, + "libelle": "Les problèmes d’hypoxie peuvent apparaître à partir de 3500 m.", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": true}, + {"index": "B", "libelle": "Faux.", "correct": false} + ] + }, + { + "num": 229, + "libelle": "Les problèmes liés à l’hypoxie sont dépendant du temps passé en altitude.", + "answers": [ + {"index": "A", "libelle": "Faux.", "correct": false}, + {"index": "B", "libelle": "Vrai.", "correct": true} + ] + } + ] + }, + { + "num": 8, + "name": "Connaissances générales", + "questions": [ + { + "num": 230, + "libelle": "Pour embarquer dans un hélicoptère léger :", + "answers": [ + {"index": "A", "libelle": "C’est sans importance.", "correct": false}, + {"index": "B", "libelle": "Il faut aborder l’appareil par l’arrière.", "correct": false}, + {"index": "C", "libelle": "Il faut aborder l’appareil par l’avant.", "correct": true} + ] + }, + { + "num": 231, + "libelle": "En montant dans l’avion :", + "answers": [ + {"index": "A", "libelle": "On risque d’accrocher quelque chose sur son équipement si l’on ne fait pas attention.", "correct": true}, + {"index": "B", "libelle": "Ce risque est négligeable.", "correct": false} + ] + }, + { + "num": 232, + "libelle": "Quand l’avion prend l’axe de largage :", + "answers": [ + {"index": "A", "libelle": "Il est inutile de contrôler son équipement.", "correct": false}, + {"index": "B", "libelle": "Il faut contrôler quelques points essentiels de son équipement avant de sauter.", "correct": true} + ] + }, + { + "num": 233, + "libelle": "En sortie d’avion", + "answers": [ + {"index": "A", "libelle": "Il ne faut plus s’occuper de problèmes de matériel.", "correct": false}, + {"index": "B", "libelle": "Il faut veiller à ne rien accrocher sur son équipement.", "correct": true} + ] + }, + { + "num": 234, + "libelle": "Pour sauter la première fois d’un avion gros porteur :", + "answers": [ + {"index": "A", "libelle": "Je m’informe des consignes particulières.", "correct": true}, + {"index": "B", "libelle": "Inutile, je suivrai les autres.", "correct": false} + ] + }, + { + "num": 235, + "libelle": "En chute, la vitesse moyenne à plat face au sol :", + "answers": [ + {"index": "A", "libelle": "Est proche de 150 km/h.", "correct": false}, + {"index": "B", "libelle": "Est proche de 200 km/h.", "correct": true}, + {"index": "C", "libelle": "Est proche de 250 km/h.", "correct": false} + ] + }, + { + "num": 236, + "libelle": "La perte de hauteur en chute libre à plat face sol est de :", + "answers": [ + {"index": "A", "libelle": "Approximativement 300 m pendant les 10 premières secondes puis 50 m/s.", "correct": true}, + {"index": "B", "libelle": "Approximativement 50 m/s pendant toute la chute.", "correct": false}, + {"index": "C", "libelle": "Approximativement 500 m pendant les 10 premières secondes puis 50 m/s.", "correct": false} + ] + }, + { + "num": 237, + "libelle": "La vitesse verticale de chute est en moyenne de :", + "answers": [ + {"index": "A", "libelle": "50 m/s et ne dépasse jamais 60 m/s.", "correct": false}, + {"index": "B", "libelle": "50 m/s, elle peut dépasser 70 m/s en piqué ou tête en bas.", "correct": true} + ] + }, + { + "num": 238, + "libelle": "Quand on chute à plat face au sol, après 15 secondes de chute, on a perdu :", + "answers": [ + {"index": "A", "libelle": "700 m de hauteur.", "correct": false}, + {"index": "B", "libelle": "550 m de hauteur.", "correct": true}, + {"index": "C", "libelle": "400 m de hauteur.", "correct": false} + ] + }, + { + "num": 239, + "libelle": "La vitesse de chute tête en bas ou debout :", + "answers": [ + {"index": "A", "libelle": "Peut atteindre 300 km/h.", "correct": true}, + {"index": "B", "libelle": "Est sensiblement la même qu’à plat face sol.", "correct": false}, + {"index": "C", "libelle": "Ne dépasse pas 250 km/h.", "correct": false} + ] + }, + { + "num": 240, + "libelle": "Lors d’un saut de groupe :", + "answers": [ + {"index": "A", "libelle": "Il faut toujours assurer la sécurité en chute pour éviter tout risque de collisions.", "correct": true}, + {"index": "B", "libelle": "Les risques de collision sont faibles car tout le monde chute à la même vitesse.", "correct": false} + ] + }, + { + "num": 241, + "libelle": "Lors d’un saut de groupe :", + "answers": [ + {"index": "A", "libelle": "Avant d’ouvrir, il suffit de dériver longtemps pour éviter tout risque de collision.", "correct": false}, + {"index": "B", "libelle": "Il faut dériver en contrôlant sa trajectoire et garder si possible le contact visuel sur les autres parachutistes pour s’assurer que la séparation est suffisante.", "correct": true} + ] + }, + { + "num": 242, + "libelle": "Si vous n’avez pas sauté depuis plusieurs mois, vous programmez :", + "answers": [ + {"index": "A", "libelle": "Un saut de reprise, sans exercice particulier, avec un matériel que vous connaissez et en adaptant la hauteur d’ouverture.", "correct": true}, + {"index": "B", "libelle": "Un saut de VR ou de free fly afin de ne pas être seul.", "correct": false}, + {"index": "C", "libelle": "N’importe quel type de saut.", "correct": false} + ] + }, + { + "num": 243, + "libelle": "En faisant des virages rapides et enchaînés en dessous de 500 mètres :", + "answers": [ + {"index": "A", "libelle": "Il n’y a pas de risques particuliers.", "correct": false}, + {"index": "B", "libelle": "On risque de faire fonctionner le déclencheur de sécurité ou d’entrer en collision avec d’autres parachutistes.", "correct": true} + ] + }, + { + "num": 244, + "libelle": "L’atterrissage dans une pente prononcée, par temps calme sur un grand terrain ou rien n’indique le vent se fait de préférence :", + "answers": [ + {"index": "A", "libelle": "En travers de la pente.", "correct": true}, + {"index": "B", "libelle": "Face à la pente.", "correct": false}, + {"index": "C", "libelle": "Dans le sens de la pente.", "correct": false} + ] + }, + { + "num": 245, + "libelle": "En finale, hors zone, vous vous rapprochez dangereusement d’une ligne électrique face à vous.", + "answers": [ + {"index": "A", "libelle": "Vous conservez votre trajectoire en freinant au maximum et en espérant que ça va passer.", "correct": false}, + {"index": "B", "libelle": "Il faut l’éviter à tout prix, vous changez de trajectoire.", "correct": true} + ] + }, + { + "num": 246, + "libelle": "Lors d’un atterrissage hors zone, l’objectif prioritaire est de :", + "answers": [ + {"index": "A", "libelle": "Se poser face au vent.", "correct": false}, + {"index": "B", "libelle": "Se poser hors obstacles avec une trajectoire dégagée.", "correct": true} + ] + }, + { + "num": 247, + "libelle": "Parachute ouvert, vous constatez que vous ne pourrez pas rejoindre la zone de sauts. Il faut :", + "answers": [ + {"index": "A", "libelle": "Ne pas survoler d’obstacles, surtout à basse hauteur.", "correct": true}, + {"index": "B", "libelle": "Se rapprocher au maximum au plus près de la zone, quel que soit le terrain survolé.", "correct": false} + ] + }, + { + "num": 248, + "libelle": "Parachute ouvert, vous constatez que vous ne pourrez pas rejoindre la zone d’atterrissage prévue. Il faut :", + "answers": [ + {"index": "A", "libelle": "Trouver rapidement une zone de dégagement facile à atteindre même si elle est très éloignée de la zone d’atterrissage prévue.", "correct": true}, + {"index": "B", "libelle": "Se rapprocher au maximum au plus près de la zone d’atterrissage prévue.", "correct": false} + ] + }, + { + "num": 249, + "libelle": "Parachute ouvert, vous constatez que vous ne pourrez pas rejoindre la zone de saut. Il faut :", + "answers": [ + {"index": "A", "libelle": "Attendre d’être près du sol pour bien voir les obstacles.", "correct": false}, + {"index": "B", "libelle": "Repérer dès que possible les obstacles et les zones dégagées.", "correct": true} + ] + }, + { + "num": 250, + "libelle": "Piste 27 en service signifie :", + "answers": [ + {"index": "A", "libelle": "Que l’avion va décoller face au sud.", "correct": false}, + {"index": "B", "libelle": "Que l’avion va décoller face à l’est.", "correct": false}, + {"index": "C", "libelle": "Que l’avion va décoller face au nord.", "correct": false}, + {"index": "D", "libelle": "Que l’avion va décoller face à l’ouest.", "correct": true} + ] + }, + { + "num": 251, + "libelle": "Piste 09 en service signifie :", + "answers": [ + {"index": "A", "libelle": "Que l’avion va décoller face à l’ouest.", "correct": false}, + {"index": "B", "libelle": "Que l’avion va décoller face à l’est.", "correct": true}, + {"index": "C", "libelle": "Que l’avion va décoller face au nord.", "correct": false}, + {"index": "D", "libelle": "Que l’avion va décoller face au sud.", "correct": false} + ] + }, + { + "num": 252, + "libelle": "Piste 18 en service signifie :", + "answers": [ + {"index": "A", "libelle": "Que l’avion va décoller face au nord.", "correct": false}, + {"index": "B", "libelle": "Que l’avion va décoller face à l’est.", "correct": false}, + {"index": "C", "libelle": "Que l’avion va décoller face au sud.", "correct": true}, + {"index": "D", "libelle": "Que l’avion va décoller face à l’ouest.", "correct": false} + ] + }, + { + "num": 253, + "libelle": "Piste 36 en service signifie :", + "answers": [ + {"index": "A", "libelle": "Que l’avion va décoller face au nord.", "correct": true}, + {"index": "B", "libelle": "Que l’avion va décoller face à l’est.", "correct": false}, + {"index": "C", "libelle": "Que l’avion va décoller face au sud.", "correct": false}, + {"index": "D", "libelle": "Que l’avion va décoller face à l’ouest.", "correct": false} + ] + }, + { + "num": 254, + "libelle": "En embarquant dans l’avion si vous remarquez quelque chose d’anormal :", + "answers": [ + {"index": "A", "libelle": "Il faut le signaler immédiatement au pilote.", "correct": true}, + {"index": "B", "libelle": "Il ne faut rien lui dire pour ne pas le perturber.", "correct": false} + ] + }, + { + "num": 255, + "libelle": "Quand l’avion prend l’axe de largage :", + "answers": [ + {"index": "A", "libelle": "Il faut contrôler ou faire contrôler son équipement pour éviter une ouverture intempestive en chute ou à la mise en place.", "correct": true}, + {"index": "B", "libelle": "Il est inutile de contrôler ou de faire contrôler son équipement car cela a été fait au sol.", "correct": false} + ] + }, + { + "num": 256, + "libelle": "En sortie d’avion en position flotteur :", + "answers": [ + {"index": "A", "libelle": "Il est possible de se tenir à n’importe quelle partie de la cellule.", "correct": false}, + {"index": "B", "libelle": "Des équipements spécifiques doivent être mis en place pour se tenir.", "correct": true} + ] + }, + { + "num": 257, + "libelle": "Lors d’un largage par vent nul, vous sortez troisième du passage.", + "answers": [ + {"index": "A", "libelle": "Vous laissez environ 8 secondes, vous surveillez le précèdent et vous contrôlez la zone et l’axe sur lesquels vous partez.", "correct": true}, + {"index": "B", "libelle": "Seul le temps de séparation compte.", "correct": false} + ] + }, + { + "num": 258, + "libelle": "Vous partez premier du passage ; le pilote donne l’autorisation de sortie.", + "answers": [ + {"index": "A", "libelle": "Vous ouvrez la porte et vous sautez.", "correct": false}, + {"index": "B", "libelle": "Vous ouvrez la porte, vous contrôlez la zone et l’axe sur lesquels vous allez partir avant de sautez.", "correct": true} + ] + }, + { + "num": 259, + "libelle": "Contrôler ou faire contrôler son équipement avant la sortie d’avion est :", + "answers": [ + {"index": "A", "libelle": "Inutile puisque déjà fait au sol.", "correct": false}, + {"index": "B", "libelle": "Conseillé juste pour les élèves.", "correct": false}, + {"index": "C", "libelle": "Indispensable quel que soit le niveau du parachutiste.", "correct": true} + ] + }, + { + "num": 260, + "libelle": "Avant de s’équiper faut-il effectuer une vérification de son parachute ?", + "answers": [ + {"index": "A", "libelle": "Non c’est inutile car cela sera fait lors de la vérification d’embarquement.", "correct": false}, + {"index": "B", "libelle": "Oui c’est important.", "correct": true} + ] + }, + { + "num": 261, + "libelle": "Que signifient les deux chiffres placés à l’entrée d’une piste ?", + "answers": [ + {"index": "A", "libelle": "L’orientation de la piste", "correct": true}, + {"index": "B", "libelle": "La longueur de la piste.", "correct": false}, + {"index": "C", "libelle": "Le code OACI de l’aérodrome.", "correct": false} + ] + } + ] + }, + { + "num": 9, + "name": "Unités de mesure", + "questions": [ + { + "num": 262, + "libelle": "Pour convertir des pieds/ minute (ft/min) en m/s on utilise la règle suivante :", + "answers": [ + {"index": "A", "libelle": "1000 ft/min = 3 m/s", "correct": false}, + {"index": "B", "libelle": "1000 ft/min = 5 m/s", "correct": true}, + {"index": "C", "libelle": "1000 ft/min = 30 m/s", "correct": false} + ] + }, + { + "num": 263, + "libelle": "Une vitesse verticale de 13 m/s correspond à environ :", + "answers": [ + {"index": "A", "libelle": "7000 ft/min.", "correct": false}, + {"index": "B", "libelle": "1000 ft/min.", "correct": false}, + {"index": "C", "libelle": "2500 ft/min.", "correct": true}, + {"index": "D", "libelle": "3500 ft/min.", "correct": false} + ] + }, + { + "num": 264, + "libelle": "Une vitesse verticale de 35 m/s correspond approximativement à :", + "answers": [ + {"index": "A", "libelle": "3500 ft/min.", "correct": false}, + {"index": "B", "libelle": "2500 ft/min.", "correct": false}, + {"index": "C", "libelle": "7000 ft/min.", "correct": true}, + {"index": "D", "libelle": "8500 ft/min.", "correct": false} + ] + }, + { + "num": 265, + "libelle": "Dire qu’un mètre est égale à trois pieds est :", + "answers": [ + {"index": "A", "libelle": "Parfaitement Juste.", "correct": false}, + {"index": "B", "libelle": "Approximatif mais acceptable.", "correct": true}, + {"index": "C", "libelle": "Trop approximatif.", "correct": false} + ] + }, + { + "num": 266, + "libelle": "Un mètre est égale à :", + "answers": [ + {"index": "A", "libelle": "32,808 pieds soit environ 33 pieds.", "correct": false}, + {"index": "B", "libelle": "3,2808 pieds soit environ 3.3 pieds.", "correct": true}, + {"index": "C", "libelle": "3 pieds.", "correct": false} + ] + }, + { + "num": 267, + "libelle": "Un pied est égal à :", + "answers": [ + {"index": "A", "libelle": "33 cm.", "correct": false}, + {"index": "B", "libelle": "34,08 cm.", "correct": false}, + {"index": "C", "libelle": "30,48 cm.", "correct": true}, + {"index": "D", "libelle": "30 cm.", "correct": false} + ] + }, + { + "num": 268, + "libelle": "Un pied carré est sensiblement égale à :", + "answers": [ + {"index": "A", "libelle": "1,2m2", "correct": false}, + {"index": "B", "libelle": "0,09 m2", "correct": true}, + {"index": "C", "libelle": "0,9 m2", "correct": false} + ] + }, + { + "num": 269, + "libelle": "1 mille terrestre est égal à :", + "answers": [ + {"index": "A", "libelle": "1609 m.", "correct": true}, + {"index": "B", "libelle": "2185 m.", "correct": false}, + {"index": "C", "libelle": "1852 m.", "correct": false} + ] + }, + { + "num": 270, + "libelle": "1 mille nautique est égale à :", + "answers": [ + {"index": "A", "libelle": "1609 m.", "correct": false}, + {"index": "B", "libelle": "2185 m.", "correct": false}, + {"index": "C", "libelle": "1852 m.", "correct": true} + ] + }, + { + "num": 271, + "libelle": "Une vitesse d’un mètre par seconde (1 m/s) est égale à :", + "answers": [ + {"index": "A", "libelle": "3,6 km/h.", "correct": true}, + {"index": "B", "libelle": "36 km/h.", "correct": false}, + {"index": "C", "libelle": "6,3 km/h.", "correct": false} + ] + }, + { + "num": 272, + "libelle": "Une vitesse de 36 km/h est égale à :", + "answers": [ + {"index": "A", "libelle": "16 m/s.", "correct": false}, + {"index": "B", "libelle": "10 m/s.", "correct": true}, + {"index": "C", "libelle": "6 m/s.", "correct": false}, + {"index": "D", "libelle": "3.6 m/s.", "correct": false} + ] + } + ] + }, + { + "num": 10, + "name": "Altimétrie", + "questions": [ + { + "num": 273, + "libelle": "L’altimètre fournit au parachutiste des informations d’altitude en mesurant des différences :", + "answers": [ + {"index": "A", "libelle": "De pression.", "correct": true}, + {"index": "B", "libelle": "De température.", "correct": false}, + {"index": "C", "libelle": "De masse volumique.", "correct": false} + ] + }, + { + "num": 274, + "libelle": "Un niveau de vol c’est :", + "answers": [ + {"index": "A", "libelle": "L’indication donnée par l’altimètre quand il est réglé à zéro au sol.", "correct": false}, + {"index": "B", "libelle": "L’indication donnée par l’altimètre quand il est réglé au QNH au sol.", "correct": false}, + {"index": "C", "libelle": "L’indication donnée par l’altimètre quand il est réglé à 1013.25 hPa au sol.", "correct": true} + ] + }, + { + "num": 275, + "libelle": "Voler au niveau 115 signifie :", + "answers": [ + {"index": "A", "libelle": "Que l’on vole à une hauteur réelle de 11500 pieds.", "correct": false}, + {"index": "B", "libelle": "Qu’un altimètre calé à 1013,25 hPa indique 11500 pieds.", "correct": true}, + {"index": "C", "libelle": "Que l’on vole à une altitude de 11500 mètres.", "correct": false}, + {"index": "D", "libelle": "Qu’un altimètre calé à 1013,25 hPa indique 11500 mètres.", "correct": false} + ] + }, + { + "num": 276, + "libelle": "En atmosphère standard :", + "answers": [ + {"index": "A", "libelle": "La pression est de 1013,25 hPa au niveau de la mer.", "correct": true}, + {"index": "B", "libelle": "La pression est de 1013,25 hPa au sol.", "correct": false} + ] + }, + { + "num": 277, + "libelle": "L’altitude :", + "answers": [ + {"index": "A", "libelle": "Est la distance sur un axe vertical entre un point et le sol.", "correct": false}, + {"index": "B", "libelle": "Est la distance sur un axe vertical entre un point et le niveau de la mer.", "correct": true}, + {"index": "C", "libelle": "Est la distance sur un axe vertical entre un point et la surface isobare 1013.25 hPa.", "correct": false} + ] + }, + { + "num": 278, + "libelle": "La hauteur :", + "answers": [ + {"index": "A", "libelle": "Est la distance sur un axe vertical entre un point et le niveau de la mer.", "correct": false}, + {"index": "B", "libelle": "Est la distance sur un axe vertical entre un point et le sol.", "correct": true}, + {"index": "C", "libelle": "Est la distance sur un axe vertical entre un point et la surface isobare 1013.25 hPa.", "correct": false} + ] + }, + { + "num": 279, + "libelle": "La distance verticale entre un point et le sol est :", + "answers": [ + {"index": "A", "libelle": "La hauteur.", "correct": true}, + {"index": "B", "libelle": "L’altitude.", "correct": false}, + {"index": "C", "libelle": "Un niveau de vol.", "correct": false} + ] + }, + { + "num": 280, + "libelle": "La distance verticale entre un point et le niveau de la mer est :", + "answers": [ + {"index": "A", "libelle": "La hauteur.", "correct": false}, + {"index": "B", "libelle": "L’altitude.", "correct": true}, + {"index": "C", "libelle": "Un niveau de vol.", "correct": false} + ] + }, + { + "num": 281, + "libelle": "La distance verticale entre un point et la surface isobare 1013.25 hPa est :", + "answers": [ + {"index": "A", "libelle": "L’altitude.", "correct": false}, + {"index": "B", "libelle": "Un niveau de vol.", "correct": true}, + {"index": "C", "libelle": "La hauteur.", "correct": false} + ] + }, + { + "num": 282, + "libelle": "Une hauteur de 3000 pieds en atmosphère standard est approximativement égale à :", + "answers": [ + {"index": "A", "libelle": "1000 m.", "correct": false}, + {"index": "B", "libelle": "900 m.", "correct": true}, + {"index": "C", "libelle": "1100 m.", "correct": false} + ] + }, + { + "num": 283, + "libelle": "Le pied (ft) est une unité de mesure qui est égale à :", + "answers": [ + {"index": "A", "libelle": "0.30 cm.", "correct": false}, + {"index": "B", "libelle": "33 cm.", "correct": false}, + {"index": "C", "libelle": "30,48 cm.", "correct": true} + ] + }, + { + "num": 284, + "libelle": "Le soir, au sol, votre altimètre indique 0. Le lendemain matin il indique + 100 m. Qu’en déduisez-vous ?", + "answers": [ + {"index": "A", "libelle": "Une dépression arrive ou la pression baisse.", "correct": true}, + {"index": "B", "libelle": "On va vers une situation anticyclonique.", "correct": false}, + {"index": "C", "libelle": "Le sol est monté.", "correct": false} + ] + }, + { + "num": 285, + "libelle": "Le soir, au sol, votre altimètre indique 0. Le lendemain matin il indique – 100 m. Qu’en déduisez-vous ?", + "answers": [ + {"index": "A", "libelle": "Une dépression arrive.", "correct": false}, + {"index": "B", "libelle": "On va vers une situation anticyclonique ou la pression augmente.", "correct": true}, + {"index": "C", "libelle": "Le sol est plus bas qu’hier.", "correct": false} + ] + }, + { + "num": 286, + "libelle": "Lorsqu’il y a une baisse de température :", + "answers": [ + {"index": "A", "libelle": "L’altimètre sous estime la hauteur.", "correct": false}, + {"index": "B", "libelle": "L’altimètre surestime la hauteur.", "correct": true}, + {"index": "C", "libelle": "La température n’a pas d’influence sur l’altimètre.", "correct": false} + ] + }, + { + "num": 287, + "libelle": "Lorsqu’il y a une hausse de température :", + "answers": [ + {"index": "A", "libelle": "L’altimètre sous-estime la hauteur.", "correct": true}, + {"index": "B", "libelle": "L’altimètre surestime la hauteur.", "correct": false}, + {"index": "C", "libelle": "La température n’a pas d’influence sur l’altimètre.", "correct": false} + ] + }, + { + "num": 288, + "libelle": "Lorsqu’il y a une baisse de pression (arrivé d’une dépression) :", + "answers": [ + {"index": "A", "libelle": "L’altimètre surestime la hauteur.", "correct": true}, + {"index": "B", "libelle": "Les changements de pression dues à la météorologie n’ont pas d’influence sur l’altimètre.", "correct": false}, + {"index": "C", "libelle": "L’altimètre sous-estime la hauteur.", "correct": false} + ] + }, + { + "num": 289, + "libelle": "Lorsqu’il y a une hausse de pression (arrivé d’un anticyclone) :", + "answers": [ + {"index": "A", "libelle": "L’altimètre surestime la hauteur.", "correct": false}, + {"index": "B", "libelle": "L’altimètre sous-estime la hauteur.", "correct": true}, + {"index": "C", "libelle": "Les changements de pression dues à la météorologie n’ont pas d’influence sur l’altimètre.", "correct": false} + ] + }, + { + "num": 290, + "libelle": "Quand vous sautez au niveau 135 (FL 135) :", + "answers": [ + {"index": "A", "libelle": "Vous sautez toujours à une altitude de 4115 mètres.", "correct": false}, + {"index": "B", "libelle": "Pour connaitre la hauteur précise du saut il faut déduire l’altitude terrain.", "correct": false}, + {"index": "C", "libelle": "La hauteur du saut va varier en fonction des conditions météorologique et de l’altitude du terrain.", "correct": true}, + {"index": "D", "libelle": "Vous sautez toujours à une hauteur de 4000 mètres.", "correct": false} + ] + }, + { + "num": 291, + "libelle": "Votre zone de saut est à 500 mètres d’altitude. Quand vous sautez au niveau 145 (FL 145) :", + "answers": [ + {"index": "A", "libelle": "Vous sautez toujours à une altitude de 3920 mètres.", "correct": false}, + {"index": "B", "libelle": "a hauteur du saut va varier en fonction des conditions météorologique et de l’altitude du terrain.", "correct": true}, + {"index": "C", "libelle": "Vous sautez toujours à une hauteur de 3920 mètres.", "correct": false}, + {"index": "D", "libelle": "Vous sautez toujours à une altitude de 4420 mètres.", "correct": false} + ] + } + ] + }, + { + "num": 11, + "name": "Météorologie et aérologie", + "questions": [ + { + "num": 292, + "libelle": "En atmosphère standard :", + "answers": [ + {"index": "A", "libelle": "La température décroît de 8.5° c tous les 1000 m.", "correct": false}, + {"index": "B", "libelle": "La température décroît de 6.5° c tous les 1000 m.", "correct": true}, + {"index": "C", "libelle": "La température décroît de 10° c tous les 1000 m.", "correct": false} + ] + }, + { + "num": 293, + "libelle": "En atmosphère standard, quelle température fera-t-il à 4000 mètres si la température au sol est de 20° c ?", + "answers": [ + {"index": "A", "libelle": "+13.5° c.", "correct": false}, + {"index": "B", "libelle": "-6° c.", "correct": true}, + {"index": "C", "libelle": "–20° c.", "correct": false} + ] + }, + { + "num": 294, + "libelle": "En atmosphère standard, quelle température fera-t-il à 3000 mètres si la température au sol est de 10° c ?", + "answers": [ + {"index": "A", "libelle": "0° c.", "correct": false}, + {"index": "B", "libelle": "– 9.5° c.", "correct": true}, + {"index": "C", "libelle": "+ 6.5° c.", "correct": false} + ] + }, + { + "num": 295, + "libelle": "En atmosphère standard, quelle température fera-t-il à 4000 mètres si la température au sol est de 0° c ?", + "answers": [ + {"index": "A", "libelle": "–26° c.", "correct": true}, + {"index": "B", "libelle": "–54° c.", "correct": false}, + {"index": "C", "libelle": "–10° c.", "correct": false} + ] + }, + { + "num": 296, + "libelle": "Comment s’appellent Les lignes d’égale pression représentées sur les cartes météo ?", + "answers": [ + {"index": "A", "libelle": "Les lignes isohypses.", "correct": false}, + {"index": "B", "libelle": "Les lignes isothermes.", "correct": false}, + {"index": "C", "libelle": "Les lignes isobares.", "correct": true} + ] + }, + { + "num": 297, + "libelle": "Sur une carte météo, plus les lignes isobares sont serrées :", + "answers": [ + {"index": "A", "libelle": "Plus le vent est fort.", "correct": true}, + {"index": "B", "libelle": "Moins le vent est fort.", "correct": false}, + {"index": "C", "libelle": "La vitesse du vent ne dépend pas du resserrement des isobares.", "correct": false} + ] + }, + { + "num": 298, + "libelle": "Comment appelle-t-on une zone de hautes pressions généralisées ?", + "answers": [ + {"index": "A", "libelle": "Une dépression.", "correct": false}, + {"index": "B", "libelle": "Un thalweg.", "correct": false}, + {"index": "C", "libelle": "Un anticyclone.", "correct": true}, + {"index": "D", "libelle": "Une dorsale.", "correct": false} + ] + }, + { + "num": 299, + "libelle": "Comment appelle-t-on une zone de basses pressions généralisées ?", + "answers": [ + {"index": "A", "libelle": "Une dépression.", "correct": true}, + {"index": "B", "libelle": "Un thalweg.", "correct": false}, + {"index": "C", "libelle": "Un anticyclone.", "correct": false}, + {"index": "D", "libelle": "Une dorsale.", "correct": false} + ] + }, + { + "num": 300, + "libelle": "Comment appelle-t-on une crête de haute pression ?", + "answers": [ + {"index": "A", "libelle": "Une dépression.", "correct": false}, + {"index": "B", "libelle": "Un thalweg.", "correct": false}, + {"index": "C", "libelle": "Un anticyclone.", "correct": false}, + {"index": "D", "libelle": "Une dorsale.", "correct": true} + ] + }, + { + "num": 301, + "libelle": "Comment appelle-t-on une vallée de basse pression ?", + "answers": [ + {"index": "A", "libelle": "Une dépression.", "correct": false}, + {"index": "B", "libelle": "Un thalweg.", "correct": true}, + {"index": "C", "libelle": "Un anticyclone.", "correct": false}, + {"index": "D", "libelle": "Une dorsale.", "correct": false} + ] + }, + { + "num": 302, + "libelle": "Quand on monte en altitude, la pression atmosphérique :", + "answers": [ + {"index": "A", "libelle": "Augmente ou diminue suivant la situation météo.", "correct": false}, + {"index": "B", "libelle": "Diminue.", "correct": true}, + {"index": "C", "libelle": "Augmente.", "correct": false} + ] + }, + { + "num": 303, + "libelle": "La variation de pression entre le sol et 500 m. Est en moyenne de :", + "answers": [ + {"index": "A", "libelle": "1 hPa tous les 17 m.", "correct": false}, + {"index": "B", "libelle": "1 hPa tous les 11 m.", "correct": false}, + {"index": "C", "libelle": "1 hPa tous les 8.5 m.", "correct": true} + ] + }, + { + "num": 304, + "libelle": "Quand on monte en altitude en dessous de 500 m. La pression atmosphérique", + "answers": [ + {"index": "A", "libelle": "Augmente de 1 hPa tous les 8.5 m.", "correct": false}, + {"index": "B", "libelle": "Diminue de 1 hPa tous les 8.5 m.", "correct": true}, + {"index": "C", "libelle": "Diminue de 1 hPa tous les 11 m.", "correct": false} + ] + }, + { + "num": 305, + "libelle": "Un nuage est constitué :", + "answers": [ + {"index": "A", "libelle": "D’eau à l’état liquide ou solide.", "correct": true}, + {"index": "B", "libelle": "De vapeur d’eau uniquement.", "correct": false} + ] + }, + { + "num": 306, + "libelle": "On parle de brouillard :", + "answers": [ + {"index": "A", "libelle": "Quand la visibilité est inférieure à 100 m.", "correct": false}, + {"index": "B", "libelle": "Quand la visibilité est inférieure à 1 km.", "correct": true}, + {"index": "C", "libelle": "Quand la visibilité est réduite mais supérieure à 1 km.", "correct": false} + ] + }, + { + "num": 307, + "libelle": "On parle de brume :", + "answers": [ + {"index": "A", "libelle": "Quand la visibilité est inférieure à 100 m.", "correct": false}, + {"index": "B", "libelle": "Quand la visibilité est inférieure à 1 km.", "correct": false}, + {"index": "C", "libelle": "Quand la visibilité est réduite mais supérieure à 1 km.", "correct": true} + ] + }, + { + "num": 308, + "libelle": "A 5600 mètres d’altitude, la pression partielle d’oxygène :", + "answers": [ + {"index": "A", "libelle": "N’a pas changée.", "correct": false}, + {"index": "B", "libelle": "A diminué de 30%", "correct": false}, + {"index": "C", "libelle": "A diminué de moitié", "correct": true} + ] + }, + { + "num": 309, + "libelle": "Le vent est dû :", + "answers": [ + {"index": "A", "libelle": "Essentiellement aux phénomènes orageux.", "correct": false}, + {"index": "B", "libelle": "Aux différences de pression atmosphérique et aux différences de température.", "correct": true}, + {"index": "C", "libelle": "Aux marées.", "correct": false} + ] + }, + { + "num": 310, + "libelle": "Le vent en altitude est dû :", + "answers": [ + {"index": "A", "libelle": "Surtout à l’influence du sol et du relief.", "correct": false}, + {"index": "B", "libelle": "Aux anticyclones et aux dépressions.", "correct": true} + ] + }, + { + "num": 311, + "libelle": "Le vent au sol est dû :", + "answers": [ + {"index": "A", "libelle": "Uniquement à la nature du sol et au relief.", "correct": false}, + {"index": "B", "libelle": "Uniquement aux anticyclones et aux dépressions.", "correct": false}, + {"index": "C", "libelle": "A la situation météorologique générale et à l’influence du sol et du relief.", "correct": true} + ] + }, + { + "num": 312, + "libelle": "Dans une zone d’étranglement (col, vallée).", + "answers": [ + {"index": "A", "libelle": "La vitesse du vent augmente.", "correct": true}, + {"index": "B", "libelle": "La vitesse du vent diminue.", "correct": false}, + {"index": "C", "libelle": "Le relief n’a pas d’influence sur la vitesse du vent.", "correct": false} + ] + }, + { + "num": 313, + "libelle": "Un vent du sud-ouest :", + "answers": [ + {"index": "A", "libelle": "Souffle du sud au sol et de l’ouest en altitude.", "correct": false}, + {"index": "B", "libelle": "Souffle en provenance du sud-ouest.", "correct": true}, + {"index": "C", "libelle": "Souffle en direction du sud-ouest.", "correct": false} + ] + }, + { + "num": 314, + "libelle": "Le vent du nord :", + "answers": [ + {"index": "A", "libelle": "Souffle en provenance du nord.", "correct": true}, + {"index": "B", "libelle": "Souffle en direction du nord.", "correct": false} + ] + }, + { + "num": 315, + "libelle": "Un vent de 10 kts souffle à environ :", + "answers": [ + {"index": "A", "libelle": "5 m/s.", "correct": true}, + {"index": "B", "libelle": "10 m/s.", "correct": false}, + {"index": "C", "libelle": "20 m/s.", "correct": false} + ] + }, + { + "num": 316, + "libelle": "Un vent de 10 kts souffle à environ :", + "answers": [ + {"index": "A", "libelle": "36 km/h.", "correct": false}, + {"index": "B", "libelle": "18 km/h.", "correct": true}, + {"index": "C", "libelle": "10 km/h.", "correct": false} + ] + }, + { + "num": 317, + "libelle": "Un vent de 10 m/s souffle à environ :", + "answers": [ + {"index": "A", "libelle": "36 km/h.", "correct": true}, + {"index": "B", "libelle": "18 km/h.", "correct": false}, + {"index": "C", "libelle": "10 km/h.", "correct": false} + ] + }, + { + "num": 318, + "libelle": "Un vent de 18 kts souffle à environ :", + "answers": [ + {"index": "A", "libelle": "18 m/s.", "correct": false}, + {"index": "B", "libelle": "9 m/s.", "correct": true}, + {"index": "C", "libelle": "5 m/s.", "correct": false} + ] + }, + { + "num": 319, + "libelle": "Un vent de 7 m/s souffle à environ :", + "answers": [ + {"index": "A", "libelle": "36 km/h.", "correct": false}, + {"index": "B", "libelle": "18 km/h.", "correct": false}, + {"index": "C", "libelle": "25 km/h.", "correct": true} + ] + }, + { + "num": 320, + "libelle": "Un vent de 5 m/s souffle à environ :", + "answers": [ + {"index": "A", "libelle": "5 km/h.", "correct": false}, + {"index": "B", "libelle": "10 km/h.", "correct": false}, + {"index": "C", "libelle": "18 km/h.", "correct": true} + ] + }, + { + "num": 321, + "libelle": "Quelle est la vitesse du vent en m/s quand il souffle à 20 kts :", + "answers": [ + {"index": "A", "libelle": "10 m/s.", "correct": true}, + {"index": "B", "libelle": "5 m/s.", "correct": false}, + {"index": "C", "libelle": "18 m/s.", "correct": false} + ] + }, + { + "num": 322, + "libelle": "La météo annonce un vent du 270° ; cela veut dire qu’il vient :", + "answers": [ + {"index": "A", "libelle": "De l’ouest.", "correct": true}, + {"index": "B", "libelle": "Du nord.", "correct": false}, + {"index": "C", "libelle": "Du sud ouest.", "correct": false} + ] + }, + { + "num": 323, + "libelle": "Qu’appelle-t-on inversion de vent ?", + "answers": [ + {"index": "A", "libelle": "Le vent change de direction en altitude par rapport au vent au sol.", "correct": true}, + {"index": "B", "libelle": "C’est l’ordre de saut des parachutistes qui est inversé à cause du vent.", "correct": false}, + {"index": "C", "libelle": "Le vent souffle de façon irrégulière dans toutes les directions.", "correct": false} + ] + }, + { + "num": 324, + "libelle": "Qu’appelle-t-on cisaillement de vent ?", + "answers": [ + {"index": "A", "libelle": "C’est un vent suffisamment fort pour couper la cime des arbres.", "correct": false}, + {"index": "B", "libelle": "C’est la zone de séparation entre deux vents de sens contraire.", "correct": true} + ] + }, + { + "num": 325, + "libelle": "Qu’est-ce qu’une zone de cisaillement ?", + "answers": [ + {"index": "A", "libelle": "C’est une zone comprise entre deux couches d’air où la température est différente.", "correct": false}, + {"index": "B", "libelle": "C’est une zone comprise entre deux couches d’air où la pression est différente.", "correct": false}, + {"index": "C", "libelle": "C’est une zone comprise entre deux couches d’air où le vent n’a pas la même direction.", "correct": true} + ] + }, + { + "num": 326, + "libelle": "Le vent en altitude et le vent au sol :", + "answers": [ + {"index": "A", "libelle": "Peuvent souffler en sens contraire.", "correct": true}, + {"index": "B", "libelle": "Soufflent toujours dans le même sens.", "correct": false} + ] + }, + { + "num": 327, + "libelle": "Dans l’hémisphère nord, le vent :", + "answers": [ + {"index": "A", "libelle": "Tourne autour des dépressions dans le sens des aiguilles d’une montre et autour des anticyclones en sens inverse.", "correct": false}, + {"index": "B", "libelle": "Tourne autour des anticyclones dans le sens des aiguilles d’une montre et autour des dépressions en sens inverse.", "correct": true} + ] + }, + { + "num": 328, + "libelle": "Dans quel sens le vent tourne-t-il autour des anticyclones dans l’hémisphère nord ?", + "answers": [ + {"index": "A", "libelle": "Il tourne dans le sens des aiguilles d’une montre.", "correct": true}, + {"index": "B", "libelle": "Il tourne dans le sens inverse des aiguilles d’une montre.", "correct": false} + ] + }, + { + "num": 329, + "libelle": "Dans quel sens le vent tourne-t-il autour des dépressions dans l’hémisphère nord ?", + "answers": [ + {"index": "A", "libelle": "Il tourne dans le sens des aiguilles d’une montre.", "correct": false}, + {"index": "B", "libelle": "Il tourne dans le sens inverse des aiguilles d’une montre.", "correct": true} + ] + }, + { + "num": 330, + "libelle": "En altitude, le vent météo :", + "answers": [ + {"index": "A", "libelle": "Est dévié sur la droite dans l’hémisphère nord.", "correct": true}, + {"index": "B", "libelle": "Est dévié sur la gauche dans l’hémisphère nord.", "correct": false}, + {"index": "C", "libelle": "Ne subit aucune déviation.", "correct": false} + ] + }, + { + "num": 331, + "libelle": "En bord de mer, par ciel couvert sans vent météorologique :", + "answers": [ + {"index": "A", "libelle": "Il y aura probablement une brise soufflant de la mer vers la terre.", "correct": false}, + {"index": "B", "libelle": "Il y aura probablement une brise soufflant de la terre vers la mer.", "correct": false}, + {"index": "C", "libelle": "Il n’y aura probablement pas de brise.", "correct": true} + ] + }, + { + "num": 332, + "libelle": "En bord de mer, en été, en milieu d’après midi et par temps ensoleillé sans vent météorologique :", + "answers": [ + {"index": "A", "libelle": "Il y aura probablement une brise soufflant de la mer vers la terre.", "correct": true}, + {"index": "B", "libelle": "Il y aura probablement une brise soufflant de la terre vers la mer.", "correct": false}, + {"index": "C", "libelle": "Il n’y aura probablement pas de brise.", "correct": false} + ] + }, + { + "num": 333, + "libelle": "En milieu de journée, par temps ensoleillé, la brise souffle.", + "answers": [ + {"index": "A", "libelle": "De la terre vers la mer.", "correct": false}, + {"index": "B", "libelle": "De la mer vers la terre.", "correct": true} + ] + }, + { + "num": 334, + "libelle": "En bord de mer, la nuit, on risque d’avoir.", + "answers": [ + {"index": "A", "libelle": "De la brise de mer.", "correct": false}, + {"index": "B", "libelle": "De la brise de terre.", "correct": true}, + {"index": "C", "libelle": "Aucune brise.", "correct": false} + ] + }, + { + "num": 335, + "libelle": "Dans une vallée étroite, en milieu d’après midi et par temps ensoleillé, il y aura probablement :", + "answers": [ + {"index": "A", "libelle": "Une forte brise de vallée montante qui risque d’empêcher les sauts.", "correct": true}, + {"index": "B", "libelle": "Une forte brise de vallée montante mais il est toujours possible de sauter.", "correct": false}, + {"index": "C", "libelle": "Une forte brise de vallée descendante.", "correct": false} + ] + }, + { + "num": 336, + "libelle": "Sur une pente, par temps ensoleillé, on rencontre en milieu de journée :", + "answers": [ + {"index": "A", "libelle": "Des brises descendantes.", "correct": false}, + {"index": "B", "libelle": "Des brises montantes.", "correct": true} + ] + }, + { + "num": 337, + "libelle": "Les brises de vallée soufflent dans la même direction toute la journée :", + "answers": [ + {"index": "A", "libelle": "Vrai.", "correct": false}, + {"index": "B", "libelle": "Faux.", "correct": true} + ] + }, + { + "num": 338, + "libelle": "Les brises sont des vents :", + "answers": [ + {"index": "A", "libelle": "Bien établis, qui changent lentement.", "correct": false}, + {"index": "B", "libelle": "Qui soufflent dans tous les sens et changent sans cesse de direction.", "correct": false}, + {"index": "C", "libelle": "Bien établis, mais qui changent de direction en peu de temps.", "correct": true} + ] + }, + { + "num": 339, + "libelle": "Avec du vent, le risque de turbulences est maximum :", + "answers": [ + {"index": "A", "libelle": "Sur un relief accidenté.", "correct": true}, + {"index": "B", "libelle": "En plaine dans une zone dégagée.", "correct": false} + ] + }, + { + "num": 340, + "libelle": "Par fort vent, derrière un hangar :", + "answers": [ + {"index": "A", "libelle": "Il n’y a pas de turbulences.", "correct": false}, + {"index": "B", "libelle": "Il y a des turbulences.", "correct": true} + ] + }, + { + "num": 341, + "libelle": "Par fort vent derrière une haie d’arbres :", + "answers": [ + {"index": "A", "libelle": "Il n’y a pas de turbulences.", "correct": false}, + {"index": "B", "libelle": "Il y a des turbulences.", "correct": true} + ] + }, + { + "num": 342, + "libelle": "On risque de rencontrer des turbulences :", + "answers": [ + {"index": "A", "libelle": "En présence de stratus.", "correct": false}, + {"index": "B", "libelle": "En présence de cumulus.", "correct": true}, + {"index": "C", "libelle": "En présence de cirrus.", "correct": false} + ] + }, + { + "num": 343, + "libelle": "On risque surtout de rencontrer des turbulences :", + "answers": [ + {"index": "A", "libelle": "Par vent faible.", "correct": false}, + {"index": "B", "libelle": "Par vent fort.", "correct": true} + ] + }, + { + "num": 344, + "libelle": "On risque surtout de rencontrer des turbulences :", + "answers": [ + {"index": "A", "libelle": "Par temps chaud et ensoleillé.", "correct": true}, + {"index": "B", "libelle": "Par ciel couvert.", "correct": false} + ] + }, + { + "num": 345, + "libelle": "On risque surtout de rencontrer des turbulences :", + "answers": [ + {"index": "A", "libelle": "Au dessus des surfaces froides.", "correct": false}, + {"index": "B", "libelle": "Au dessus des surfaces chaudes.", "correct": true}, + {"index": "C", "libelle": "La température du sol n’a pas d’influence sur les turbulences.", "correct": false} + ] + }, + { + "num": 346, + "libelle": "Avec un vent de 5 m/s, les turbulences derrière une haie d’arbres ou un bâtiment de même hauteur sont dangereuses :", + "answers": [ + {"index": "A", "libelle": "Juste derrière sur quelques mètres.", "correct": false}, + {"index": "B", "libelle": "Jusqu’à plus de 50 m.", "correct": true}, + {"index": "C", "libelle": "Ne sont pas dangereuse avec un vent de 5 m/s.", "correct": false} + ] + }, + { + "num": 347, + "libelle": "Les vents rabattants en montagne :", + "answers": [ + {"index": "A", "libelle": "Ne sont pas dangereux car ils nous font dévier en plaine.", "correct": false}, + {"index": "B", "libelle": "Sont seulement dangereux pour les avions.", "correct": false}, + {"index": "C", "libelle": "Sont dangereux et risquent de nous plaquer contre le relief.", "correct": true} + ] + }, + { + "num": 348, + "libelle": "En été par temps chaud et en présence de cumulus bien développés :", + "answers": [ + {"index": "A", "libelle": "Il n’y a pas de turbulences.", "correct": false}, + {"index": "B", "libelle": "Il y a probablement des turbulences.", "correct": true} + ] + }, + { + "num": 349, + "libelle": "L’aérologie en montagne :", + "answers": [ + {"index": "A", "libelle": "Est bien moins turbulente qu’en plaine car l’air est frais.", "correct": false}, + {"index": "B", "libelle": "Est souvent plus turbulentes qu’en plaine.", "correct": true} + ] + }, + { + "num": 350, + "libelle": "Par temps chaud, les turbulences sont plus importantes :", + "answers": [ + {"index": "A", "libelle": "Au dessus d’un terrain en herbe.", "correct": false}, + {"index": "B", "libelle": "Au dessus des hangars et des parkings goudronnés.", "correct": true} + ] + }, + { + "num": 351, + "libelle": "Le risque de rencontrer des courants ascendants est maximum :", + "answers": [ + {"index": "A", "libelle": "Au-dessus des surfaces froides par temps couvert.", "correct": false}, + {"index": "B", "libelle": "Au-dessus des surfaces froides par temps ensoleillé.", "correct": false}, + {"index": "C", "libelle": "Au-dessus des surfaces chaudes par temps couvert.", "correct": false}, + {"index": "D", "libelle": "Au-dessus des surfaces chaudes par temps ensoleillé.", "correct": true} + ] + }, + { + "num": 352, + "libelle": "On risque de rencontrer des courants ascendants.", + "answers": [ + {"index": "A", "libelle": "Sous le vent d’un relief.", "correct": false}, + {"index": "B", "libelle": "Au vent d’un relief.", "correct": true} + ] + }, + { + "num": 353, + "libelle": "Á quel étage rencontre-t-on les nuages dont le nom comporte le préfixe « Cirro. » ?", + "answers": [ + {"index": "A", "libelle": "Á l’étage supérieur.", "correct": true}, + {"index": "B", "libelle": "Á l’étage moyen.", "correct": false}, + {"index": "C", "libelle": "Á l’étage inférieur.", "correct": false} + ] + }, + { + "num": 354, + "libelle": "Á quel étage rencontre-t-on les nuages dont le nom comporte le préfixe « Strato. » ?", + "answers": [ + {"index": "A", "libelle": "Á l’étage supérieur.", "correct": false}, + {"index": "B", "libelle": "Á l’étage moyen.", "correct": false}, + {"index": "C", "libelle": "Á l’étage inférieur.", "correct": true} + ] + }, + { + "num": 355, + "libelle": "Les cumulus sont des nuages :", + "answers": [ + {"index": "A", "libelle": "Isolés et de forme bien marqués.", "correct": true}, + {"index": "B", "libelle": "En voile ou en nappe continue avec des contours flous.", "correct": false}, + {"index": "C", "libelle": "En filaments.", "correct": false} + ] + }, + { + "num": 356, + "libelle": "Les nuages de type « cumulo. » se forment :", + "answers": [ + {"index": "A", "libelle": "Dans des conditions d’instabilité verticale.", "correct": true}, + {"index": "B", "libelle": "Dans des conditions de stabilité verticale.", "correct": false} + ] + }, + { + "num": 357, + "libelle": "Les stratus sont des nuages :", + "answers": [ + {"index": "A", "libelle": "Isolés et de forme bien marqués.", "correct": false}, + {"index": "B", "libelle": "En voile ou en nappe continue avec des contours flous.", "correct": true}, + {"index": "C", "libelle": "En filaments.", "correct": false} + ] + }, + { + "num": 358, + "libelle": "Les nuages de type « strato. » se forment", + "answers": [ + {"index": "A", "libelle": "Dans des conditions d’instabilité verticale.", "correct": false}, + {"index": "B", "libelle": "Dans des conditions de stabilité verticale.", "correct": true} + ] + }, + { + "num": 359, + "libelle": "Les cirrus sont des nuages :", + "answers": [ + {"index": "A", "libelle": "Isolés et de forme bien marqués.", "correct": false}, + {"index": "B", "libelle": "En voile ou en nappe continue avec des contours flous.", "correct": false}, + {"index": "C", "libelle": "En filaments.", "correct": true} + ] + }, + { + "num": 360, + "libelle": "Le nimbostratus :", + "answers": [ + {"index": "A", "libelle": "Un nuage de beau temps.", "correct": false}, + {"index": "B", "libelle": "Est un nuage épais associé à des précipitations durables.", "correct": true} + ] + }, + { + "num": 361, + "libelle": "La présence d’une couche nuageuse en altitude (cirrocumulus ou cirrostratus) :", + "answers": [ + {"index": "A", "libelle": "Indique que le mauvais temps est passé.", "correct": false}, + {"index": "B", "libelle": "Annonce l ‘arrivée d’une perturbation.", "correct": true} + ] + }, + { + "num": 362, + "libelle": "Les strato-cumulus :", + "answers": [ + {"index": "A", "libelle": "Sont des nuages bas.", "correct": true}, + {"index": "B", "libelle": "Sont des nuages de l’étage moyen.", "correct": false}, + {"index": "C", "libelle": "Sont des nuages d’altitude.", "correct": false} + ] + }, + { + "num": 363, + "libelle": "Quel est le nuage le plus dangereux pour le parachutisme ?", + "answers": [ + {"index": "A", "libelle": "Le cumulonimbus.", "correct": true}, + {"index": "B", "libelle": "Le cirro-stratus.", "correct": false}, + {"index": "C", "libelle": "Le cumulus.", "correct": false} + ] + }, + { + "num": 364, + "libelle": "A l’approche d’un orage :", + "answers": [ + {"index": "A", "libelle": "Le vent est stable en vitesse et en direction.", "correct": false}, + {"index": "B", "libelle": "S’il y a une accalmie de vent, c’est que le risque d’orage a disparu.", "correct": false}, + {"index": "C", "libelle": "Le vent peut s’inverser très rapidement de 180°.", "correct": true} + ] + }, + { + "num": 365, + "libelle": "A l’approche d’un orage :", + "answers": [ + {"index": "A", "libelle": "Il faut arrêter immédiatement les largages s’il y a des éclairs ou du tonnerre.", "correct": true}, + {"index": "B", "libelle": "On peut sauter tant que le vent ne dépasse pas la limite réglementaire.", "correct": false}, + {"index": "C", "libelle": "On peut sauter tant qu’il ne pleut pas.", "correct": false} + ] + }, + { + "num": 366, + "libelle": "Le risque d’orage est plus important :", + "answers": [ + {"index": "A", "libelle": "Au printemps et en été.", "correct": true}, + {"index": "B", "libelle": "En hiver.", "correct": false} + ] + }, + { + "num": 367, + "libelle": "En saison chaude, le risque d’orage est plus important :", + "answers": [ + {"index": "A", "libelle": "L’après-midi.", "correct": true}, + {"index": "B", "libelle": "Le matin.", "correct": false}, + {"index": "C", "libelle": "La nuit.", "correct": false} + ] + }, + { + "num": 368, + "libelle": "Lequel de ces nuages est un nuage d’orage ?", + "answers": [ + {"index": "A", "libelle": "Le strato-cumulus.", "correct": false}, + {"index": "B", "libelle": "Le cumulonimbus.", "correct": true}, + {"index": "C", "libelle": "L’altocumulus.", "correct": false} + ] + }, + { + "num": 369, + "libelle": "Voler près d’un cumulonimbus :", + "answers": [ + {"index": "A", "libelle": "Peux se faire tout de suite après le moment le plus fort de l’orage.", "correct": false}, + {"index": "B", "libelle": "N’est jamais dangereux tant que l’orage n’a pas éclaté.", "correct": false}, + {"index": "C", "libelle": "Est très dangereux.", "correct": true} + ] + }, + { + "num": 370, + "libelle": "Un nuage d’orage :", + "answers": [ + {"index": "A", "libelle": "Evolue toujours très lentement.", "correct": false}, + {"index": "B", "libelle": "Peut évoluer rapidement.", "correct": true} + ] + }, + { + "num": 371, + "libelle": "Un nuage d’orage :", + "answers": [ + {"index": "A", "libelle": "Peut être masqué par d’autres nuages.", "correct": true}, + {"index": "B", "libelle": "Est toujours visible de loin.", "correct": false} + ] + }, + { + "num": 372, + "libelle": "A l’approche d’un orage, il faut cesser les sauts :", + "answers": [ + {"index": "A", "libelle": "Uniquement si le vent au sol dépasse la limite autorisée.", "correct": false}, + {"index": "B", "libelle": "Dès que l’on a un doute sur la situation.", "correct": true} + ] + }, + { + "num": 373, + "libelle": "A proximité d’un cumulonimbus :", + "answers": [ + {"index": "A", "libelle": "Il y a toujours de fortes turbulences.", "correct": true}, + {"index": "B", "libelle": "Il n’y a pas de turbulences.", "correct": false} + ] + }, + { + "num": 374, + "libelle": "En situation orageuse, on rencontre :", + "answers": [ + {"index": "A", "libelle": "Des vents forts et en rafales qui peuvent atteindre des vitesses très élevées.", "correct": true}, + {"index": "B", "libelle": "Des vents forts mais réguliers.", "correct": false}, + {"index": "C", "libelle": "Des vents faibles.", "correct": false} + ] + }, + { + "num": 375, + "libelle": "L’orage est un phénomène :", + "answers": [ + {"index": "A", "libelle": "Très dangereux pour toutes les activités aéronautiques.", "correct": true}, + {"index": "B", "libelle": "Dangereux pour le parachutisme mais pas pour les avions.", "correct": false} + ] + }, + { + "num": 376, + "libelle": "Un nuage d’orage :", + "answers": [ + {"index": "A", "libelle": "Se déplace parfois en sens contraire au vent dominant.", "correct": true}, + {"index": "B", "libelle": "Se déplace toujours dans le sens du vent dominant.", "correct": false} + ] + }, + { + "num": 377, + "libelle": "Un cumulonimbus :", + "answers": [ + {"index": "A", "libelle": "Dépasse rarement 5000 mètres d’altitude à son sommet.", "correct": false}, + {"index": "B", "libelle": "Peut dépasser 5000 mètres d’altitude mais jamais 10000 mètres.", "correct": false}, + {"index": "C", "libelle": "Peut dépasser 10000 mètres d’altitude.", "correct": true} + ] + }, + { + "num": 378, + "libelle": "Le vent donne-t-il une indication sur l’évolution d’un cumulonimbus ?", + "answers": [ + {"index": "A", "libelle": "Oui.", "correct": false}, + {"index": "B", "libelle": "Non.", "correct": true} + ] + }, + { + "num": 379, + "libelle": "Une station météo proche du terrain passe un avis de tempête :", + "answers": [ + {"index": "A", "libelle": "Il faut arrêter la séance quand le vent atteint la limite autorisée.", "correct": false}, + {"index": "B", "libelle": "On peut continuer à sauter encore quelques temps en surveillant le vent.", "correct": false}, + {"index": "C", "libelle": "Il faut arrêter immédiatement la séance de sauts.", "correct": true} + ] + }, + { + "num": 380, + "libelle": "Au passage d’un cumulonimbus, on peut s’attendre :", + "answers": [ + {"index": "A", "libelle": "A un renforcement soudain du vent sans changement de direction.", "correct": false}, + {"index": "B", "libelle": "A un renforcement soudain du vent faisant suite à un changement de direction.", "correct": true} + ] + }, + { + "num": 381, + "libelle": "Sous un cumulonimbus, les précipitations sont souvent :", + "answers": [ + {"index": "A", "libelle": "Faibles.", "correct": false}, + {"index": "B", "libelle": "Inexistantes.", "correct": false}, + {"index": "C", "libelle": "Violentes.", "correct": true} + ] + } + ] + }, + { + "num": 12, + "name": "Sauts spéciaux", + "questions": [ + { + "num": 382, + "libelle": "Pour effectuer un saut de nuit, il faut au minimum :", + "answers": [ + {"index": "A", "libelle": "Un altimètre fluorescent ou éclairé.", "correct": false}, + {"index": "B", "libelle": "Une lampe et un altimètre fluorescent ou éclairé.", "correct": true}, + {"index": "C", "libelle": "Une lampe.", "correct": false} + ] + }, + { + "num": 383, + "libelle": "Pour effectuer un saut de nuit, en plus de l’autorisation du directeur technique, il faut au minimum :", + "answers": [ + {"index": "A", "libelle": "Etre titulaire du brevet C ou D et avoir effectué 10 sauts dans les 3 derniers mois.", "correct": false}, + {"index": "B", "libelle": "Etre titulaire du BPA et avoir effectué 50 sauts dans les 12 derniers mois.", "correct": true}, + {"index": "C", "libelle": "Etre titulaire du BPA.", "correct": false} + ] + }, + { + "num": 384, + "libelle": "Pour effectuer un saut à haute altitude, outre l’autorisation du directeur technique, il faut au minimum :", + "answers": [ + {"index": "A", "libelle": "Etre titulaire du brevet C ou D et avoir effectué 10 sauts dans les 3 derniers mois.", "correct": false}, + {"index": "B", "libelle": "Etre titulaire du BPA et avoir effectué 50 sauts dans les 12 derniers mois.", "correct": true}, + {"index": "C", "libelle": "Etre titulaire du BPA.", "correct": false} + ] + }, + { + "num": 385, + "libelle": "Lors d’un saut à 4500 mètres, si l’on doit attendre 15 minutes à la hauteur de largage :", + "answers": [ + {"index": "A", "libelle": "Cela ne pose pas de problèmes particuliers.", "correct": false}, + {"index": "B", "libelle": "Les problèmes physiologiques dus à l’altitude sont beaucoup plus conséquents que si l’on n’a pas d’attente.", "correct": true} + ] + }, + { + "num": 386, + "libelle": "Pour effectuer un saut à 5000 mètres d’altitude, l’oxygène à bord :", + "answers": [ + {"index": "A", "libelle": "Est obligatoire.", "correct": true}, + {"index": "B", "libelle": "N’est pas obligatoire.", "correct": false} + ] + }, + { + "num": 387, + "libelle": "Lors d’un saut à 6000 mètres, l’oxygène à bord :", + "answers": [ + {"index": "A", "libelle": "Est obligatoire parce que la réglementation l’impose.", "correct": false}, + {"index": "B", "libelle": "N’est pas nécessaire.", "correct": false}, + {"index": "C", "libelle": "Est physiologiquement indispensable et réglementairement obligatoire.", "correct": true} + ] + }, + { + "num": 388, + "libelle": "Lors d’un saut à 6000 mètres, la prise d’oxygène à partir de 4000 mètres est :", + "answers": [ + {"index": "A", "libelle": "Nécessaire uniquement pour ceux qui en ressentent le besoin.", "correct": false}, + {"index": "B", "libelle": "Obligatoire uniquement pour les personnes mineures.", "correct": false}, + {"index": "C", "libelle": "Indispensable quel que soit le niveau des pratiquants.", "correct": true} + ] + }, + { + "num": 389, + "libelle": "L’emport à bord d’un aéronef d’un système individuel d’oxygène est-il obligatoire pour effectuer des sauts à haute altitude ?", + "answers": [ + {"index": "A", "libelle": "Uniquement pour les sauts s’effectuant au dessus de 5000 mètres.", "correct": false}, + {"index": "B", "libelle": "Uniquement pour les sauts s’effectuant au dessus de 6000 mètres.", "correct": false}, + {"index": "C", "libelle": "Oui.", "correct": true} + ] + }, + { + "num": 390, + "libelle": "Le contrôle des équipements au sol et avant la sortie à 6000 mètres doit être particulièrement rigoureux pour :", + "answers": [ + {"index": "A", "libelle": "Minimiser les risques d’ouverture en altitude.", "correct": true}, + {"index": "B", "libelle": "Ne pas risquer une ouverture intempestive à bord et payer un saut non fait.", "correct": false}, + {"index": "C", "libelle": "Cela n’est pas nécessaire.", "correct": false} + ] + }, + { + "num": 391, + "libelle": "Le temps de chute (à plat) pour un départ à 6000 m est d’environ", + "answers": [ + {"index": "A", "libelle": "1 minute et 40 secondes. ", "correct": true}, + {"index": "B", "libelle": "1 minute et 10 secondes.", "correct": false}, + {"index": "C", "libelle": "2 minutes et 30 secondes.", "correct": false} + ] + }, + { + "num": 392, + "libelle": "Quelle sera la dérive en chute lors d’un saut à 6000 mètres avec un vent de 10 m/s ?", + "answers": [ + {"index": "A", "libelle": "Environ 1500 m.", "correct": false}, + {"index": "B", "libelle": "Environ 1000 m.", "correct": true}, + {"index": "C", "libelle": "Environ 400 m.", "correct": false} + ] + }, + { + "num": 393, + "libelle": "Quelle sera la dérive en chute lors d’un saut à 6000 mètres avec un vent de 40 kts ?", + "answers": [ + {"index": "A", "libelle": "Environ 1200 m.", "correct": false}, + {"index": "B", "libelle": "Environ 2000 m.", "correct": true}, + {"index": "C", "libelle": "Environ 500 m.", "correct": false} + ] + }, + { + "num": 394, + "libelle": "Pour sauter d’un ULM il faut au minimum :", + "answers": [ + {"index": "A", "libelle": "Le brevet C ou D.", "correct": true}, + {"index": "B", "libelle": "Le BPA.", "correct": false}, + {"index": "C", "libelle": "Le brevet B.", "correct": false} + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/routes/api/aeronefs.js b/routes/api/aeronefs.js new file mode 100644 index 0000000..49281db --- /dev/null +++ b/routes/api/aeronefs.js @@ -0,0 +1,68 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Aeronef = mongoose.model('Aeronef'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../auth'); + + +router.get('/', auth.required, function (req, res, next) { + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + var query = [ + { + '$match': { + '$expr': { + '$eq': [ '$author' , { '$toObjectId': user._id.toString() } ] + } + } + }, { + '$group': { + '_id': { + 'aeronef': '$aeronef', + 'imat': '$imat' + }, + 'count': { + '$sum': 1 + } + } + } + ]; + Promise.all([ + Jump.aggregate(query) + .limit(Number(limit)) + .skip(Number(offset)) + .exec() + //Aeronef.count(query) + ]).then(function (results) { + var aeronefs = results[0]; + //var aeronefsCount = results[1]; + return res.json({ + aeronefs: aeronefs, + aeronefsCount: aeronefs.length + }); + /* + return res.json({ + aeronefs: aeronefs.map(function (aeronef) { + return aeronef.toJSONFor(user); + }), + aeronefsCount: aeronefsCount + }); + */ + }).catch(next); + }).catch(next); + +}); + +module.exports = router; diff --git a/routes/api/applications.js b/routes/api/applications.js new file mode 100644 index 0000000..b90528f --- /dev/null +++ b/routes/api/applications.js @@ -0,0 +1,160 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Application = mongoose.model('Application'), + User = mongoose.model('User'); +const auth = require('../auth'), + mailer = require('@sendgrid/mail'); + +mailer.setApiKey(process.env.SENDGRID_API_KEY); + +// Preload application objects on routes with ':application' +router.param('application', function (req, res, next, slug) { + Application.findOne({ slug: slug }) + .populate('author') + .then(function (application) { + if (!application) { + return res.sendStatus(404); + } + req.application = application; + return next(); + }).catch(next); +}); + +router.get('/', auth.required, function (req, res, next) { + var query = {}; + var limit = 20; + var offset = 0; + + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.apikey !== 'undefined') { + query.apikey = req.query.apikey; + } + if (typeof req.query.maskedkey !== 'undefined') { + query.maskedkey = req.query.maskedkey; + } + + Promise.all([ + req.query.author ? User.findOne({ username: req.query.author }) : null + ]).then(function (author) { + if (author[0]) { + query.author = author[0]._id; + } + return Promise.all([ + Application.find(query) + .limit(Number(limit)) + .skip(Number(offset)) + .sort({ createdAt: 'desc' }) + .populate('author') + .exec(), + Application.count(query).exec(), + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + var applications = results[0]; + var applicationsCount = results[1]; + var user = results[2]; + + return res.json({ + applications: applications.map(function (application) { + return application.toJSONFor(user); + }), + applicationsCount: applicationsCount + }); + }); + }).catch(next); +}); + +router.post('/', auth.required, function (req, res, next) { + Promise.all([ + User.findById(req.payload.id), + auth.generateAPIKey() + ]).then(function (results) { + var user = results[0]; + var keys = results[1]; + if (!user) { + return res.sendStatus(401).json({message: "Unauthorized - You are not allowed to access this resource."}); + } + var application = new Application(req.body.application); + application.author = user; + application.apikey = keys.encrypted; + application.maskedkey = keys.masked; + return application.save().then(function () { + var msg = { + to: user.email, + from: process.env.SENDGRID_FROM_MAIL, + subject: `Api-Key ${application.title}`, + text: `Votre Api-Key: ${keys.key}\nAttention, cette Api-Key doit être conservée et ne sera plus affichée.\n\n${application.title}\n${application.description}`, + html: `

${application.title}

+

+ Votre Api-Key: ${keys.key}
+ Attention, cette Api-Key doit être conservée et ne sera plus affichée. +

+
+

${application.description}

` + }; + mailer.send(msg).then(resp => { + return res.json({ application: application.toJSONFor(user) }); + }) + .catch(err => { + res.sendStatus(500).json({message: "A SendGrid error occured while sending an email", err: err}); + }); + }); + }).catch(next); +}); + +// return an application +router.get('/:application', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + req.application.populate('author') + ]).then(function (results) { + var user = results[0]; + return res.json({ application: req.application.toJSONFor(user) }); + }).catch(next); +}); + +router.put('/:application', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (req.application.author._id.toString() === req.payload.id.toString()) { + if (typeof req.body.application.title !== 'undefined') { + req.application.title = req.body.application.title; + } + if (typeof req.body.application.description !== 'undefined') { + req.application.description = req.body.application.description; + } + if (typeof req.body.application.apikey !== 'undefined') { + req.application.apikey = req.body.application.apikey; + } + if (typeof req.body.application.maskedkey !== 'undefined') { + req.application.maskedkey = req.body.application.maskedkey; + } + req.application.save().then(function (application) { + return res.json({ application: application.toJSONFor(user) }); + }).catch(next); + } else { + return res.sendStatus(403); + } + }); +}); + +// delete application +router.delete('/:application', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401).json({message: "Unauthorized - You are not allowed to access this resource."}); + } + if (user.role == 'Admin' || req.application.author._id.toString() === req.payload.id.toString()) { + return req.application.remove().then(function () { + return res.sendStatus(204); + }); + } else { + return res.sendStatus(403).json({message: "Forbidden - You are not allowed to access this resource."}); + } + }).catch(next); +}); + +module.exports = router; diff --git a/routes/api/calcul.js b/routes/api/calcul.js new file mode 100644 index 0000000..9886914 --- /dev/null +++ b/routes/api/calcul.js @@ -0,0 +1,129 @@ +var router = require('express').Router(); +const auth = require('../auth'); +//var ExceptionsHandler = require('../../middlewares/exceptions.handler'); +//var UnknownRoutesHandler = require('../../middlewares/unknownRoutes.handler'); + +router.use('/', function (req, res, next) { + let taxationValues = ['cours_legaux', 'jetons', 'metaux_precieux']; + let errors = []; + if (req.body.taxationType == undefined || taxationValues.indexOf(req.body.taxationType) === -1) { + errors.push('La valeur du paramètre \'taxationType\' est incorrecte.'); + } + if (req.body.purchasePrice == undefined || req.body.purchasePrice <= 0) { + errors.push('La valeur du paramètre \'purchasePrice\' est incorrecte.'); + } + if (req.body.sellPrice == undefined || req.body.sellPrice <= 0) { + errors.push('La valeur du paramètre \'sellPrice\' est incorrecte.'); + } + if (req.body.yearOwnership == undefined || req.body.yearOwnership < 0) { + errors.push('La valeur du paramètre \'yearOwnership\' est incorrecte.'); + } + if (errors.length) { + return res.status(422).json({ + errors: { + name: 'ValidationError', + message: 'La validation des données a échoué.', + messages: errors + } + }); + } + return next(); +}); + +router.post('/', function (req, res, next) { + const config = { + palierDetention: 2, + coeff: { + metaux_precieux: 0.115, + jetons: 0.065, + plus_value: 0.362 + } + }; + const params = { + taxationType: req.body.taxationType ? req.body.taxationType : null, + purchasePrice: req.body.purchasePrice ? req.body.purchasePrice : 0, + sellPrice: req.body.sellPrice ? req.body.sellPrice : 0, + yearOwnership: req.body.yearOwnership ? req.body.yearOwnership : 0 + }; + let results = { + capitalGain: 0, + capitalGainCoeff: 0, + lumpSum: 0, + lumpSumCoeff: 0, + isTaxable: false, + bestChoice: null, // valeurs possibles : 'lumpSum', 'gain' or null + messages: [] + } + if(req.body.purchasePrice >= req.body.sellPrice) { + results.messages.push((req.body.purchasePrice > req.body.sellPrice) ? 'Vous êtes dans le cas d\'une moins-value.' : 'Vous êtes dans le cas d\'une plus-value nulle.'); + } + if(req.body.yearOwnership >= 22) { + results.messages.push('Vous êtes dans le cas d\'une vente non imposable (à partir de 22 ans de détention).'); + } + if (params.sellPrice <= 5000) { + if (params.taxationType == 'cours_legaux') { + results.messages.push('Vous êtes dans le cas d\'une vente non imposable sur le cours légal (inférieure à 5000 €).'); + } else if (params.taxationType == 'jetons') { + results.messages.push('Vous êtes dans le cas d\'une vente non imposable sur les jetons (inférieure à 5000 €).'); + } + } + if (results.messages.length) { + results.messages.push('La déclaration est inutile, il n\'y a pas d\'imposition.'); + return res.json({ + params: params, + results: results + }); + } + + results.isTaxable = true; + results.capitalGainCoeff = config.coeff.plus_value; + if (params.yearOwnership > config.palierDetention) { + /* A partir de la deuxième année, abattement de 5% sur le taux appliqué */ + results.capitalGainCoeff = roundNumber((config.coeff.plus_value - (config.coeff.plus_value * (0.05 * (params.yearOwnership - config.palierDetention)))), 4); + results.capitalGain = Math.floor(results.capitalGainCoeff * (params.sellPrice - params.purchasePrice)); + } else { + results.capitalGain = Math.floor(config.coeff.plus_value * (params.sellPrice - params.purchasePrice)); + } + + let value = roundNumber((results.capitalGainCoeff * 100), 2); + results.messages.push(`Le montant du régime des plus-value (${value}%) est de ${results.capitalGain} €.`); + results.bestChoice = 'gain'; + if (params.taxationType != 'cours_legaux') { + results.lumpSum = Math.floor(params.sellPrice * config.coeff[params.taxationType]); + if (results.lumpSum < results.capitalGain) { + results.bestChoice = 'lumpSum'; + } + results.lumpSumCoeff = config.coeff[params.taxationType]; + value = (config.coeff[params.taxationType] * 100 ); + results.messages.push(`Le montant de la taxe forfaitaire (${value}%) est de ${results.lumpSum} €.`); + results.messages.push(`La meilleure option pour vous est ${(results.bestChoice == 'lumpSum') ? 'la taxe forfaitaire' : 'la taxe sur le régime des plus values'}.`); + if (params.taxationType == 'jetons') { + results.messages.push(`À noter : pour une revente de produits de jetons en dessous du seuil des 5000€, aucune taxe n'est applicable.`); + } + } else { + results.messages.push('La taxe forfaitaire n\'est pas applicable pour les produits à cours légaux. Seule la taxe sur le régime des plus-values est applicable.'); + results.messages.push('À noter : pour une revente de produits à cours légaux en dessous du seuil des 5000 €, aucune taxe n\'est applicable.'); + } + + return res.json({ + params: params, + results: results + }); + +}); + +router.all('*', function (req, res, next) { + return res.status(404).json({ + errors: { + name: 'UnknownRouteError', + message: 'La route API demandée est introuvable.' + } + }); +}); + +function roundNumber(num, dec){ + var num_sign = num >= 0 ? 1 : -1; + return parseFloat((Math.round((num * Math.pow(10, dec)) + (num_sign * 0.0001)) / Math.pow(10, dec)).toFixed(dec)); +} + +module.exports = router; \ No newline at end of file diff --git a/routes/api/canopies.js b/routes/api/canopies.js new file mode 100644 index 0000000..677004a --- /dev/null +++ b/routes/api/canopies.js @@ -0,0 +1,58 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Canopy = mongoose.model('Canopy'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../auth'); + + +router.get('/', auth.required, function (req, res, next) { + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + var query = [ + { + '$match': { + '$expr': { + '$eq': [ '$author' , { '$toObjectId': user._id.toString() } ] + } + } + }, { + '$group': { + '_id': { + 'voile': '$voile', + 'taille': '$taille' + }, + 'count': { + '$sum': 1 + } + } + } + ]; + Promise.all([ + Jump.aggregate(query) + .limit(Number(limit)) + .skip(Number(offset)) + .exec() + ]).then(function (results) { + var canopies = results[0]; + return res.json({ + canopies: canopies, + canopiesCount: canopies.length + }); + }).catch(next); + }).catch(next); + +}); + +module.exports = router; diff --git a/routes/api/dropzones.js b/routes/api/dropzones.js new file mode 100644 index 0000000..ebe5fcf --- /dev/null +++ b/routes/api/dropzones.js @@ -0,0 +1,58 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Dropzone = mongoose.model('Dropzone'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../auth'); + + +router.get('/', auth.required, function (req, res, next) { + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + var query = [ + { + '$match': { + '$expr': { + '$eq': [ '$author' , { '$toObjectId': user._id.toString() } ] + } + } + }, { + '$group': { + '_id': { + 'lieu': '$lieu', + 'oaci': '$oaci' + }, + 'count': { + '$sum': 1 + } + } + } + ]; + Promise.all([ + Jump.aggregate(query) + .limit(Number(limit)) + .skip(Number(offset)) + .exec() + ]).then(function (results) { + var dropzones = results[0]; + return res.json({ + dropzones: dropzones, + dropzonesCount: dropzones.length + }); + }).catch(next); + }).catch(next); + +}); + +module.exports = router; diff --git a/routes/api/index.js b/routes/api/index.js new file mode 100644 index 0000000..524529e --- /dev/null +++ b/routes/api/index.js @@ -0,0 +1,28 @@ +var router = require('express').Router(); + +router.use('/', require('./users')); +router.use('/profiles', require('./profiles')); +router.use('/applications', require('./applications')); +router.use('/aeronefs', require('./aeronefs')); +router.use('/canopies', require('./canopies')); +router.use('/dropzones', require('./dropzones')); +router.use('/jumps', require('./jumps')); +/* +router.use('/products', require('./products')); +router.use('/metals', require('./metals')); +router.use('/prices', require('./prices')); +router.use('/calcul', require('./calcul')); +*/ +router.use(function (err, req, res, next) { + if (err.name === 'ValidationError') { + return res.status(422).json({ + errors: Object.keys(err.errors).reduce(function (errors, key) { + errors[key] = err.errors[key].message; + return errors; + }, {}) + }); + } + return next(err); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/api/jumps.js b/routes/api/jumps.js new file mode 100644 index 0000000..2202d5b --- /dev/null +++ b/routes/api/jumps.js @@ -0,0 +1,236 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Jump = mongoose.model('Jump'), + User = mongoose.model('User'); +const auth = require('../auth'); + +// Preload jump objects on routes with ':jump' +router.param('jump', function (req, res, next, slug) { + Jump.findOne({ slug: slug }) + .populate('author') + .then(function (jump) { + if (!jump) { + return res.sendStatus(404); + } + req.jump = jump; + return next(); + }).catch(next); +}); + +router.get('/', auth.required, function (req, res, next) { + var query = {}; + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.title !== 'undefined') { + const rgx = new RegExp(`.*${req.query.title}.*`); + query = { + $or: [ + { title: { $regex: rgx, $options: "i" } }, + { slug: { $regex: rgx, $options: "i" } }, + ], + } + } + + Promise.all([ + req.query.author ? User.findOne({ username: req.query.author }) : null, + req.query.favorited ? User.findOne({ username: req.query.favorited }) : null + ]).then(function (users) { + var author = users[0]; + + if (author) { + query.author = author._id; + } + + return Promise.all([ + Jump.find(query) + .limit(Number(limit)) + .skip(Number(offset)) + .sort({ numero: 'asc' }) + .populate('author') + .exec(), + Jump.count(query).exec(), + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + var jumps = results[0]; + var jumpsCount = results[1]; + var user = results[2]; + + return res.json({ + jumps: jumps.map(function (jump) { + return jump.toJSONFor(user); + }), + jumpsCount: jumpsCount + }); + }); + }).catch(next); +}); + +router.get('/feed', auth.required, function (req, res, next) { + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + var query = { author: { $in: user.following } }; + Promise.all([ + Jump.find(query) + .limit(Number(limit)) + .skip(Number(offset)) + .populate('author') + .exec(), + Jump.count(query) + ]).then(function (results) { + var jumps = results[0]; + var jumpsCount = results[1]; + return res.json({ + jumps: jumps.map(function (jump) { + return jump.toJSONFor(user); + }), + jumpsCount: jumpsCount + }); + }).catch(next); + }); +}); + +router.post('/', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null + /*Jump + .find({}).select('numero') + .sort({"numero" : -1}).limit(1) + .exec()*/ + ]).then(function (results) { + var user = results[0]; + //var numero = (results[1][0].numero + 1); + if (!user) { + return res.sendStatus(401); + } + var jump = new Jump(req.body.jump); + jump.author = user; + //jump.numero = numero; + return jump.save().then(function () { + return res.json({ jump: jump.toJSONFor(user) }); + }); + }).catch(next); +}); + +// return a jump +router.get('/:jump', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null + /*req.jump.populate('author')*/ + ]).then(function (results) { + var user = results[0]; + if (!user) { + return res.sendStatus(401); + } + if (req.jump.author._id.toString() !== req.payload.id.toString()) { + return res.sendStatus(403); + } + return res.json({ jump: req.jump.toJSONFor(user) }); + }).catch(next); +}); + +router.put('/:jump', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + var user = results[0]; + if (!user) { + return res.sendStatus(401); + } + if (req.jump.author._id.toString() !== req.payload.id.toString()) { + return res.sendStatus(403); + } + if (typeof req.body.jump.date !== 'undefined') { + req.jump.date = req.body.jump.date; + } + if (typeof req.body.jump.numero !== 'undefined') { + req.jump.numero = req.body.jump.numero; + } + if (typeof req.body.jump.lieu !== 'undefined') { + req.jump.lieu = req.body.jump.lieu; + } + if (typeof req.body.jump.oaci !== 'undefined') { + req.jump.oaci = req.body.jump.oaci; + } + if (typeof req.body.jump.aeronef !== 'undefined') { + req.jump.aeronef = req.body.jump.aeronef; + } + if (typeof req.body.jump.imat !== 'undefined') { + req.jump.imat = req.body.jump.imat; + } + if (typeof req.body.jump.hauteur !== 'undefined') { + req.jump.hauteur = req.body.jump.hauteur; + } + if (typeof req.body.jump.voile !== 'undefined') { + req.jump.voile = req.body.jump.voile; + } + if (typeof req.body.jump.taille !== 'undefined') { + req.jump.taille = req.body.jump.taille; + } + if (typeof req.body.jump.categorie !== 'undefined') { + req.jump.categorie = req.body.jump.categorie; + } + if (typeof req.body.jump.module !== 'undefined') { + req.jump.module = req.body.jump.module; + } + if (typeof req.body.jump.participants !== 'undefined') { + req.jump.participants = req.body.jump.participants; + } + if (typeof req.body.jump.sautants !== 'undefined') { + req.jump.sautants = req.body.jump.sautants; + } + if (typeof req.body.jump.programme !== 'undefined') { + req.jump.programme = req.body.jump.programme; + } + if (typeof req.body.jump.accessoires !== 'undefined') { + req.jump.accessoires = req.body.jump.accessoires; + } + if (typeof req.body.jump.zone !== 'undefined') { + req.jump.zone = req.body.jump.zone; + } + if (typeof req.body.jump.dossier !== 'undefined') { + req.jump.dossier = req.body.jump.dossier; + } + if (typeof req.body.jump.video !== 'undefined') { + req.jump.video = req.body.jump.video; + } + + return req.jump.save().then(function (jump) { + return res.json({ jump: jump.toJSONFor(user) }); + }); + }).catch(next); +}); + +// delete jump +router.delete('/:jump', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + if (req.jump.author._id.toString() === req.payload.id.toString()) { + return req.jump.remove().then(function () { + return res.sendStatus(204); + }); + } else { + return res.sendStatus(403); + } + }).catch(next); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/api/metals.js b/routes/api/metals.js new file mode 100644 index 0000000..55dae01 --- /dev/null +++ b/routes/api/metals.js @@ -0,0 +1,64 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Metal = mongoose.model('Metal'); +const auth = require('../auth'); + +// Preload metal objects on routes with ':code' +router.param('code', function (req, res, next, code) { + Metal.findOne({ code: code }) + .then(function (metal) { + if (!metal) { + return res.sendStatus(404); + } + req.metal = metal; + return next(); + }).catch(next); +}); + +router.get('/', auth.required, function (req, res, next) { + var query = {}; + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.code !== 'undefined') { + query.code = req.query.code; + } + if (typeof req.query.color !== 'undefined') { + query.color = req.query.color; + } + if (typeof req.query.ordering !== 'undefined') { + query.ordering = req.query.ordering; + } + if (typeof req.query.title !== 'undefined') { + query.title = req.query.title; + } + Promise.all([ + Metal.find(query) + .limit(Number(limit)) + .skip(Number(offset)) + .exec(), + Metal.count(query).exec() + ]).then(function (results) { + var metals = results[0]; + var metalsCount = results[1]; + + return res.json({ + metals: metals.map(function (metal) { + return metal.toJSONFor(); + }), + metalsCount: metalsCount + }); + }).catch(next); +}); + +// return a metal +router.get('/:code', auth.required, function (req, res, next) { + return res.json({ metal: req.metal.toJSONFor() }); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/api/prices.js b/routes/api/prices.js new file mode 100644 index 0000000..8bfb478 --- /dev/null +++ b/routes/api/prices.js @@ -0,0 +1,133 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Metal = mongoose.model('Metal'), + Price = mongoose.model('Price'), + Currency = mongoose.model('Currency'); +const auth = require('../auth'), + utils = require('../utils'), + chalk = require('chalk'); + +// Preload metal objects on routes with ':metal' +router.param('metal', function (req, res, next, code) { + Metal.findOne({ code: code }) + .then(function (metal) { + if (!metal) { + return res.sendStatus(404); + } + req.metal = metal.code; + return next(); + }).catch(next); +}); +// Preload currency objects on routes with ':currency' +router.param('currency', function (req, res, next, code) { + req.currency = code; + Currency.findOne({ code: code }) + .then(function (currency) { + if (!currency) { + return res.sendStatus(404); + } + req.currency = currency.code; + return next(); + }).catch(next); +}); + +router.get('/', auth.required, function (req, res, next) { + var query = {}; + var limit = 20; + var offset = 0; + const fields = ['metal', 'currency', 'exchange', 'symbol']; + + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + fields.forEach(field => { + if (typeof req.query[field] !== 'undefined') { + query[field] = req.query[field]; + } + }); + + Metal.find({}) + .limit(Number(limit)) + .skip(Number(offset)) + .sort({ ordering: 1 }) + .exec() + .then(metals => { + const currency = 'EUR'; + Promise.all( + metals.map(metal => Price.findOne({ metal: metal.code, currency: currency })) + ).then(results => { + let queries$ = []; + var prices = results.map((price, index) => { + if (!price) { + price = new Price({ metal: metals[index].code, currency: currency }); + } + const now = new Date().getTime(); + const diff = parseInt(((now - price.timestamp) / 1000)); + if (diff >= 50) { + queries$.push(utils.fetchApi(price, res).then(result => { + if (result.errors === undefined) { + price.overwrite(result); + price.save(); + console.log(`${utils.getDateTimeISOString()}`, chalk.green(`${price.metal} | ${price.currency} - Sauvegarde du prix spot`), chalk.magenta(price.price)); + } else { + console.log(`${utils.getDateTimeISOString()}`, chalk.red(`${price.metal} | ${price.currency} - Une erreur ${result.errors.code} '${result.errors.type}' est survenue : ${result.errors.name} ${result.errors.message}`)); + } + return price; + })); + } else { + console.log(`${utils.getDateTimeISOString()}`, chalk.cyan(`${price.metal} | ${price.currency} - Utilisation du prix spot`), chalk.magenta(price.price)); + } + return price; + }); + if (queries$.length) { + Promise.all( + queries$ + ).then(() => { + return res.json({ + prices: prices, + pricesCount: prices.length + }); + }).catch(next); + } else { + return res.json({ + prices: prices, + pricesCount: results[2] + }); + } + }).catch(next); + } + ); +}); + +router.get('/:metal/:currency', auth.required, function (req, res, next) { + Price.findOne({ metal: req.metal, currency: req.currency }) + .then(function (price) { + if (!price) { + price = new Price({ metal: req.metal, currency: req.currency }); + } + const now = new Date().getTime(); + const diff = parseInt(((now - price.timestamp) / 1000)); + if (diff >= 50) { + utils.fetchApi(price, res).then(result => { + if (result.errors === undefined) { + price.overwrite(result); + return price.save().then(function (data) { + console.log(`${utils.getDateTimeISOString()}`, chalk.green(`${data.metal} | ${data.currency} - Enregistrement du prix spot`), chalk.magenta(data.price)); + return res.json({ price: data.toJSONFor() }); + }); + } else { + console.log(`${utils.getDateTimeISOString()}`, chalk.red(`${price.metal} | ${price.currency} - Une erreur ${result.errors.code} '${result.errors.type}' est survenue : `), chalk.red(`${result.errors.name} ${result.errors.message}`)); + return res.json({ price: price }); + } + }); + } else { + console.log(`${utils.getDateTimeISOString()}`, chalk.cyan(`${price.metal} | ${price.currency} - Utilisation du prix stocké`), chalk.magenta(price.price)); + return res.json({ price: price.toJSONFor() }); + } + }).catch(next); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/api/products.js b/routes/api/products.js new file mode 100644 index 0000000..92e0f6d --- /dev/null +++ b/routes/api/products.js @@ -0,0 +1,238 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + Product = mongoose.model('Product'), + User = mongoose.model('User'), + Metal = mongoose.model('Metal'); +const auth = require('../auth'); + +// Preload product objects on routes with ':product' +router.param('product', function (req, res, next, slug) { + Product.findOne({ slug: slug }) + .populate('author') + .populate('metal') + .then(function (product) { + if (!product) { + return res.sendStatus(404); + } + req.product = product; + return next(); + }).catch(next); +}); + +router.get('/', auth.required, function (req, res, next) { + var query = {}; + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + if (typeof req.query.title !== 'undefined') { + const rgx = new RegExp(`.*${req.query.title}.*`); + query = { + $or: [ + { title: { $regex: rgx, $options: "i" } }, + { slug: { $regex: rgx, $options: "i" } }, + ], + } + } + + Promise.all([ + req.query.author ? User.findOne({ username: req.query.author }) : null, + req.query.favorited ? User.findOne({ username: req.query.favorited }) : null + ]).then(function (users) { + var author = users[0]; + var favoriter = users[1]; + + if (author) { + query.author = author._id; + } + + if (favoriter) { + query._id = { $in: favoriter.favorites }; + } else if (req.query.favorited) { + query._id = { $in: [] }; + } + + return Promise.all([ + Product.find(query) + .limit(Number(limit)) + .skip(Number(offset)) + .sort({ ordering: 'asc' }) + .populate('author') + .populate('metal') + .exec(), + Product.count(query).exec(), + req.payload ? User.findById(req.payload.id) : null + ]).then(function (results) { + var products = results[0]; + var productsCount = results[1]; + var user = results[2]; + + return res.json({ + products: products.map(function (product) { + return product.toJSONFor(user); + }), + productsCount: productsCount + }); + }); + }).catch(next); +}); + +router.get('/feed', auth.required, function (req, res, next) { + var limit = 20; + var offset = 0; + if (typeof req.query.limit !== 'undefined') { + limit = req.query.limit; + } + if (typeof req.query.offset !== 'undefined') { + offset = req.query.offset; + } + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + var query = { author: { $in: user.following } }; + Promise.all([ + Product.find(query) + .limit(Number(limit)) + .skip(Number(offset)) + .populate('author') + .populate('metal') + .exec(), + Product.count(query) + ]).then(function (results) { + var products = results[0]; + var productsCount = results[1]; + return res.json({ + products: products.map(function (product) { + return product.toJSONFor(user); + }), + productsCount: productsCount + }); + }).catch(next); + }); +}); + +router.post('/', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + Metal.findOne({ code: req.body.product.metal.code }).exec(), + Product + .find({}).select('ordering') + .sort({"ordering" : -1}).limit(1) + .exec() + ]).then(function (results) { + var user = results[0]; + var metal = results[1]; + var ordering = (results[2][0].ordering + 1); + if (!user) { + return res.sendStatus(401); + } + var product = new Product(req.body.product); + product.author = user; + product.metal = metal; + product.ordering = ordering; + return product.save().then(function () { + return res.json({ product: product.toJSONFor(user) }); + }); + }).catch(next); +}); + +// return a product +router.get('/:product', auth.optional, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + req.product.populate('author'), + req.product.populate('metal') + ]).then(function (results) { + var user = results[0]; + return res.json({ product: req.product.toJSONFor(user) }); + }).catch(next); +}); + +router.put('/:product', auth.required, function (req, res, next) { + Promise.all([ + req.payload ? User.findById(req.payload.id) : null, + Metal.findOne({ code: req.body.product.metal.code }).exec() + ]).then(function (results) { + var user = results[0]; + var metal = results[1]; + if (!user) { + return res.sendStatus(401); + } + if (req.product.author._id.toString() !== req.payload.id.toString()) { + return res.sendStatus(403); + } + if (typeof req.body.product.title !== 'undefined') { + req.product.title = req.body.product.title; + } + if (typeof req.body.product.prime_achat !== 'undefined') { + req.product.prime_achat = req.body.product.prime_achat; + } + if (typeof req.body.product.prime_vente !== 'undefined') { + req.product.prime_vente = req.body.product.prime_vente; + } + if (typeof req.body.product.poids !== 'undefined') { + req.product.poids = req.body.product.poids; + } + req.product.metal = metal; + + return req.product.save().then(function (product) { + return res.json({ product: product.toJSONFor(user) }); + }); + }).catch(next); +}); + +// delete product +router.delete('/:product', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + if (req.product.author._id.toString() === req.payload.id.toString()) { + return req.product.remove().then(function () { + return res.sendStatus(204); + }); + } else { + return res.sendStatus(403); + } + }).catch(next); +}); + +// Favorite an product +router.post('/:product/favorite', auth.required, function (req, res, next) { + var productId = req.product._id; + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + + return user.favorite(productId).then(function () { + return req.product.updateFavoriteCount().then(function (product) { + return res.json({ product: product.toJSONFor(user) }); + }); + }); + }).catch(next); +}); + +// Unfavorite an product +router.delete('/:product/favorite', auth.required, function (req, res, next) { + var productId = req.product._id; + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + + return user.unfavorite(productId).then(function () { + return req.product.updateFavoriteCount().then(function (product) { + return res.json({ product: product.toJSONFor(user) }); + }); + }); + }).catch(next); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/api/profiles.js b/routes/api/profiles.js new file mode 100644 index 0000000..078356b --- /dev/null +++ b/routes/api/profiles.js @@ -0,0 +1,57 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + User = mongoose.model('User'); +const auth = require('../auth'); + +// Preload user profile on routes with ':username' +router.param('username', function (req, res, next, username) { + User.findOne({ username: username }).then(function (user) { + if (!user) { + return res.sendStatus(404); + } + req.profile = user; + + return next(); + }).catch(next); +}); + +router.get('/:username', auth.optional, function (req, res, next) { + if (req.payload) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.json({ profile: req.profile.toProfileJSONFor(false) }); + } + return res.json({ profile: req.profile.toProfileJSONFor(user) }); + }); + } else { + return res.json({ profile: req.profile.toProfileJSONFor(false) }); + } +}); + +router.post('/:username/follow', auth.required, function (req, res, next) { + var profileId = req.profile._id; + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + return user.follow(profileId).then(function () { + return res.json({ profile: req.profile.toProfileJSONFor(user) }); + }); + }).catch(next); +}); + +router.delete('/:username/follow', auth.required, function (req, res, next) { + var profileId = req.profile._id; + + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + return user.unfollow(profileId).then(function () { + return res.json({ profile: req.profile.toProfileJSONFor(user) }); + }); + }).catch(next); +}); + +module.exports = router; diff --git a/routes/api/users.js b/routes/api/users.js new file mode 100644 index 0000000..4108f35 --- /dev/null +++ b/routes/api/users.js @@ -0,0 +1,117 @@ +var router = require('express').Router(), + mongoose = require('mongoose'), + User = mongoose.model('User'); +const passport = require('passport'), + auth = require('../auth'); + +router.get('/user', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + return res.json({ user: user.toAuthJSON() }); + }).catch(next); +}); + +router.put('/user', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { + return res.sendStatus(401); + } + if (typeof req.body.user.username !== 'undefined') { + user.username = req.body.user.username; + } + if (typeof req.body.user.email !== 'undefined') { + user.email = req.body.user.email; + } + if (typeof req.body.user.firstname !== 'undefined') { + user.firstname = req.body.user.firstname; + } + if (typeof req.body.user.lastname !== 'undefined') { + user.lastname = req.body.user.lastname; + } + if (typeof req.body.user.phone !== 'undefined') { + user.phone = req.body.user.phone; + } + if (typeof req.body.user.licence !== 'undefined') { + user.licence = req.body.user.licence; + } + if (typeof req.body.user.image !== 'undefined') { + user.image = req.body.user.image; + } + if (typeof req.body.user.bg_image !== 'undefined') { + user.bg_image = req.body.user.bg_image; + } + if (typeof req.body.user.password !== 'undefined') { + user.setPassword(req.body.user.password); + } + if (typeof req.body.user.role !== 'undefined') { + user.role = req.body.user.role; + } + + return user.save().then(function () { + return res.json({ user: user.toAuthJSON() }); + }); + }).catch(next); +}); + +router.post('/users/login', function (req, res, next) { + if (!req.body.user.email) { + return res.status(422).json({ errors: { email: "can't be blank" } }); + } + if (!req.body.user.password) { + return res.status(422).json({ errors: { password: "can't be blank" } }); + } + passport.authenticate('local', { session: false }, function (err, user, info) { + if (err) { + return next(err); + } + if (user) { + user.token = user.generateJWT(); + return res.json({ user: user.toAuthJSON() }); + } else { + return res.status(422).json(info); + } + })(req, res, next); +}); + +router.get('/authenticate', function (req, res, next) { + passport.authenticate('headerapikey', { session: false }, function (err, application, info) { + if (err) { + return res.status(err.status || 500).json({message: "An authentication error occured.", err: err.message}); + //return next(err); + } + if (!application) { + return res.status(401).json({message: "Unauthorized - You are not allowed to access this resource.", err: err, info: info}); + } + if (application.author) { + application.author.token = application.author.generateJWT(); + return res.json({ user: application.author.toAuthJSON(), application: application.toAuthJSON() }); + } else { + return res.status(422).json(info); + } + })(req, res, next); +}); +router.post('/users', function (req, res, next) { + var user = new User(); + if (typeof req.body.user.username !== 'undefined') { + user.username = req.body.user.username; + } else { + user.username = `${req.body.user.firstname}_${req.body.user.lastname}`; + } + user.email = req.body.user.email; + user.firstname = req.body.user.firstname; + user.lastname = req.body.user.lastname; + user.phone = req.body.user.phone; + if (typeof req.body.user.licence !== 'undefined') { + user.licence = req.body.user.licence; + } + user.setPassword(req.body.user.password); + user.role = 'User'; // valeurs possibles : 'User' or 'Admin' + + user.save().then(function () { + return res.json({ user: user.toAuthJSON() }); + }).catch(next); +}); + +module.exports = router; diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..9d70923 --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,83 @@ + +//var jwt = require('express-jwt'); +var { expressjwt: jwt } = require("express-jwt"), + secret = require('../config').secret, + bcrypt = require('bcrypt'); +const { v4 } = require('uuid'), + passport = require('passport'), + auths = ['Api-Key', 'Basic', 'Bearer', 'Token']; + +function getTokenFromHeader(req) { + if(req.headers.authorization && auths.indexOf(req.headers.authorization.split(' ')[0]) !== -1) { + return req.headers.authorization.split(' ')[1]; + } + return null; +} + +const auth = { + requiredJwt: jwt({ + secret: secret, + algorithms: ['HS256'], + requestProperty: 'payload', + getToken: getTokenFromHeader + }), + optional: jwt({ + secret: secret, + algorithms: ['HS256'], + requestProperty: 'payload', + credentialsRequired: false, + getToken: getTokenFromHeader + }), + required: async (req, res, next) => { + if(req.headers.authorization && req.headers.authorization.split(' ')[0] === process.env.AUTHORIZATION_PREFIX) { + passport.authenticate('headerapikey', { session: false }, function (err, application, info) { + if (err) { + //return res.json({message: "An authentication error occured (required).", err: err.message}); + //return res.status(err.status || 500).json({message: "An authentication error occured (required).", err: err.message}); + return next({message: "An authentication error occured.", err: err.message}); + } + if (!application) { + return res.status(401).json({message: "Unauthorized - You are not allowed to access this resource.", err: err, info: info}); + } + req.application = application; + return next(); + })(req, res, next); + } else { + return jwt({ + secret: secret, + algorithms: ['HS256'], + requestProperty: 'payload', + getToken: getTokenFromHeader + })(req, res, next); + } + }, + getAuthType: (req) => { + if(req.headers.authorization && auths.indexOf(req.headers.authorization.split(' ')[0]) !== -1) { + let value = req.headers.authorization.split(' ')[0]; + return value; + } + return null; + }, + generateAPIKey: async () => { + const key = v4().replace(/-/g, ''); + const salt = secret; + // Génération d'un salt aléatoire : const salt = await Promise.resolve(bcrypt.genSalt(10)); + const maskedKey = `${key.slice(0, 4)}...${key.slice(-4)}`; + const encryptedKey = await Promise.resolve(bcrypt.hash(key, salt)); + return { key: key, masked: maskedKey, encrypted: encryptedKey}; + }, + authenticateKey: async (req, res, next) => { + passport.authenticate('headerapikey', { session: false }, function (err, application, info) { + if (err) { + return res.status(err.status || 500).json({message: "An authentication error occured (authenticateKey).", err: err.message}); + } + if (!application) { + return res.status(401).json({message: "Unauthorized - You are not allowed to access this resource.", err: err, info: info}); + } + req.application = application; + return next(); + })(req, res, next); + } +}; + +module.exports = auth; diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..470acba --- /dev/null +++ b/routes/index.js @@ -0,0 +1,4 @@ +var router = require('express').Router(); +router.use('/api', require('./api')); + +module.exports = router; diff --git a/routes/utils.js b/routes/utils.js new file mode 100644 index 0000000..36a15e3 --- /dev/null +++ b/routes/utils.js @@ -0,0 +1,68 @@ +const chalk = require('chalk'); +const nodeFetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); + +const utils = { + getDateString: () => { + let today = new Date(); + let dateNow = today.toLocaleDateString(); + + return `${dateNow}`; + }, + getTimeString: () => { + let today = new Date(); + let timeNow = today.toLocaleTimeString(); + + return `${timeNow}`; + }, + getDateTimeString: (delimiter = true) => { + let today = new Date(); + let dateString = `${today.toLocaleDateString()} ${today.toLocaleTimeString()}`; + if (delimiter) { + dateString = `[${dateString}]`; + } + + return dateString; + }, + getDateTimeISOString: (delimiter = true) => { + let today = new Date(); + let dateString = today.toISOString(); + if (delimiter) { + dateString = `[${dateString}]`; + } + + return dateString; + }, + async fetchApi(price, next) { + let url = `${process.env.GOLDAPI_URL}/${price.metal}/${price.currency}`; + // url pour provoquer une erreur en dev : url = `https://www.goldapi.ioppppp/api/${price.metal}/${price.currency}`; + let options = { + method: 'GET', + headers: { + 'x-access-token': process.env.GOLDAPI_TOKEN, + 'Content-Type': 'application/json' + }, + redirect: 'follow' + }; + try { + // Pour tester le retour d'erreur : return { errors: { name: 'error name', message: 'error message', type: 'TYPE', code: 'CODE' } }; + const response = await nodeFetch(url, options); + if (response.status >= 200 && response.status < 400) { + console.log(`${utils.getDateTimeISOString()}`, chalk.green(`${price.metal} | ${price.currency} - Réception du prix spot via API`), chalk.magenta(price.price), `${response.status} ${response.statusText}`); + price = response.json(); + price.then(values => { + values.timestamp *= 1000; + return values; + }); + } else { + console.log(`${utils.getDateTimeISOString()}`, chalk.red(`${price.metal} | ${price.currency} - Erreur ${response.status} ${response.statusText} - Réutilisation du prix stocké`), chalk.magenta(price.price)); + price = { errors: { name: response.statusText, message: `Une erreur ${response.status} '${response.statusText}' est survenue lors de la récupération des prix spots`, type: 'API Error', code: response.status } }; + } + return price; + } catch (error) { + console.log(`${utils.getDateTimeISOString()}`, chalk.red(`${price.metal} | ${price.currency} - fetchApi error`)); + return { errors: { name: error.name, message: error.message, type: error.type, code: error.code } }; + } + } +}; + +module.exports = utils; diff --git a/tests/api-tests.postman.json b/tests/api-tests.postman.json new file mode 100644 index 0000000..161725a --- /dev/null +++ b/tests/api-tests.postman.json @@ -0,0 +1,2015 @@ +{ + "variables": [], + "info": { + "name": "Head Up API Tests", + "_postman_id": "dda3e595-02d7-bf12-2a43-3daea0970192", + "description": "Collection for testing the Head Up API\n\nhttps://api.rampeur.com/", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "Auth", + "description": "", + "item": [ + { + "name": "Register", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if (!(environment.isIntegrationTest)) {", + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"user\" property'] = responseJSON.hasOwnProperty('user');", + "", + "var user = responseJSON.user || {};", + "", + "tests['User has \"email\" property'] = user.hasOwnProperty('email');", + "tests['User has \"username\" property'] = user.hasOwnProperty('username');", + "tests['User has \"token\" property'] = user.hasOwnProperty('token');", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/users", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"user\":{\"email\":\"anne@honime.com\", \"password\":\"annehonimepwd\", \"username\":\"annehonime\"}}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Login", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"user\" property'] = responseJSON.hasOwnProperty('user');", + "", + "var user = responseJSON.user || {};", + "", + "tests['User has \"email\" property'] = user.hasOwnProperty('email');", + "tests['User has \"username\" property'] = user.hasOwnProperty('username');", + "tests['User has \"token\" property'] = user.hasOwnProperty('token');", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/users/login", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"user\":{\"email\":\"anne@honime.com\", \"password\":\"annehonimepwd\"}}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Login and Remember Token", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"user\" property'] = responseJSON.hasOwnProperty('user');", + "", + "var user = responseJSON.user || {};", + "", + "tests['User has \"email\" property'] = user.hasOwnProperty('email');", + "tests['User has \"username\" property'] = user.hasOwnProperty('username');", + "tests['User has \"token\" property'] = user.hasOwnProperty('token');", + "", + "if(tests['User has \"token\" property']){", + " postman.setEnvironmentVariable('token', user.token);", + "}", + "", + "tests['Environment variable \"token\" has been set'] = environment.token === user.token;", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/users/login", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"user\":{\"email\":\"anne@honime.com\", \"password\":\"annehonimepwd\"}}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Current User", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"user\" property'] = responseJSON.hasOwnProperty('user');", + "", + "var user = responseJSON.user || {};", + "", + "tests['User has \"email\" property'] = user.hasOwnProperty('email');", + "tests['User has \"username\" property'] = user.hasOwnProperty('username');", + "tests['User has \"token\" property'] = user.hasOwnProperty('token');", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/user", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + }, + { + "name": "Update User", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"user\" property'] = responseJSON.hasOwnProperty('user');", + "", + "var user = responseJSON.user || {};", + "", + "tests['User has \"email\" property'] = user.hasOwnProperty('email');", + "tests['User has \"username\" property'] = user.hasOwnProperty('username');", + "tests['User has \"token\" property'] = user.hasOwnProperty('token');", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/user", + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"user\":{\"email\":\"anne@honime.com\"}}" + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Articles with authentication", + "description": "", + "item": [ + { + "name": "Feed", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/feed", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "All Articles", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "All Articles with auth", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Articles by Author", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?author=annehonime", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "author", + "value": "annehonime" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Articles by Author with auth", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?author=annehonime", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "author", + "value": "annehonime", + "equals": true, + "description": "" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + }, + { + "name": "Articles Favorited by Username", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + " ", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?favorited=jane", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "favorited", + "value": "jane" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Articles Favorited by Username with auth", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + " ", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?favorited=jane", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "favorited", + "value": "jane" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Articles by Tag", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?tag=octopus", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "tag", + "value": "octopus" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Create Article", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"article\" property'] = responseJSON.hasOwnProperty('article');", + "", + "var article = responseJSON.article || {};", + "", + "tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + "tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + "if(tests['Article has \"slug\" property']){", + " postman.setEnvironmentVariable('slug', article.slug);", + "}", + "tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + "tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + "tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + "tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + "tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + "tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + "tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + "tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + "tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + "tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + "tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + "tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"article\":{\"title\":\"How to tame an octopus\", \"description\":\"Ever wonder how?\", \"body\":\"Very carefully.\", \"tagList\":[\"octopus\",\"training\"]}}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Single Article by slug", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"article\" property'] = responseJSON.hasOwnProperty('article');", + "", + "var article = responseJSON.article || {};", + "", + "tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + "tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + "tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + "tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + "tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + "tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + "tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + "tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + "tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + "tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + "tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + "tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + "tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + "tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/{{slug}}", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + }, + { + "name": "Update Article", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if (!(environment.isIntegrationTest)) {", + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"article\" property'] = responseJSON.hasOwnProperty('article');", + "", + "var article = responseJSON.article || {};", + "", + "tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + "tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + "tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + "tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + "tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + "tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + "tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + "tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + "tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + "tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + "tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + "tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + "tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + "tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/{{slug}}", + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"article\":{\"body\":\"With two hands\"}}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Favorite Article", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"article\" property'] = responseJSON.hasOwnProperty('article');", + "", + "var article = responseJSON.article || {};", + "", + "tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + "tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + "tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + "tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + "tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + "tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + "tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + "tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + "tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + "tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + "tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + "tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + "tests[\"Article's 'favorited' property is true\"] = article.favorited === true;", + "tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + "tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + "tests[\"Article's 'favoritesCount' property is greater than 0\"] = article.favoritesCount > 0;", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/{{slug}}/favorite", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Unfavorite Article", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"article\" property'] = responseJSON.hasOwnProperty('article');", + "", + "var article = responseJSON.article || {};", + "", + "tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + "tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + "tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + "tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + "tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + "tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + "tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + "tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + "tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + "tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + "tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + "tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + "tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + "tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + "tests[\"Article's \\\"favorited\\\" property is true\"] = article.favorited === false;", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/{{slug}}/favorite", + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Articles", + "description": "", + "item": [ + { + "name": "All Articles", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Articles by Author", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?author=annehonime", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "author", + "value": "annehonime" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Articles Favorited by Username", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + " ", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?favorited=jane", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "favorited", + "value": "jane" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Articles by Tag", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"articles\" property'] = responseJSON.hasOwnProperty('articles');", + " tests['Response contains \"articlesCount\" property'] = responseJSON.hasOwnProperty('articlesCount');", + " tests['articlesCount is an integer'] = Number.isInteger(responseJSON.articlesCount);", + "", + " if(responseJSON.articles.length){", + " var article = responseJSON.articles[0];", + "", + " tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + " tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + " tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + " tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + " tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + " tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + " tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + " tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + " tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + " tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + " tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + " tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + " tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + " tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + " } else {", + " tests['articlesCount is 0 when feed is empty'] = responseJSON.articlesCount === 0;", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": { + "raw": "{{apiUrl}}/articles?tag=octopus", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "articles" + ], + "query": [ + { + "key": "tag", + "value": "octopus" + } + ], + "variable": [] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Single Article by slug", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"article\" property'] = responseJSON.hasOwnProperty('article');", + "", + "var article = responseJSON.article || {};", + "", + "tests['Article has \"title\" property'] = article.hasOwnProperty('title');", + "tests['Article has \"slug\" property'] = article.hasOwnProperty('slug');", + "tests['Article has \"body\" property'] = article.hasOwnProperty('body');", + "tests['Article has \"createdAt\" property'] = article.hasOwnProperty('createdAt');", + "tests['Article\\'s \"createdAt\" property is an ISO 8601 timestamp'] = new Date(article.createdAt).toISOString() === article.createdAt;", + "tests['Article has \"updatedAt\" property'] = article.hasOwnProperty('updatedAt');", + "tests['Article\\'s \"updatedAt\" property is an ISO 8601 timestamp'] = new Date(article.updatedAt).toISOString() === article.updatedAt;", + "tests['Article has \"description\" property'] = article.hasOwnProperty('description');", + "tests['Article has \"tagList\" property'] = article.hasOwnProperty('tagList');", + "tests['Article\\'s \"tagList\" property is an Array'] = Array.isArray(article.tagList);", + "tests['Article has \"author\" property'] = article.hasOwnProperty('author');", + "tests['Article has \"favorited\" property'] = article.hasOwnProperty('favorited');", + "tests['Article has \"favoritesCount\" property'] = article.hasOwnProperty('favoritesCount');", + "tests['favoritesCount is an integer'] = Number.isInteger(article.favoritesCount);", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/{{slug}}", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Comments", + "description": "", + "item": [ + { + "name": "All Comments for Article", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"comments\" property'] = responseJSON.hasOwnProperty('comments');", + "", + " if(responseJSON.comments.length){", + " var comment = responseJSON.comments[0];", + "", + " tests['Comment has \"id\" property'] = comment.hasOwnProperty('id');", + " tests['Comment has \"body\" property'] = comment.hasOwnProperty('body');", + " tests['Comment has \"createdAt\" property'] = comment.hasOwnProperty('createdAt');", + " tests['\"createdAt\" property is an ISO 8601 timestamp'] = new Date(comment.createdAt).toISOString() === comment.createdAt;", + " tests['Comment has \"updatedAt\" property'] = comment.hasOwnProperty('updatedAt');", + " tests['\"updatedAt\" property is an ISO 8601 timestamp'] = new Date(comment.updatedAt).toISOString() === comment.updatedAt;", + " tests['Comment has \"author\" property'] = comment.hasOwnProperty('author');", + " }", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/{{slug}}/comments", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + }, + { + "name": "Create Comment for Article", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON = JSON.parse(responseBody);", + "", + "tests['Response contains \"comment\" property'] = responseJSON.hasOwnProperty('comment');", + "", + "var comment = responseJSON.comment || {};", + "", + "tests['Comment has \"id\" property'] = comment.hasOwnProperty('id');", + "tests['Comment has \"body\" property'] = comment.hasOwnProperty('body');", + "tests['Comment has \"createdAt\" property'] = comment.hasOwnProperty('createdAt');", + "tests['\"createdAt\" property is an ISO 8601 timestamp'] = new Date(comment.createdAt).toISOString() === comment.createdAt;", + "tests['Comment has \"author\" property'] = comment.hasOwnProperty('author');", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/articles/{{slug}}/comments", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"comment\":{\"body\":\"Thank you so much!\"}}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Delete Comment for Article", + "request": { + "url": "{{apiUrl}}/articles/{{slug}}/comments/1", + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Profiles", + "description": "", + "item": [ + { + "name": "Profile", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if (!(environment.isIntegrationTest)) {", + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"profile\" property'] = responseJSON.hasOwnProperty('profile');", + " ", + " var profile = responseJSON.profile || {};", + " ", + " tests['Profile has \"username\" property'] = profile.hasOwnProperty('username');", + " tests['Profile has \"image\" property'] = profile.hasOwnProperty('image');", + " tests['Profile has \"following\" property'] = profile.hasOwnProperty('following');", + "}", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/profiles/annehonime", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + }, + { + "name": "Follow Profile", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if (!(environment.isIntegrationTest)) {", + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"profile\" property'] = responseJSON.hasOwnProperty('profile');", + " ", + " var profile = responseJSON.profile || {};", + " ", + " tests['Profile has \"username\" property'] = profile.hasOwnProperty('username');", + " tests['Profile has \"image\" property'] = profile.hasOwnProperty('image');", + " tests['Profile has \"following\" property'] = profile.hasOwnProperty('following');", + " tests['Profile\\'s \"following\" property is true'] = profile.following === true;", + "}", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/profiles/annehonime/follow", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\"user\":{\"email\":\"anne@honime.com\"}}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Unfollow Profile", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if (!(environment.isIntegrationTest)) {", + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + "", + " tests['Response contains \"profile\" property'] = responseJSON.hasOwnProperty('profile');", + " ", + " var profile = responseJSON.profile || {};", + " ", + " tests['Profile has \"username\" property'] = profile.hasOwnProperty('username');", + " tests['Profile has \"image\" property'] = profile.hasOwnProperty('image');", + " tests['Profile has \"following\" property'] = profile.hasOwnProperty('following');", + " tests['Profile\\'s \"following\" property is false'] = profile.following === false;", + "}", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/profiles/annehonime/follow", + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Tags", + "description": "", + "item": [ + { + "name": "All Tags", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var is200Response = responseCode.code === 200;", + "", + "tests['Response code is 200 OK'] = is200Response;", + "", + "if(is200Response){", + " var responseJSON = JSON.parse(responseBody);", + " ", + " tests['Response contains \"tags\" property'] = responseJSON.hasOwnProperty('tags');", + " tests['\"tags\" property returned as array'] = Array.isArray(responseJSON.tags);", + "}", + "" + ] + } + } + ], + "request": { + "url": "{{apiUrl}}/tags", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Cleanup", + "description": "", + "item": [ + { + "name": "Delete Article", + "request": { + "url": "{{apiUrl}}/articles/{{slug}}", + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "X-Requested-With", + "value": "XMLHttpRequest", + "description": "" + }, + { + "key": "Authorization", + "value": "Token {{token}}", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/env-api-tests.postman.json b/tests/env-api-tests.postman.json new file mode 100644 index 0000000..4b41cc7 --- /dev/null +++ b/tests/env-api-tests.postman.json @@ -0,0 +1,16 @@ +{ + "id": "4aa60b52-97fc-456d-4d4f-14a350e95dff", + "name": "Head Up API Tests - Environment", + "values": [ + { + "enabled": true, + "key": "apiUrl", + "value": "http://localhost:3200/api", + "type": "text" + } + ], + "timestamp": 1505871382668, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2017-09-20T01:36:34.835Z", + "_postman_exported_using": "Postman/5.2.0" +} \ No newline at end of file