├── exercises
├── extend
│ ├── exercise.js
│ ├── solution
│ │ └── solution.js
│ └── problem.md
├── static_serving
│ ├── exercise.js
│ ├── solution
│ │ └── solution.js
│ └── problem.md
├── uploads
│ ├── solution
│ │ ├── input.txt
│ │ └── solution.js
│ ├── solution_fr
│ │ ├── input.txt
│ │ └── solution.js
│ ├── exercise.js
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.md
│ └── problem.fr.md
├── streams
│ ├── solution
│ │ ├── input.txt
│ │ └── solution.js
│ ├── exercise.js
│ ├── problem.ja.md
│ ├── problem.ko.md
│ ├── problem.md
│ └── problem.fr.md
├── handling
│ ├── solution
│ │ ├── index.html
│ │ └── solution.js
│ ├── exercise.js
│ ├── solution_fr
│ │ ├── index.html
│ │ └── solution.js
│ ├── problem.ko.md
│ ├── problem.fr.md
│ ├── problem.ja.md
│ └── problem.md
├── helping
│ ├── solution
│ │ ├── helpers
│ │ │ └── helper.js
│ │ ├── templates
│ │ │ └── template.html
│ │ └── solution.js
│ ├── solution_fr
│ │ ├── helpers
│ │ │ └── helper.js
│ │ ├── templates
│ │ │ └── template.html
│ │ └── solution.js
│ ├── exercise.js
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.md
│ └── problem.fr.md
├── directories
│ ├── solution
│ │ ├── public
│ │ │ └── file.html
│ │ └── solution.js
│ ├── solution_fr
│ │ ├── public
│ │ │ └── file.html
│ │ └── solution.js
│ ├── exercise.js
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.md
│ └── problem.fr.md
├── hello_hapi
│ ├── exercise.js
│ ├── solution
│ │ └── solution.js
│ ├── solution_fr
│ │ └── solution.js
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.md
│ └── problem.fr.md
├── routes
│ ├── exercise.js
│ ├── problem.ja.md
│ ├── problem.ko.md
│ ├── solution_ko
│ │ └── solution.js
│ ├── problem.md
│ ├── solution_ja
│ │ └── solution.js
│ ├── solution
│ │ └── solution.js
│ ├── problem.fr.md
│ └── solution_fr
│ │ └── solution.js
├── views
│ ├── solution
│ │ ├── templates
│ │ │ └── index.html
│ │ └── solution.js
│ ├── solution_fr
│ │ ├── templates
│ │ │ └── index.html
│ │ └── solution.js
│ ├── exercise.js
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.fr.md
│ └── problem.md
├── validation
│ ├── exercise.js
│ ├── solution_fr
│ │ └── solution.js
│ ├── solution
│ │ └── solution.js
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.md
│ └── problem.fr.md
├── menu.json
├── auth
│ ├── exercise.js
│ ├── solution
│ │ └── solution.js
│ ├── problem.ja.md
│ ├── problem.ko.md
│ └── problem.md
├── cookies
│ ├── exercise.js
│ ├── solution
│ │ └── solution.js
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.fr.md
│ └── problem.md
├── proxies
│ ├── solution
│ │ └── solution.js
│ ├── problem.fr.md
│ ├── problem.ko.md
│ ├── problem.ja.md
│ ├── problem.md
│ └── exercise.js
└── validation_using_joi_object
│ ├── exercise.js
│ ├── solution
│ └── solution.js
│ ├── solution_fr
│ └── solution.js
│ ├── problem.ja.md
│ ├── problem.ko.md
│ ├── problem.md
│ └── problem.fr.md
├── bin
└── makemehapi
├── images
└── makemehapi.png
├── lib
├── utils.js
├── verifyStatusProcessor.js
└── exercise.js
├── i18n
├── credits
│ ├── ja.txt
│ ├── ko.txt
│ ├── br.txt
│ ├── en.txt
│ └── fr.txt
├── ko.json
├── en.json
├── br.json
├── ja.json
└── fr.json
├── .workshopper-test.config.js
├── .github
├── dependabot.yml
└── workflows
│ └── master.yml
├── .gitignore
├── credits.txt
├── AUTHORS
├── .gitattributes
├── .eslintrc.js
├── index.js
├── credits.js
├── README.md
├── CONTRIBUTING.md
├── CHANGELOG.md
├── LICENSE
└── package.json
/exercises/extend/exercise.js:
--------------------------------------------------------------------------------
1 | // extend
2 |
--------------------------------------------------------------------------------
/exercises/extend/solution/solution.js:
--------------------------------------------------------------------------------
1 | // extend
2 |
--------------------------------------------------------------------------------
/exercises/static_serving/exercise.js:
--------------------------------------------------------------------------------
1 | // static server
2 |
--------------------------------------------------------------------------------
/exercises/uploads/solution/input.txt:
--------------------------------------------------------------------------------
1 | We like 'makemehapi'.
--------------------------------------------------------------------------------
/exercises/streams/solution/input.txt:
--------------------------------------------------------------------------------
1 | The Pursuit of Hapi-ness
2 |
--------------------------------------------------------------------------------
/exercises/uploads/solution_fr/input.txt:
--------------------------------------------------------------------------------
1 | On adore « makemehapi ».
2 |
--------------------------------------------------------------------------------
/exercises/static_serving/solution/solution.js:
--------------------------------------------------------------------------------
1 | // setup a static server
2 |
--------------------------------------------------------------------------------
/exercises/static_serving/problem.md:
--------------------------------------------------------------------------------
1 | static directories, using depth on a configuration
--------------------------------------------------------------------------------
/bin/makemehapi:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require('../index').execute(process.argv.slice(2))
4 |
--------------------------------------------------------------------------------
/images/makemehapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccarruitero/makemehapi/HEAD/images/makemehapi.png
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | const rndport = () => 1024 + Math.floor(Math.random() * 64511);
2 |
3 | module.exports = {
4 | rndport
5 | };
6 |
--------------------------------------------------------------------------------
/i18n/credits/ja.txt:
--------------------------------------------------------------------------------
1 | {yellow}{bold}makemehapi は以下のメンバーがお届けいたしました。:{/bold}{/yellow}
2 | {bold}Name GitHub Username{/bold}
3 |
--------------------------------------------------------------------------------
/i18n/credits/ko.txt:
--------------------------------------------------------------------------------
1 | {yellow}{bold}makemehapi는 아래 구성원들의 공헌으로 만들어졌습니다.{/bold}{/yellow}
2 | {bold}Name GitHub Username{/bold}
3 |
--------------------------------------------------------------------------------
/.workshopper-test.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | exercisesFolder: 'exercises',
3 | files: 'solution/*.*',
4 | testFileRegex: 'solution.js'
5 | }
6 |
--------------------------------------------------------------------------------
/i18n/credits/br.txt:
--------------------------------------------------------------------------------
1 | {yellow}{bold}makemehapi é trazido a você pelas seguintes pessoas:{/bold}{/yellow}
2 | {bold}Nome Usuário GitHub{/bold}
3 |
--------------------------------------------------------------------------------
/i18n/credits/en.txt:
--------------------------------------------------------------------------------
1 | {yellow}{bold}makemehapi is brought to you by the following persons:{/bold}{/yellow}
2 | {bold}Name GitHub Username{/bold}
3 |
--------------------------------------------------------------------------------
/i18n/credits/fr.txt:
--------------------------------------------------------------------------------
1 | {yellow}{bold}makemehapi vous est proposé par les personnes suivantes :{/bold}{/yellow}
2 | {bold}Nom Compte GitHub{/bold}
3 |
--------------------------------------------------------------------------------
/exercises/handling/solution/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Hello Handling
3 |
4 | Hello Handling
5 |
6 |
7 |
--------------------------------------------------------------------------------
/exercises/helping/solution/helpers/helper.js:
--------------------------------------------------------------------------------
1 | module.exports = function (context) {
2 | const { query } = context.data.root;
3 | return query.name + query.suffix;
4 | };
5 |
--------------------------------------------------------------------------------
/exercises/helping/solution_fr/helpers/helper.js:
--------------------------------------------------------------------------------
1 | module.exports = function (context) {
2 | const { query } = context.data.root;
3 | return query.name + query.suffix;
4 | };
5 |
--------------------------------------------------------------------------------
/exercises/directories/solution/public/file.html:
--------------------------------------------------------------------------------
1 |
2 | Hello Directories
3 |
4 | Hello Directories
5 |
6 |
7 |
--------------------------------------------------------------------------------
/exercises/handling/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => `http://localhost:${port}/`;
4 |
5 | module.exports = exercise;
6 |
--------------------------------------------------------------------------------
/exercises/hello_hapi/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => `http://localhost:${port}/`;
4 |
5 | module.exports = exercise;
6 |
--------------------------------------------------------------------------------
/exercises/helping/solution/templates/template.html:
--------------------------------------------------------------------------------
1 |
2 | Hello {{helper}}
3 |
4 | Hello {{helper}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/exercises/routes/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => `http://localhost:${port}/world`;
4 |
5 | module.exports = exercise;
6 |
--------------------------------------------------------------------------------
/exercises/helping/solution_fr/templates/template.html:
--------------------------------------------------------------------------------
1 |
2 | Bonjour {{helper}}
3 |
4 | Bonjour {{helper}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/exercises/views/solution/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 | Hello {{query.name}}
3 |
4 | Hello {{query.name}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/exercises/directories/solution_fr/public/file.html:
--------------------------------------------------------------------------------
1 |
2 | Salut les répertoires
3 |
4 | Salut les répertoires
5 |
6 |
7 |
--------------------------------------------------------------------------------
/exercises/views/solution_fr/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 | Bonjour {{query.name}}
3 |
4 | Bonjour {{query.name}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/exercises/validation/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => `http://localhost:${port}/chickens/foo`;
4 |
5 | module.exports = exercise;
6 |
--------------------------------------------------------------------------------
/exercises/directories/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => `http://localhost:${port}/foo/bar/baz/file.html`;
4 |
5 | module.exports = exercise;
6 |
--------------------------------------------------------------------------------
/exercises/handling/solution_fr/index.html:
--------------------------------------------------------------------------------
1 |
2 | Coucou du gestionnaire
3 |
4 | Salut, je suis servi grâce au gestionnaire
5 |
6 |
7 |
--------------------------------------------------------------------------------
/exercises/views/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => `http://localhost:${port}/?name=${exercise.__('name_param')}`;
4 |
5 | module.exports = exercise;
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 | npm-debug.log
4 | dump.rdb
5 | node_modules
6 | results.tap
7 | results.xml
8 | npm-shrinkwrap.json
9 | .DS_Store
10 | */.DS_Store
11 | */*/.DS_Store
12 | ._*
13 | */._*
14 | */*/._*
15 | package-lock.json
16 |
--------------------------------------------------------------------------------
/exercises/menu.json:
--------------------------------------------------------------------------------
1 | [
2 | "HELLO_HAPI",
3 | "ROUTES",
4 | "HANDLING",
5 | "DIRECTORIES",
6 | "VIEWS",
7 | "PROXIES",
8 | "HELPING",
9 | "STREAMS",
10 | "VALIDATION",
11 | "VALIDATION USING JOI OBJECT",
12 | "UPLOADS",
13 | "COOKIES",
14 | "AUTH"
15 | ]
16 |
--------------------------------------------------------------------------------
/exercises/helping/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => {
4 | const nameParam = encodeURIComponent(exercise.__('name_param'));
5 | return `http://localhost:${port}/?name=${nameParam}`;
6 | };
7 |
8 | module.exports = exercise;
9 |
--------------------------------------------------------------------------------
/credits.txt:
--------------------------------------------------------------------------------
1 | ------------------------------------
2 | Ben Acker @nvcexploder
3 | Lloyd Benson @lloydbenson
4 | Lin Clark @linclark
5 | Chetan Dhembre @chetandhembre
6 | Van Nguyen @thegoleffect
7 | Wyatt Lyon Preul @geek
8 | Christophe Porteneuve @tdd
9 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Ben Acker
2 | Lin Clark (http://lin-clark.com)
3 | Chetan Dhembre
4 | Van Nguyen
5 | Wyatt Preul (http://jsgeek.com)
6 | Christophe Porteneuve (http://tddsworld.com)
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behaviour, in case users don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files we want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.js text eol=lf
7 |
8 | # Denote all files that are truly binary and should not be modified.
9 | *.png binary
10 | *.jpg binary
11 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es6: true
6 | },
7 | extends: [
8 | 'standard',
9 | 'plugin:json/recommended'
10 | ],
11 | globals: {
12 | Atomics: 'readonly',
13 | SharedArrayBuffer: 'readonly'
14 | },
15 | parserOptions: {
16 | ecmaVersion: 2018
17 | },
18 | rules: {
19 | indent: ['error', 2],
20 | semi: 0
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/exercises/streams/exercise.js:
--------------------------------------------------------------------------------
1 | const exercise = require('../../lib/exercise')
2 |
3 | exercise.queryUrl = (port) => `http://localhost:${port}/`;
4 |
5 | exercise.addVerifyProcessor(function (callback) {
6 | const { response, url } = this.submissionResult;
7 | if (response.statusCode === 404) {
8 | const msg = exercise.__('fail.page_not_found', { url });
9 | exercise.emit('fail', msg);
10 |
11 | callback(null, false);
12 | }
13 | });
14 |
15 | module.exports = exercise;
16 |
--------------------------------------------------------------------------------
/exercises/auth/exercise.js:
--------------------------------------------------------------------------------
1 | const hyperquest = require('hyperquest');
2 | const exercise = require('../../lib/exercise');
3 |
4 | exercise.queryUrl = (port) => `http://hapi:auth@localhost:${port}/`;
5 |
6 | exercise.handleInput = (request, stream, port) => {
7 | const failUrl = `http://hapi:fail@localhost:${port}/`;
8 |
9 | request.on('response', (res) => {
10 | stream.write(`${res.statusCode}\n`);
11 |
12 | hyperquest.get(failUrl).pipe(stream);
13 | })
14 | };
15 |
16 | module.exports = exercise;
17 |
--------------------------------------------------------------------------------
/exercises/hello_hapi/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 |
3 | (async () => {
4 | try {
5 | const server = Hapi.Server({
6 | host: 'localhost',
7 | port: Number(process.argv[2] || 8080)
8 | });
9 |
10 | server.route({
11 | path: '/',
12 | method: 'GET',
13 | handler: (request, h) => 'Hello hapi'
14 | });
15 |
16 | await server.start();
17 |
18 | console.log(`Server running at: ${server.info.uri}`);
19 | } catch (error) {
20 | console.log(error);
21 | }
22 | })();
23 |
--------------------------------------------------------------------------------
/.github/workflows/master.yml:
--------------------------------------------------------------------------------
1 | name: master
2 |
3 | on:
4 | push:
5 | branches: [ '**' ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [19.x, 18.x, 16.x]
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Node ${{ matrix.node_version }}
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: ${{ matrix.node_version }}
22 | - run: npm install
23 | - run: npm run lint
24 | - run: npm test
25 |
--------------------------------------------------------------------------------
/exercises/hello_hapi/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 |
3 | (async () => {
4 | try {
5 | const server = Hapi.Server({
6 | host: 'localhost',
7 | port: Number(process.argv[2] || 8080)
8 | });
9 |
10 | server.route({
11 | path: '/',
12 | method: 'GET',
13 | handler: (request, h) => {
14 | return 'Bonjour hapi!';
15 | }
16 | });
17 |
18 | await server.start();
19 |
20 | console.log(`Serveur fonctionnant à: ${server.info.uri}`);
21 | } catch (error) {
22 | console.log(error);
23 | }
24 | })();
25 |
--------------------------------------------------------------------------------
/exercises/cookies/exercise.js:
--------------------------------------------------------------------------------
1 | const hyperquest = require('hyperquest');
2 | const exercise = require('../../lib/exercise');
3 |
4 | exercise.queryUrl = (port) => `http://localhost:${port}/set-cookie`;
5 |
6 | exercise.handleInput = (request, stream, port) => {
7 | const checkCookiesUrl = `http://localhost:${port}/check-cookie`;
8 |
9 | request.on('response', (res) => {
10 | stream.write(`${JSON.stringify(res.headers['set-cookie'])}\n`);
11 | hyperquest.get(checkCookiesUrl, {
12 | headers: { Cookie: res.headers['set-cookie'] }
13 | }).pipe(stream);
14 | })
15 | };
16 |
17 | module.exports = exercise;
18 |
--------------------------------------------------------------------------------
/exercises/proxies/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const H2o2 = require('@hapi/h2o2');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: process.argv[2] || 8080
9 | });
10 |
11 | await server.register(H2o2);
12 |
13 | server.route({
14 | method: 'GET',
15 | path: '/proxy',
16 | handler: {
17 | proxy: {
18 | host: '127.0.0.1',
19 | port: 65535
20 | }
21 | }
22 | });
23 |
24 | await server.start();
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/exercises/validation_using_joi_object/exercise.js:
--------------------------------------------------------------------------------
1 | const { Readable } = require('stream');
2 | const exercise = require('../../lib/exercise')
3 |
4 | exercise.queryUrl = (port) => `http://localhost:${port}/login`;
5 |
6 | exercise.requestOpts = { method: 'POST' };
7 |
8 | exercise.handleInput = (request, stream, port) => {
9 | const message = {
10 | isGuest: false,
11 | username: 'hapi',
12 | password: 'makemehapi'
13 | };
14 | const input = new Readable({ read(size) {} });
15 | input.pipe(request).pipe(stream);
16 | input.push(JSON.stringify(message));
17 | input.push(null);
18 | };
19 |
20 | module.exports = exercise;
21 |
--------------------------------------------------------------------------------
/exercises/streams/problem.ja.md:
--------------------------------------------------------------------------------
1 | '`/`'への'GET'リクエストに対し、ストリームでROT13変換(シーザー暗号の1種)を
2 | 行ったファイルの内容を返すサーバーを作ります。変換するファイルの内容は下記です。
3 |
4 | ```
5 | The Pursuit of Hapi-ness
6 | ```
7 |
8 | 出力は以下のようになるはずです。
9 |
10 | ```
11 | Gur Chefhvg bs Uncv-arff
12 | ```
13 |
14 | -----------------------------------------------------------------
15 | ## ヒント
16 |
17 | ### ストリーム
18 |
19 | '`reply`'というhapiのハンドラは引数としてストリームを取ることが出来ます。
20 |
21 | ### ファイル
22 |
23 | '`fs`'モジュールは'`createReadStream(pathToFile)`'を持っていますが、
24 | この関数が役に立つはずです。
25 |
26 | ### シンプル ROT13
27 |
28 | このエクササイズでは、'`rot13-transform`'を使用します。以下のコマンドで
29 | インストールして下さい。
30 |
31 | ```sh
32 | npm install rot13-transform
33 | ```
34 |
--------------------------------------------------------------------------------
/exercises/streams/problem.ko.md:
--------------------------------------------------------------------------------
1 | hapi 서버를 만들어봅시다. `/`로 들어오는 HTTP GET request에 스트림을 이용하여 ROT13
2 | 변환을 거친 파일 내용을 돌려주도록 해주세요. 변환할 파일의 내용은 다음과 같습니다.
3 |
4 | ```
5 | The Pursuit of Hapi-ness
6 | ```
7 |
8 | 출력되는 내용은 다음과 같습니다.
9 |
10 | ```
11 | Gur Chefhvg bs Uncv-arff
12 | ```
13 |
14 | -----------------------------------------------------------------
15 | ##힌트
16 |
17 | ### 스트림
18 |
19 | `reply` hapi 핸들러는 스트림을 인수로 받을 수 있습니다.
20 |
21 | ### 파일
22 |
23 | `fs` 모듈은 `createReadStream(pathToFile)`이라는 유용한 함수가 있습니다.
24 |
25 | ### 간단한 ROT13
26 |
27 | 이번 과제에서 `rot13-transform`을 사용할 것입니다. 다음 명령어로 rot13-transform을 설치하세요.
28 |
29 | ```sh
30 | npm install rot13-transform
31 | ```
32 |
--------------------------------------------------------------------------------
/exercises/handling/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Inert = require('@hapi/inert');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: process.argv[2] || 8080,
9 | routes: {
10 | files: {
11 | relativeTo: __dirname
12 | }
13 | }
14 | });
15 |
16 | await server.register(Inert);
17 |
18 | server.route({
19 | path: '/',
20 | method: 'GET',
21 | handler: {
22 | file: 'index.html'
23 | }
24 | });
25 |
26 | await server.start();
27 | } catch (error) {
28 | console.log(error);
29 | }
30 | })();
31 |
--------------------------------------------------------------------------------
/exercises/handling/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Inert = require('@hapi/inert');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: process.argv[2] || 8080,
9 | routes: {
10 | files: {
11 | relativeTo: __dirname
12 | }
13 | }
14 | });
15 |
16 | await server.register(Inert);
17 |
18 | server.route({
19 | path: '/',
20 | method: 'GET',
21 | handler: {
22 | file: 'index.html'
23 | }
24 | });
25 |
26 | await server.start();
27 | } catch (error) {
28 | console.log(error);
29 | }
30 | })();
31 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const Workshopper = require('workshopper-adventure');
4 | const path = require('path');
5 | const credits = require('./credits');
6 |
7 | const name = 'makemehapi';
8 |
9 | function fpath (f) {
10 | return path.join(__dirname, f);
11 | }
12 |
13 | const workshopper = Workshopper({
14 | name,
15 | exerciseDir: fpath('./exercises/'),
16 | appDir: __dirname,
17 | languages: ['en', 'fr', 'ja', 'ko'],
18 | helpFile: fpath('./i18n/help/{lang}.txt'),
19 | menuItems: [{
20 | name: 'credits',
21 | handler: credits
22 | }]
23 | });
24 |
25 | workshopper.addAll(require('./exercises/menu.json'));
26 |
27 | module.exports = workshopper;
28 |
--------------------------------------------------------------------------------
/exercises/uploads/exercise.js:
--------------------------------------------------------------------------------
1 | const FormData = require('form-data');
2 | const fs = require('fs');
3 | const exercise = require('../../lib/exercise')
4 |
5 | exercise.queryUrl = (port) => `http://localhost:${port}/upload`;
6 |
7 | exercise.requestOpts = { method: 'POST' };
8 |
9 | exercise.handleInput = (request, stream, port) => {
10 | const form = new FormData();
11 | form.append('description', 'makemehapi');
12 | form.append('file', fs.createReadStream(`${__dirname}/solution/input.txt`));
13 | const headers = form.getHeaders();
14 | request.setHeader('content-type', headers['content-type']);
15 | form.pipe(request).pipe(stream);
16 | };
17 |
18 | module.exports = exercise;
19 |
--------------------------------------------------------------------------------
/exercises/directories/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Hapi = require('@hapi/hapi');
3 | const Inert = require('@hapi/inert');
4 |
5 | (async () => {
6 | try {
7 | const server = Hapi.Server({
8 | host: 'localhost',
9 | port: process.argv[2] || 8080
10 | });
11 |
12 | await server.register(Inert);
13 |
14 | server.route({
15 | path: '/foo/bar/baz/{filename}',
16 | method: 'GET',
17 | handler: {
18 | directory: {
19 | path: Path.join(__dirname, 'public')
20 | }
21 | }
22 | });
23 |
24 | await server.start();
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/exercises/directories/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Hapi = require('@hapi/hapi');
3 | const Inert = require('@hapi/inert');
4 |
5 | (async () => {
6 | try {
7 | const server = Hapi.Server({
8 | host: 'localhost',
9 | port: process.argv[2] || 8080
10 | });
11 |
12 | await server.register(Inert);
13 |
14 | server.route({
15 | path: '/foo/bar/baz/{filename}',
16 | method: 'GET',
17 | handler: {
18 | directory: {
19 | path: Path.join(__dirname, 'public')
20 | }
21 | }
22 | });
23 |
24 | await server.start();
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/lib/verifyStatusProcessor.js:
--------------------------------------------------------------------------------
1 | const verifyStatusProcessor = (exercise) => {
2 | exercise.addVerifyProcessor(function (callback) {
3 | const { response, url } = this.submissionResult;
4 | const { statusCode } = response;
5 | const expected = 200;
6 | let pass = false;
7 |
8 | if (statusCode === expected) {
9 | exercise.emit('pass', exercise.__('pass.status_code'));
10 | pass = true;
11 | } else {
12 | const msg = exercise.__('fail.wrong_status_code', { statusCode, url, expected });
13 | exercise.emit('fail', msg);
14 | }
15 |
16 | callback(null, pass);
17 | });
18 | return exercise;
19 | }
20 |
21 | module.exports = verifyStatusProcessor;
22 |
--------------------------------------------------------------------------------
/credits.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const colorsTmpl = require('colors-tmpl');
4 | const combinedStream = require('combined-stream');
5 |
6 | function read (file) {
7 | return fs.createReadStream(path.join(__dirname, file), { encoding: 'utf8' });
8 | }
9 |
10 | function credits (workshopper) {
11 | combinedStream
12 | .create()
13 | .append(read(`./i18n/credits/${workshopper.lang}.txt`))
14 | .append(read('./credits.txt'))
15 | .on('error', (err) => {
16 | console.log(err);
17 | throw err;
18 | })
19 | .on('data', (data) => {
20 | console.log(colorsTmpl(data));
21 | })
22 | .resume();
23 | }
24 |
25 | module.exports = credits;
26 |
--------------------------------------------------------------------------------
/exercises/streams/problem.md:
--------------------------------------------------------------------------------
1 | Create a hapi server which responds to `GET` requests to `/` by streaming a
2 | ROT13'd version of a file that contains:
3 |
4 | ```
5 | The Pursuit of Hapi-ness
6 | ```
7 |
8 | Output should look like:
9 |
10 | ```
11 | Gur Chefhvg bs Uncv-arff
12 | ```
13 |
14 | -----------------------------------------------------------------
15 | ##HINTS
16 |
17 | ### Stream
18 |
19 | The hapi handler return a stream.
20 |
21 | ### File
22 |
23 | The `fs` module has a `createReadStream(pathToFile)` function that would be useful.
24 |
25 | ### Simple ROT13
26 |
27 | In this exercise, we'll be using `rot13-transform`. To install rot13-transform:
28 |
29 | ```sh
30 | npm install rot13-transform
31 | ```
32 |
--------------------------------------------------------------------------------
/exercises/validation/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Joi = require('joi');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: Number(process.argv[2] || 8080)
9 | });
10 |
11 | server.route({
12 | method: 'GET',
13 | path: '/chickens/{breed?}',
14 | config: {
15 | handler: (request, h) => `Vous avez demandé les poulets ${request.params.breed}`,
16 | validate: {
17 | params: {
18 | breed: Joi.string().required()
19 | }
20 | }
21 | }
22 | });
23 |
24 | await server.start();
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/exercises/validation/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Joi = require('@hapi/joi');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: Number(process.argv[2] || 8080)
9 | });
10 |
11 | server.route({
12 | method: 'GET',
13 | path: '/chickens/{breed?}',
14 | config: {
15 | handler: (request, h) => `You asked for the chicken ${request.params.breed}`,
16 | validate: {
17 | params: Joi.object({
18 | breed: Joi.string().required()
19 | })
20 | }
21 | }
22 | });
23 |
24 | await server.start();
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/exercises/streams/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Fs = require('fs');
2 | const Hapi = require('@hapi/hapi');
3 | const Path = require('path');
4 | const Rot13 = require('rot13-transform');
5 |
6 | (async () => {
7 | try {
8 | const server = Hapi.Server({
9 | host: 'localhost',
10 | port: Number(process.argv[2] || 8080)
11 | });
12 |
13 | server.route({
14 | path: '/',
15 | method: 'GET',
16 | config: {
17 | handler: (request, h) => {
18 | const thisfile = Fs.createReadStream(Path.join(__dirname, 'input.txt'));
19 | return thisfile.pipe(Rot13());
20 | }
21 | }
22 | });
23 |
24 | await server.start();
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/exercises/proxies/problem.fr.md:
--------------------------------------------------------------------------------
1 | Un proxy vous permet de relayer des requêtes d’un serveur/service à un autre.
2 |
3 | Créez un serveur qui écoute sur le numéro de port passé en argument de ligne
4 | de commande, qui accepte toute requête sur le chemin `/proxy` et les relaie
5 | à `http://localhost:65535/proxy`.
6 |
7 | -----------------------------------------------------------------
8 |
9 | ## Conseils
10 |
11 | La clé `proxy` sert à générer un gestionnaire de proxy inverse.
12 |
13 | ```js
14 | handler: {
15 | proxy: {
16 | host: '127.0.0.1',
17 | port: 65535
18 | }
19 | }
20 | ```
21 |
22 | -----------------------------------------------------------------
23 |
24 | En savoir plus sur le fond : http://fr.wikipedia.org/wiki/Proxy_inverse
25 |
--------------------------------------------------------------------------------
/exercises/proxies/problem.ko.md:
--------------------------------------------------------------------------------
1 | 프락시를 사용하면, 서버/서비스 간에 request를 넘겨주는 것이 가능합니다.
2 |
3 | 서버를 만들어봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고 `/proxy`로 들어오는 request를 전부 `http://localhost:65535/proxy`로 넘겨줍시다.
4 |
5 | -----------------------------------------------------------------
6 | ## 힌트
7 |
8 | 이번 과제는 `h2o2` 모듈이 필요합니다. 이 모듈은 프락시를 다룰 수 있는 hapi 플러그인입니다. `proxy` 설정을 사용하려면 이 모듈을 다음과 같이 등록해야 합니다.
9 |
10 | ```js
11 | var H2o2 = require('h2o2');
12 |
13 | await server.register(H2o2);
14 | ```
15 |
16 | `proxy` 키를 사용하면 리버스 프락시 handler를 만들 수도 있습니다.
17 |
18 | ```js
19 | handler: {
20 | proxy: {
21 | host: '127.0.0.1',
22 | port: 65535
23 | }
24 | }
25 | ```
26 |
27 | -----------------------------------------------------------------
28 | 참고자료: en.wikipedia.org/wiki/Proxy_server
29 |
--------------------------------------------------------------------------------
/exercises/routes/problem.ja.md:
--------------------------------------------------------------------------------
1 | コマンドラインから渡されたポート番号をlistenするhapiサーバーを作ります。
2 | HTTP GETリクエストに対し、"Hello [name]"を返します。
3 | `/{name}`の形式で渡されるパスパラメータを取得して[name]の部分を置き換えて下さい。
4 |
5 | サーバープログラムが完成すれば、以下のコマンドでテスト環境での実行が出来ます。
6 |
7 | `{appname} run program.js`
8 |
9 | 期待した結果が得られれば、以下のコマンドで結果の検証を行って下さい。
10 |
11 | `{appname} verify program.js`
12 |
13 | -----------------------------------------------------------------
14 | ## ヒント
15 |
16 | 以下のコードでは、コマンドラインから何も渡されなかった場合ポート`8080`をlistenします。
17 |
18 | ```js
19 | var Hapi = require('@hapi/hapi');
20 | var server = Hapi.Server({
21 | host: 'localhost',
22 | port: Number(process.argv[2] || 8080)
23 | });
24 | ```
25 |
26 | 以下のようなコードをルートのハンドラとして追加して下さい。
27 |
28 | ```js
29 | function handler (request, h) {
30 | return `Hello ${request.params.name}`;
31 | }
32 | ```
33 |
--------------------------------------------------------------------------------
/exercises/proxies/problem.ja.md:
--------------------------------------------------------------------------------
1 | プロキシを使用することにより、リクエストを他のサーバー/サービスへ
2 | 中継することが出来ます。
3 |
4 | コマンドラインで指定されるポート番号をlistenするサーバーを作成
5 | します。このサーバーはパス'`/proxy`'へのリクエストを全て
6 | '`http://localhost:65535/proxy`'に転送します。
7 |
8 | -----------------------------------------------------------------
9 | ## ヒント
10 |
11 | このエクササイズでは、`h2o2`モジュールが必要となります。これはhapiの
12 | プラグインで、プロキシを使用することが出来ます。このモジュールを使用
13 | するためには、下記のようにコード中でプラグインを登録する必要があります。
14 |
15 | ```js
16 | var H2o2 = require('h2o2');
17 |
18 | await server.register(H2o2);
19 | ```
20 |
21 | ハンドラの`proxy`キーを使用し、リバースプロキシを作ることが出来ます。
22 |
23 | ```js
24 | handler: {
25 | proxy: {
26 | host: '127.0.0.1',
27 | port: 65535
28 | }
29 | }
30 | ```
31 |
32 | -----------------------------------------------------------------
33 | [背景知識]
34 | https://en.wikipedia.org/wiki/Proxy_server
35 |
--------------------------------------------------------------------------------
/exercises/routes/problem.ko.md:
--------------------------------------------------------------------------------
1 | hapi 서버를 만들어봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고, `/{name}`으로 들어오는 HTTP GET request에 "Hello [name]"을 출력하도록 하세요. [name]에는 GET request의 `{name}`이 들어갑니다.
2 |
3 | 서버를 만들었다면, 다음 명령어로 테스트 환경에서 실행 가능합니다.
4 |
5 | `{appname} run program.js`
6 |
7 | 테스트 결과가 괜찮다면 다음 커맨드로 정답을 확인해보세요.
8 |
9 | `{appname} verify program.js`
10 |
11 | -----------------------------------------------------------------
12 | ## 힌트
13 |
14 | `8080` 포트를 사용하는 서버를 만드세요. 커맨드 라인으로부터 아무것도 전달되지 않는다면 다음 코드를 사용하세요.
15 |
16 | ```js
17 | var Hapi = require('@hapi/hapi');
18 | var server = Hapi.Server({
19 | host: 'localhost',
20 | port: Number(process.argv[2] || 8080)
21 | });
22 | ```
23 |
24 | 경로 handler를 다음과 같은 코드로 추가해주세요.
25 |
26 | ```js
27 | function handler (request, h) {
28 | return `Hello ${request.params.name}`;
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/i18n/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "HAPI의 은혜를 누려보자",
3 | "subtitle": "\u001b[23m과제를 선택한 다음 \u001b[3m엔터\u001b[23m로 시작하세요",
4 | "menu": {
5 | "credits": "만든 사람들"
6 | },
7 | "common": {
8 | "exercise": {
9 | "fail": {
10 | "cannot_connect": "다음 주소로의 접속에 실패하였습니다. http://localhost:{{port}}: {{code}}",
11 | "page_not_found": "다음 페이지를 찾을 수 없습니다. {{url}}",
12 | "wrong_status_code": "HTTP 상태 코드 {{statusCode}}가 다음 URL로부터 응답하였습니다. {{url}}, 기댓값은 {{expected}}입니다."
13 | },
14 | "pass": {
15 | "status_code": "상태 코드가 정확"
16 | }
17 | }
18 | },
19 | "exercises": {
20 | "VIEWS": {
21 | "name_param": "the handling"
22 | },
23 | "PROXIES": {
24 | "greeting": "Hello Proxies"
25 | },
26 | "HELPING": {
27 | "name_param": "Helping Hands"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Make Me Hapi
2 |
3 | [](https://nodei.co/npm/makemehapi/)
4 |
5 | Learn all about [hapi](http://hapijs.com) through a series of self-guided
6 | challenges.
7 |
8 | 
9 |
10 | ## Getting Started
11 |
12 | 1. Install **makemehapi** by running `npm install -g makemehapi`
13 | 2. Run `makemehapi`
14 |
15 | **makemehapi** will run you through a series of challenges ranging from a basic
16 | "hello world" server then move on to more advanced exercises dealing with
17 | rendering views, handling uploads, and managing cookies.
18 |
19 | ## Getting Help
20 |
21 | Run into a problem or want to discuss the exercises?
22 | [Open an issue](https://github.com/hapijs/makemehapi/issues) on this
23 | repository!
24 |
--------------------------------------------------------------------------------
/exercises/helping/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Hapi = require('@hapi/hapi');
3 | const Vision = require('@hapi/vision');
4 | const Handlebars = require('handlebars');
5 |
6 | (async () => {
7 | try {
8 | const server = Hapi.Server({
9 | host: 'localhost',
10 | port: Number(process.argv[2] || 8080)
11 | });
12 |
13 | await server.register(Vision);
14 |
15 | server.views({
16 | engines: {
17 | html: Handlebars
18 | },
19 | path: Path.join(__dirname, 'templates'),
20 | helpersPath: Path.join(__dirname, 'helpers')
21 | });
22 |
23 | server.route({
24 | path: '/',
25 | method: 'GET',
26 | handler: {
27 | view: 'template.html'
28 | }
29 | });
30 |
31 | await server.start();
32 | } catch (error) {
33 | console.log(error);
34 | }
35 | })();
36 |
--------------------------------------------------------------------------------
/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "REST WELL WITH HAPI",
3 | "subtitle": "\u001b[23mSelect an exercise and hit \u001b[3mEnter\u001b[23m to begin",
4 | "menu": {
5 | "credits": "CREDITS"
6 | },
7 | "common": {
8 | "exercise": {
9 | "fail": {
10 | "cannot_connect": "Error connecting to http://localhost:{{port}}: {{code}}",
11 | "page_not_found": "Page not found at {{url}}",
12 | "wrong_status_code": "Status code {{statusCode}} returned from url {{url}}, expected {{expected}}."
13 | },
14 | "pass": {
15 | "status_code": "Status code returned is correct"
16 | }
17 | }
18 | },
19 | "exercises": {
20 | "VIEWS": {
21 | "name_param": "the handling"
22 | },
23 | "PROXIES": {
24 | "greeting": "Hello Proxies"
25 | },
26 | "HELPING": {
27 | "name_param": "Helping Hands"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 | We welcome contributions from the community and are pleased to have them. Please follow this guide when logging issues or making code changes.
3 |
4 | ## Logging Issues
5 | All issues should be created using the [new issue form](https://github.com/hapijs/makemehapi/issues/new). Clearly describe the issue including steps
6 | to reproduce if there are any. Also, make sure to indicate the earliest version that has the issue being reported.
7 |
8 | ## Patching Code
9 |
10 | Code changes are welcome and should follow the guidelines below.
11 |
12 | * Fork the repository on GitHub.
13 | * Fix the issue ensuring that your code follows the [style guide](https://github.com/hapijs/contrib/blob/master/Style.md).
14 | * [Pull requests](http://help.github.com/send-pull-requests/) should be made to the [master branch](https://github.com/hapijs/makemehapi/tree/master).
15 |
--------------------------------------------------------------------------------
/i18n/br.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "REPOLSE BEM COM HAPI",
3 | "subtitle": "\u001b[23mSelecione um exercício a aperte \u001b[3mEnter\u001b[23m para começar",
4 | "menu": {
5 | "credits": "CRÉDITOS"
6 | },
7 | "common": {
8 | "exercise": {
9 | "fail": {
10 | "cannot_connect": "Erro ao conectar em http://localhost:{{port}}: {{code}}",
11 | "page_not_found": "Página não encontrada em {{url}}",
12 | "wrong_status_code": "Código de status {{statusCode}} retornado da url {{url}}, esperado {{expected}}."
13 | },
14 | "pass": {
15 | "status_code": "Código de status é correto"
16 | }
17 | }
18 | },
19 | "exercises": {
20 | "VIEWS": {
21 | "name_param": "o manuseamento"
22 | },
23 | "PROXIES": {
24 | "greeting": "Olá Proxies"
25 | },
26 | "HELPING": {
27 | "name_param": "Mãos que Ajudam"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/exercises/views/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Hapi = require('@hapi/hapi');
3 | const Vision = require('@hapi/vision');
4 | const Handlebars = require('handlebars');
5 |
6 | (async () => {
7 | try {
8 | const serverPort = process.argv[2] || 8080;
9 | const server = Hapi.Server({
10 | host: 'localhost',
11 | port: serverPort
12 | });
13 |
14 | await server.register(Vision);
15 |
16 | server.views({
17 | engines: {
18 | html: Handlebars
19 | },
20 | path: Path.join(__dirname, 'templates')
21 | });
22 |
23 | server.route({
24 | path: '/',
25 | method: 'GET',
26 | handler: {
27 | view: 'index.html'
28 | }
29 | });
30 |
31 | await server.start();
32 |
33 | console.log(`Server running at: ${server.info.uri}`);
34 | } catch (error) {
35 | console.log(error);
36 | }
37 | })();
38 |
--------------------------------------------------------------------------------
/exercises/views/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Hapi = require('@hapi/hapi');
3 | const Vision = require('@hapi/vision');
4 | const Handlebars = require('handlebars');
5 |
6 | (async () => {
7 | try {
8 | const serverPort = process.argv[2] || 8080;
9 | const server = Hapi.Server({
10 | host: 'localhost',
11 | port: serverPort
12 | });
13 |
14 | await server.register(Vision);
15 |
16 | server.views({
17 | engines: {
18 | html: Handlebars
19 | },
20 | path: Path.join(__dirname, 'templates')
21 | });
22 |
23 | server.route({
24 | path: '/',
25 | method: 'GET',
26 | handler: {
27 | view: 'index.html'
28 | }
29 | });
30 |
31 | await server.start();
32 |
33 | console.log(`Server running at: ${server.info.uri}`);
34 | } catch (error) {
35 | console.log(error);
36 | }
37 | })();
38 |
--------------------------------------------------------------------------------
/exercises/helping/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Hapi = require('@hapi/hapi');
3 | const Vision = require('@hapi/vision');
4 | const Handlebars = require('handlebars');
5 |
6 | (async () => {
7 | try {
8 | const server = Hapi.Server({
9 | host: 'localhost',
10 | port: Number(process.argv[2] || 8080)
11 | });
12 |
13 | await server.register(Vision);
14 |
15 | server.views({
16 | engines: {
17 | html: Handlebars
18 | },
19 | path: Path.join(__dirname, 'templates'),
20 | helpersPath: Path.join(__dirname, 'helpers')
21 | });
22 |
23 | server.route({
24 | path: '/',
25 | method: 'GET',
26 | handler: {
27 | view: 'template.html'
28 | }
29 | });
30 |
31 | await server.start();
32 |
33 | console.log(`Server running at: ${server.info.uri}`);
34 | } catch (error) {
35 | console.log(error);
36 | }
37 | })();
38 |
--------------------------------------------------------------------------------
/exercises/auth/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Auth = require('@hapi/basic');
3 |
4 | const user = { name: 'hapi', password: 'auth' };
5 |
6 | const validate = async (request, username, password, h) => {
7 | const isValid = username === user.name && password === user.password;
8 |
9 | return { isValid, credentials: { name: user.name } };
10 | };
11 |
12 | (async () => {
13 | try {
14 | const server = Hapi.Server({
15 | host: 'localhost',
16 | port: Number(process.argv[2] || 8080)
17 | });
18 |
19 | await server.register(Auth);
20 |
21 | server.auth.strategy('simple', 'basic', { validate });
22 | server.auth.default('simple');
23 |
24 | server.route({
25 | method: 'GET',
26 | path: '/',
27 | handler (request, h) {
28 | return 'welcome';
29 | }
30 | });
31 |
32 | await server.start();
33 | } catch (error) {
34 | console.log(error);
35 | }
36 | })();
37 |
--------------------------------------------------------------------------------
/exercises/routes/solution_ko/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 |
3 | (async () => {
4 | try {
5 | const server = Hapi.Server({
6 | host: 'localhost',
7 | port: Number(process.argv[2] || 8080)
8 | });
9 |
10 | server.route({
11 | path: '/{name}',
12 | method: 'GET',
13 | handler (request, h) {
14 | return `Hello ${request.params.name} `;
15 | // 다음 코드가 더 안전합니다
16 | //
17 | // return `Hello ${encodeURIComponent(request.params.name)}`;
18 | //
19 | // encodeURIComponent는 이하의 문자 이외에는 전부 escape 시킵니다.
20 | // 영숫자, - _ . ! ~ * ' ( )
21 | // 유저가 입력한 매개변수에 대해 왜 encodeURIComponent를 적용해야 하는지에 대한 이유는 아래 URL을 참고하세요.
22 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
23 | }
24 | });
25 |
26 | await server.start();
27 | } catch (error) {
28 | console.log(error);
29 | }
30 | })();
31 |
--------------------------------------------------------------------------------
/exercises/auth/problem.ja.md:
--------------------------------------------------------------------------------
1 | Basic認証はあなたのアプリケーションを保護するシンプルな方法で、ユーザー名と
2 | パスワードのみを使用します。クッキーやセッションは必要とせず、標準の
3 | HTTPヘッダーのみを使用します。
4 |
5 | コマンドラインから渡されるポート番号をlistenする、Basic認証で保護された
6 | サーバーを作成します。認証のユーザー名は"hapi"、パスワードは"auth"です。
7 | 認証に失敗した場合はHTTPステータスコードの401を返して下さい。
8 |
9 | --------------------
10 |
11 | ## ヒント
12 |
13 | Basic認証を扱うhapiのプラグインがあります。以下のコマンドでインストール
14 | して下さい。
15 |
16 | ```sh
17 | npm install @hapi/basic
18 | ```
19 |
20 | '`@hapi/basic`'プラグインを登録し、`basic`認証ストラテジを名前を
21 | 付けて設定して下さい。設定が完了すれば、その認証ストラテジの名前に対し、
22 | '`auth`'プロパティをルートコンフィギュレーション中で設定して下さい。
23 |
24 | ```js
25 | server.auth.strategy('simple', 'basic', { validateFunc: validate });
26 | server.auth.default('simple');
27 |
28 | server.route({
29 | method: 'GET',
30 | path: '/',
31 | handler: function (request, h) {
32 |
33 | return 'welcome';
34 | }
35 | });
36 | ```
37 |
38 |
39 | Hapi-auth-basicの情報は以下にあります。
40 |
41 | {rootdir:/node_modules/hapi-auth-basic/README.md}
42 |
43 |
--------------------------------------------------------------------------------
/exercises/validation_using_joi_object/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Joi = require('@hapi/joi');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: Number(process.argv[2] || 8080)
9 | });
10 |
11 | server.route({
12 | method: 'POST',
13 | path: '/login',
14 | config: {
15 | handler: (request, h) => 'login successful',
16 | validate: {
17 | payload: Joi.object({
18 | isGuest: Joi.boolean().required(),
19 | username: Joi.string().when('isGuest', { is: false, then: Joi.required() }),
20 | password: Joi.string().alphanum(),
21 | accessToken: Joi.string().alphanum()
22 | }).options({ allowUnknown: true }).without('password', 'accessToken')
23 | }
24 | }
25 | });
26 |
27 | await server.start();
28 | } catch (error) {
29 | console.log(error);
30 | }
31 | })();
32 |
--------------------------------------------------------------------------------
/exercises/proxies/problem.md:
--------------------------------------------------------------------------------
1 | A proxy lets you relay requests from one server/service to another.
2 |
3 | Create a server which listens on a port passed from the command line, takes any
4 | requests to the path `/proxy` and proxies them to `http://localhost:65535/proxy`.
5 |
6 | -----------------------------------------------------------------
7 | ##HINTS
8 |
9 | This exercise requires you to install the `h2o2` module, which is a hapi plugin
10 | for handling proxies. You'll need to register the plugin in your code in
11 | order to use the `proxy` configuration:
12 |
13 | ```js
14 | var H2o2 = require('h2o2');
15 |
16 | await server.register(H2o2);
17 | ```
18 |
19 | The `proxy` key can be used to generate a reverse proxy handler.
20 |
21 | ```js
22 | handler: {
23 | proxy: {
24 | host: '127.0.0.1',
25 | port: 65535
26 | }
27 | }
28 | ```
29 |
30 | -----------------------------------------------------------------
31 | Background info: en.wikipedia.org/wiki/Proxy_server
32 |
--------------------------------------------------------------------------------
/exercises/directories/problem.ko.md:
--------------------------------------------------------------------------------
1 | 서버를 만들어봅시다. `/foo/bar/baz/file.html` 경로의 요청에 대해 특정 디렉터리에 있는 파일(예: public/file.html)을 반환해주도록 설정해 주세요. 파일에는 다음 내용이 포함되도록 해주세요.
2 |
3 | ```html
4 |
5 | Hello Directories
6 |
7 | Hello Directories
8 |
9 |
10 | ```
11 |
12 | -----------------------------------------------------------------
13 | ## 힌트
14 |
15 | 이번 과제에서는 이전 과제와 마찬가지로, 정적인 디렉터리의 내용을 제공하려면 `inert` 플러그인을 `require` 해서 `register` 해야 합니다.
16 |
17 | `handler`는 다음과 같이 디렉터리 경로를 포함한 객체로 선언될 수 있습니다.
18 |
19 | ```js
20 | handler: {
21 | directory: { path: './public' }
22 | }
23 | ```
24 |
25 | 디렉터리 `handler`에서 사용하는 경로는, 반드시 path 문자열 뒤에 path 매개변수를 붙여야 합니다. 경로 지정에 사용되는 path는 파일 시스템 디렉터리 구조와 일치하지 않아도 됩니다. 매개변수 이름은 아무거나 괜찮습니다.
26 |
27 |
28 | ```js
29 | path: "/path/to/somewhere/{param}"
30 | ```
31 |
32 | 실제 사용 시에는, 프로그램 디렉터리 안에 있는 `public` 디렉터리에 절대 경로를 지정해야 하는 것에 유의하세요. 이를 위해선 `path` 모듈의 `join()` 함수와 `__dirname`이라는 글로벌 변수가 필요할 것입니다.
33 |
--------------------------------------------------------------------------------
/exercises/validation_using_joi_object/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Joi = require('@hapi/joi');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: Number(process.argv[2] || 8080)
9 | });
10 |
11 | server.route({
12 | method: 'POST',
13 | path: '/login',
14 | config: {
15 | handler: (request, h) => 'authentification réussie',
16 | validate: {
17 | payload: Joi.object({
18 | isGuest: Joi.boolean().required(),
19 | username: Joi.string().when('isGuest', { is: false, then: Joi.required() }),
20 | password: Joi.string().alphanum(),
21 | accessToken: Joi.string().alphanum()
22 | }).options({ allowUnknown: true }).without('password', 'accessToken')
23 | }
24 | }
25 | });
26 |
27 | await server.start();
28 | } catch (error) {
29 | console.log(error);
30 | }
31 | })();
32 |
--------------------------------------------------------------------------------
/exercises/routes/problem.md:
--------------------------------------------------------------------------------
1 | Create a hapi server that listens on a port passed from the command line and
2 | outputs "Hello [name]" where [name] is replaced with the path parameter supplied
3 | to GET `/{name}`
4 |
5 |
6 | When you have completed your server, you can run it in the test environment
7 | with:
8 |
9 | `{appname} run program.js`
10 |
11 | And once you are ready to verify it then run:
12 |
13 | `{appname} verify program.js`
14 |
15 | -----------------------------------------------------------------
16 | ##HINTS
17 |
18 | Create a server that listens on port `8080`, if none is passed from the command
19 | line, with the following code:
20 |
21 | ```js
22 | var Hapi = require('@hapi/hapi');
23 | var server = Hapi.Server({
24 | host: 'localhost',
25 | port: Number(process.argv[2] || 8080)
26 | });
27 | ```
28 |
29 | Add a route handler similar to the following:
30 |
31 | ```js
32 | function handler (request, h) {
33 | return `Hello ${request.params.name}`;
34 | }
35 | ```
36 |
--------------------------------------------------------------------------------
/exercises/routes/solution_ja/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 |
3 | (async () => {
4 | try {
5 | const server = Hapi.Server({
6 | host: 'localhost',
7 | port: Number(process.argv[2] || 8080)
8 | });
9 |
10 | server.route({
11 | path: '/{name}',
12 | method: 'GET',
13 | handler (request, h) {
14 | return `Hello ${request.params.name} `;
15 | // より安全な方法は以下です:
16 | //
17 | // return `Hello ${encodeURIComponent(request.params.name)}`;
18 | //
19 | // 'encodeURIComponent'は以下の文字以外、全ての文字をエスケープします。
20 | // * 英数字
21 | // * 右の記号 - _ . ! ~ * ' ( )
22 | // ユーザーの入力パラメータに対し、'encodeURIComponent'を使用すべき理由は
23 | // 以下を参照して下さい。
24 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
25 | }
26 | });
27 |
28 | await server.start();
29 | } catch (error) {
30 | console.log(error);
31 | }
32 | })();
33 |
--------------------------------------------------------------------------------
/exercises/auth/problem.ko.md:
--------------------------------------------------------------------------------
1 | Basic 인증은 유저명과 비밀번호만으로 앱으로의 접근을 보호할 수 있는 간단한 인증 방법입니다. 쿠키와 세션은 필요 없으며 표준 HTTP 헤더만 있으면 됩니다.
2 |
3 | hapi 서버를 만들어 봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고, Basic 인증을 사용해 봅시다. 인증에 사용되는 유저명은 "hapi"로, 비밀번호는 "auth"로 설정해주세요. 인증에 실패한 경우는 서버가 HTTP 401 상태 코드를 반환하도록 해주세요.
4 |
5 | --------------------
6 |
7 | ## 힌트
8 |
9 | 다음 명령어를 실행해서, Basic 인증을 다루는 플러그인을 설치해주세요.
10 |
11 | ```sh
12 | npm install @hapi/basic
13 | ```
14 |
15 | 설치한 `@hapi/basic` 플러그인을 등록하고, 다음과 같이 임의의 이름(simple)을 지정한 Basic 인증을 서버의 strategy로 설정해주세요. 인증 설정이 끝났다면, 서버의 라우트 설정의 `auth` 프로퍼티에 strategy를 설정할 때 사용한 이름을 지정해야 합니다.
16 |
17 | ```js
18 | server.auth.strategy('simple', 'basic', { validateFunc: validate });
19 | server.auth.default('simple');
20 |
21 | server.route({
22 | method: 'GET',
23 | path: '/',
24 | handler: function (request, h) {
25 |
26 | return 'welcome';
27 | }
28 | });
29 | ```
30 |
31 | Hapi-auth-basic에 관한 자세한 정보는 아래 URL에서 확인하세요.
32 |
33 | {rootdir:/node_modules/hapi-auth-basic/README.md}
34 |
35 |
--------------------------------------------------------------------------------
/exercises/proxies/exercise.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const exercise = require('../../lib/exercise')
3 |
4 | exercise.queryUrl = (port) => `http://localhost:${port}/proxy`;
5 |
6 | exercise.addSetup(function (mode, callback) {
7 | const self = this;
8 |
9 | (async () => {
10 | try {
11 | // start the server being proxied to
12 | self.server = new Hapi.Server({
13 | host: 'localhost',
14 | port: 65535
15 | });
16 |
17 | self.server.route({
18 | method: 'GET',
19 | path: '/proxy',
20 | handler: request => exercise.__('greeting')
21 | });
22 |
23 | await self.server.start();
24 | } catch (error) {
25 | console.log(error);
26 | }
27 | })();
28 |
29 | process.nextTick(callback);
30 | });
31 |
32 | exercise.addCleanup(function (mode, passed, callback) {
33 | if (this.server) {
34 | this.server.stop();
35 | }
36 |
37 | process.nextTick(callback)
38 | });
39 |
40 | module.exports = exercise;
41 |
--------------------------------------------------------------------------------
/exercises/directories/problem.ja.md:
--------------------------------------------------------------------------------
1 | '`/foo/bar/baz/file.html`'へのリクエストを、あるディレクトリに存在する
2 | ファイルへルートするサーバーを作成しましょう。例えば、'`public/file.html`'
3 | にルートします。このファイルは以下の内容を持ちます。
4 |
5 | ```html
6 |
7 | Hello Directories
8 |
9 | Hello Directories
10 |
11 |
12 | ```
13 |
14 | -----------------------------------------------------------------
15 | ## ヒント
16 |
17 | サーバーが静的なコンテンツを提供するために、前回のエクササイズと同様、
18 | `inert`モジュールを`require`し、`register`する必要があるでしょう。
19 |
20 | ハンドラは以下のように、ディレクトリパスをもつオブジェクトとして宣言
21 | することが出来ます。
22 |
23 | ```js
24 | handler: {
25 | directory: { path: './public' }
26 | }
27 | ```
28 |
29 | ディレクトリハンドラを使用するルートは、パス(URL)文字列の最後にパス
30 | パラメータを含めなければなりません。
31 |
32 | ルートに設定すパスは、実際のファイルシステムのディレクトリ構造に
33 | 一致している必要はありません。また、パラメータ名は何でも構いません。
34 |
35 | ```js
36 | path: "/path/to/somewhere/{param}"
37 | ```
38 |
39 | [注意]
40 | 実際にはプログラムディレクトリの中にある`public`ディレクトリへの
41 | 絶対パスを指定する必要があるでしょう。このパスを取得するために、
42 | `path`コアモジュールの`join()`関数、及びグローバル変数の`__dirname`
43 | が必要となるでしょう。
44 |
--------------------------------------------------------------------------------
/exercises/handling/problem.ko.md:
--------------------------------------------------------------------------------
1 | 서버를 만들어 봅시다. `/` request에 정적 HTML 파일인 `index.html`을 반환하도록 해주세요. `index.html`의 내용은 다음과 같습니다.
2 |
3 | ```html
4 |
5 | Hello Handling
6 |
7 | Hello Handling
8 |
9 |
10 | ```
11 |
12 | -----------------------------------------------------------------
13 | ## 힌트
14 |
15 | 이번 과제에는 `inert` 모듈이 필요합니다. 이 모듈은 hapi 플러그인으로, 정적 파일과 디렉터리를 다룹니다. 정적 파일을 서비스하려면 플러그인을 코드에 등록해야 합니다.
16 |
17 | ```js
18 | var Inert = require('@hapi/inert');
19 |
20 | await server.register(Inert);
21 | ```
22 |
23 | 함수 대신 객체로 `handler`를 선언할 수도 있습니다. 객체는 다음 항목 중 반드시 하나는 가지고 있어야 합니다. `file`(`@hapi/inert` 플러그인 필요), `directory`(`@hapi/inert` 플러그인 필요), `proxy`(`@hapi/h2o2` 플러그인 필요), `view`(`@hapi/vision` 플러그인 필요)
24 |
25 | 다음과 같이 `handler`에 `file` 키를 갖는 객체를 설정할 수 있습니다.
26 |
27 | ```js
28 | handler: {
29 | file: "index.html"
30 | }
31 | ```
32 |
33 | 실제 사용 시에는, 프로그램 디렉터리 안에 있는 `index.html`에 절대 경로를 지정해야 하는 것에 유의하세요. 이를 위해선 `path` 모듈의 `join()` 함수와 `__dirname`이라는 글로벌 변수가 필요할 것입니다.
34 |
35 |
--------------------------------------------------------------------------------
/exercises/handling/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur qui répond aux requêtes sur `/` avec un fichier HTML
2 | statique nommé `index.html` qui contiendra le code suivant :
3 |
4 | ```html
5 |
6 | Coucou du gestionnaire
7 |
8 | Salut, je suis servi grâce au gestionnaire
9 |
10 |
11 | ```
12 |
13 | -----------------------------------------------------------------
14 |
15 | ## Conseils
16 |
17 | Vous pouvez déclarer comme gestionnaires des objets plutôt que des fonctions.
18 | Un tel objet doit contenir une des clés suivantes : `file`, `directory`,
19 | `proxy` ou `view`.
20 |
21 | Par exemple, `handler` peut recevoir comme valeur un objet avec la clé `file` :
22 |
23 | ```js
24 | handler: {
25 | file: "index.html"
26 | }
27 | ```
28 |
29 | Attention, dans la pratique, vous devrez fournir un chemin absolu pointant
30 | vers un fichier `index.html` situé dans le répertoire courant. Vous aurez
31 | sans doute besoin du module noyau `path`, de sa fonction `join()`, et de
32 | la variable globale `__dirname` pour y arriver.
33 |
--------------------------------------------------------------------------------
/exercises/routes/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 |
3 | (async () => {
4 | try {
5 | const server = Hapi.Server({
6 | host: 'localhost',
7 | port: Number(process.argv[2] || 8080)
8 | });
9 |
10 | server.route({
11 | path: '/{name}',
12 | method: 'GET',
13 | handler (request, h) {
14 | return `Hello ${request.params.name}`;
15 |
16 | // a more secure alternative is this:
17 | //
18 | // return `Hello ${encodeURIComponent(request.params.name)}`;
19 | //
20 | // encodeURIComponent escapes all characters except the following: alphabetic, decimal digits, - _ . ! ~ * ' ( )
21 | // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
22 | // for more details why you should call encodeURIComponent on any user-entered parameter
23 | }
24 | });
25 |
26 | await server.start();
27 |
28 | console.log(`Server running at: ${server.info.uri}`);
29 | } catch (error) {
30 | console.log(error);
31 | }
32 | })();
33 |
--------------------------------------------------------------------------------
/exercises/handling/problem.ja.md:
--------------------------------------------------------------------------------
1 | パス"`/`"へのリクエストに対し、静的なHTMLファイル`index.html`の内容を返す
2 | サーバーを作成して下さい。`index.html`の内容は以下のとおりです。
3 |
4 | ```html
5 |
6 | Hello Handling
7 |
8 | Hello Handling
9 |
10 |
11 | ```
12 |
13 | -----------------------------------------------------------------
14 | ## ヒント
15 |
16 | このエクササイズをするために、`@hapi/inert`モジュールをインストールする
17 | 必要があります。これはhapiのプラグインで、静的なファイル、あるいは
18 | ディレクトリを扱うためのものです。このモジュールを使用するためには、
19 | コードの中でプラグインをhapiに登録する必要があります。
20 |
21 | ```js
22 | var Inert = require('@hapi/inert');
23 |
24 | await server.register(Inert);
25 | ```
26 |
27 | ハンドラは関数ではなく、オブジェクトとして宣言することが出来ます。
28 | オブジェクトは次の何れかを持ちます。
29 |
30 | * `file` (要`@hapi/inert`プラグイン)
31 | * `directory` (要`@hapi/inert`プラグイン)
32 | * `proxy` (要`@hapi/h2o2`プラグイン)
33 | * `view` (要`@hapi/vision`プラグイン)
34 |
35 | 以下は`file`キーを持つオブジェクトをハンドラとして設定する例です。
36 |
37 | ```js
38 | handler: {
39 | file: "index.html"
40 | }
41 | ```
42 |
43 | [注意]
44 | 実際にはプログラムディレクトリの中にある`index.html`への絶対パスを
45 | 指定する必要があるでしょう。このパスを取得するために、`path`コア
46 | モジュールの`join()`関数、及びグローバル変数の`__dirname`が必要と
47 | なるかもしれません。
48 |
--------------------------------------------------------------------------------
/exercises/streams/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur Hapi qui répond aux requêtes GET sur `/`. La réponse doit être
2 | un flux issu d’un fichier auquel vous aurez appliqué une transformation ROT13.
3 | Le fichier d’origine contient le texte suivant :
4 |
5 | ```
6 | The Pursuit of Hapi-ness
7 | ```
8 |
9 | Du coup la sortie devrait être :
10 |
11 | ```
12 | Gur Chefhvg bs Uncv-arff
13 | ```
14 |
15 | -----------------------------------------------------------------
16 |
17 | ## Conseils
18 |
19 | ### Flux
20 |
21 | La fonction de traitement de requête `reply()` dans Hapi accepte aussi un flux
22 | comme argument.
23 |
24 | ### Fichier
25 |
26 | Le module noyau `fs` fournit une fonction `createReadStream(cheminDuFichier)`
27 | qui vous sera probablement utile. À vous de créer le fichier et de fournir
28 | son chemin complet de façon dynamique.
29 |
30 | ### ROT13 facile
31 |
32 | Pour cet exercice, vous aurez besoin du module tiers `rot13-transform`. Pour
33 | l’installez, faites simplement :
34 |
35 | ```sh
36 | npm install rot13-transform
37 | ```
38 |
39 | Sa documentation est ici :
40 |
41 | https://www.npmjs.com/package/rot13-transform
42 |
--------------------------------------------------------------------------------
/exercises/helping/problem.ko.md:
--------------------------------------------------------------------------------
1 | 서버를 만들어봅시다. VIEWS 과제에서 만든 템플릿을 이용해서 `/?name=Helping&suffix=!`로의 requests에 응답하도록 해주세요.
2 |
3 | 템플릿에 쿼리 매개변수를 직접 넣는 대신, `helpers/helper.js`에 helper를 만들고 템플릿에서 helper를 이용하여 쿼리 파라미터 `name`을 출력하세요.
4 |
5 | ```html
6 |
7 | Hello Helping!
8 |
9 | Hello Helping!
10 |
11 |
12 | ```
13 |
14 | helper를 사용해서 쿼리 매개변수 `name`과 `suffix`를 결합해주세요.
15 |
16 | -----------------------------------------------------------------
17 | ## 힌트
18 |
19 | 템플릿을 렌더링할 때 `@hapi/vision` 플러그인을 등록하는 걸 잊지 마세요.
20 |
21 | helper는 템플릿 내에서, 템플릿 컨텍스트와 기타 입력을 통해 변환과 기타 데이터 조작을 수행하는 함수입니다.
22 |
23 | helper의 경로는 서버의 옵션에서 지정할 수 있습니다. 지정된 디렉터리에 있는 모든 `.js` 파일이 로드되고 파일명은 helper의 이름이 됩니다.
24 |
25 | ```js
26 | var options = {
27 | views: {
28 | ...
29 | helpersPath: 'helpers'
30 | }
31 | };
32 | ```
33 |
34 | 각 파일은 `function(context)` 형식의 메서드를 export 해야 하며, 문자열을 반환해야 합니다.
35 |
36 | ```
37 | module.exports = function(context) {
38 | return context.data.root.query.foo;
39 | }
40 | ```
41 |
42 | 완성된 helper 함수는 다음과 같이 템플릿 안에서 사용됩니다.
43 |
44 | ```html
45 | {{helper}}
46 | ```
47 |
--------------------------------------------------------------------------------
/exercises/hello_hapi/problem.ko.md:
--------------------------------------------------------------------------------
1 | hapi 서버를 만들어 봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고, `/`로 들어오는 HTTP GET request에 "Hello hapi"라고 응답하도록 만들어 봅시다.
2 |
3 | 이 워크숍은 서버에 request를 보내고, 출력을 확인합니다.
4 |
5 | -----------------------------------------------------------------
6 | ## 힌트
7 |
8 | `8080` 포트를 사용하는 서버를 만드세요. 커맨드 라인에서 아무것도 전달되지 않는다면 다음 코드를 사용하세요.
9 |
10 | ```js
11 | var Hapi = require('@hapi/hapi');
12 | var server = Hapi.Server({
13 | host: 'localhost',
14 | port: Number(process.argv[2] || 8080)
15 | });
16 | ```
17 |
18 | 경로는 `route` 함수를 통해 추가하세요.
19 |
20 | ```js
21 | server.route({path: '/', method:'GET', handler: anonOrYourFunction});
22 | ```
23 |
24 | 핸들러는 익명 함수나 별도로 선언할 수 있습니다. (자바스크립트처럼요! :P) 단, 반드시 아래와 같이 작성해야 합니다.
25 |
26 | ```js
27 | function handler(request, reply) {
28 |
29 | // Request has all information
30 | // Reply handles client response
31 |
32 | reply();
33 | }
34 | ```
35 |
36 | `start` 함수를 호출해서 지정된 포트를 사용하는 서버를 가져오세요. `start` 함수를 호출할 때 콜백을 작성해야 하는 것도 잊지 마세요.
37 |
38 | ```js
39 | await server.start();
40 |
41 | console.log('Server running at:', server.info.uri);
42 | ```
43 | -----------------------------------------------------------------
44 |
--------------------------------------------------------------------------------
/exercises/hello_hapi/problem.ja.md:
--------------------------------------------------------------------------------
1 | コマンドラインから渡されたポート番号をlistenするhapiサーバーを作ります。
2 | '/'に対するHTTP GETリクエストに対し、"Hello hapi"を返しましょう。
3 |
4 | このワークショッパーがあなたのサーバーにリクエストを発行し、結果を検証します。
5 |
6 | -----------------------------------------------------------------
7 | ## ヒント
8 |
9 | 以下のコードでは、コマンドラインから何も渡されなかった場合ポート`8080`をlistenします。
10 |
11 | ```js
12 | var Hapi = require('@hapi/hapi');
13 | var server = Hapi.Server({
14 | host: 'localhost',
15 | port: Number(process.argv[2] || 8080)
16 | });
17 | ```
18 |
19 | ルートは`route`関数で追加することが出来ます。
20 |
21 | ```js
22 | server.route({path: '/', method:'GET', handler: anonOrYourFunction});
23 | ```
24 |
25 | ハンドラには無名関数、あるいは別に定義された関数を設定することが出来ます。
26 | (Javascriptではよくあることですね!) 但し、全てのハンドラは以下の形式
27 | でなければなりません。
28 |
29 | ```js
30 | function handler(request, reply) {
31 |
32 | // Requestが全ての情報を持ちます
33 | // Replyがクライアントへのレスポンスを扱います
34 |
35 | reply();
36 | }
37 | ```
38 |
39 | `start`関数をコールすることにより、サーバーは指定されたポートのlistenを始めます。
40 | `start`関数にはコールバックが必要となることに気をつけて下さい。
41 |
42 | ```js
43 | await server.start();
44 |
45 | console.log('Server running at:', server.info.uri);
46 | ```
47 | -----------------------------------------------------------------
48 |
--------------------------------------------------------------------------------
/exercises/validation/problem.ko.md:
--------------------------------------------------------------------------------
1 | hapi 앱의 각 endpoint는 route 설정을 통해 다양하게 커스터마이징 할 수 있습니다. 그중 하나가 유효성 검사입니다.
2 |
3 | 유효성 검사는 경로의 매개변수, request, response에서 설정할 수 있습니다. 유효성 검사를 위한 객체는 `Joi`라는 유효성 검사 프레임워크에 정의되어 있습니다.
4 |
5 | `chickens`라는 endpoint를 제공하는 route 설정을 가진 서버를 만들어 보세요. 구체적으로는 다음과 같습니다.
6 |
7 | ```
8 | /chickens
9 | ```
10 |
11 | route 설정 내에 `breed`라는 유효성 검사가 추가된 경로 매개변수를 추가하세요. 이번 과제에서는 `breed`가 설정되어 있는지만 확인하며, 유효성 검사의 내용은 확인하지 않습니다.
12 |
13 | -----------------------------------------------------------------
14 | ## 힌트
15 |
16 | 다음 코드를 사용해서 `8080` 포트를 사용하는 서버를 만드세요.
17 |
18 | ```js
19 | var routeConfig = {
20 | path: '/a/path/{with}/{parameters}',
21 | method: 'GET',
22 | handler: myHandler,
23 | config: {
24 | validate: {
25 | params: {
26 | with: Joi.string().required(),
27 | parameters: Joi.string().required()
28 | }
29 | }
30 | }
31 | }
32 | ```
33 |
34 | route에 관한 정보는 여기서 확인하세요.
35 |
36 | https://hapijs.com/api
37 |
38 | Joi에 관한 정보는 여기서 확인하세요.
39 |
40 | {rootdir:/node_modules/joi/README.md}
41 |
42 | Joi를 설치하는 명령어는 다음과 같습니다.
43 |
44 | ```sh
45 | npm install joi
46 | ```
47 |
--------------------------------------------------------------------------------
/exercises/routes/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur hapi qui écoute sur le numéro de port passé en
2 | ligne de commande, et affiche « Bonjour [nom] », sachant que `[nom]`
3 | est remplacé par un paramètre de chemin fourni par uner requête GET
4 | sur `/{name}`.
5 |
6 | Quand vous aurez terminé le serveur, vous pouvez l’exécuter dans
7 | l’environnement de test avec :
8 |
9 | ```sh
10 | {appname} run program.js
11 | ```
12 |
13 | Et quand vous serez prêt-e à valider votre exercice, faites :
14 |
15 | ```sh
16 | {appname} verify program.js
17 | ```
18 |
19 | -----------------------------------------------------------------
20 |
21 | ## Conseils
22 |
23 | Créez pour commencer un serveur qui écoute par défaut sur le port 8080,
24 | sauf si on lui passe un numéro de port explicite via la ligne de commande,
25 | à l’aide du code suivant :
26 |
27 | ```js
28 | var Hapi = require('@hapi/hapi');
29 | var server = Hapi.Server({
30 | host: 'localhost',
31 | port: Number(process.argv[2] || 8080)
32 | });
33 | ```
34 |
35 | Ajoutez un gestionnaire de route similaire à celui-ci :
36 |
37 | ```js
38 | function handler (request, h) {
39 | return `Bonjour ${request.params.name}`;
40 | }
41 | ```
42 |
--------------------------------------------------------------------------------
/exercises/helping/problem.ja.md:
--------------------------------------------------------------------------------
1 | ビューエクササイズのテンプレートを使用し、'`/?name=Helping&suffix=!`'への
2 | リクエストに応答するサーバーを作成します。
3 |
4 | 今回はクエリパラメータを直接テンプレートで使用するのではなく、'`helpers/helper.js`'
5 | にヘルパーを作成し、このヘルパーをテンプレート中で使用してクエリパラメータ'`name`'
6 | を出力します。
7 |
8 | ```html
9 |
10 | Hello Helping!
11 |
12 | Hello Helping!
13 |
14 |
15 | ```
16 |
17 | ヘルパーでクエリパラメータ'`name`'と'`suffix`'を結合して下さい。
18 |
19 | -----------------------------------------------------------------
20 | ## ヒント
21 |
22 | テンプレートを使用するので、'`@hapi/vision`'プラグインを登録していることを
23 | 確認して下さい。
24 |
25 | ヘルパーはテンプレートコンテキストやその他の入力を使用し、テンプレート中で
26 | 変換・その他のデータ操作を行う関数です。
27 |
28 | ヘルパーのパスはサーバーオプションで指定することが出来ます。指定された
29 | ディレクトリ中にある全ての'`.js'`ファイルがロードされます、ファイル名が
30 | ヘルパー名となります。
31 |
32 | ```js
33 | var options = {
34 | views: {
35 | ...
36 | helpersPath: 'helpers'
37 | }
38 | };
39 | ```
40 |
41 | 各ファイルは'`function(context)`'の形式の関数を1つだけエクスポートする
42 | 必要があります。この関数の返り値は文字列です。
43 |
44 | ```
45 | module.exports = function(context) {
46 | return context.data.root.query.foo;
47 | }
48 | ```
49 |
50 | このヘルパーファンクションはテンプレート中で以下のように使用します。
51 |
52 | ```html
53 | {{helper}}
54 | ```
55 |
--------------------------------------------------------------------------------
/exercises/routes/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 |
3 | (async () => {
4 | try {
5 | const server = Hapi.Server({
6 | host: 'localhost',
7 | port: Number(process.argv[2] || 8080)
8 | });
9 |
10 | server.route({
11 | path: '/{name}',
12 | method: 'GET',
13 | handler (request, h) {
14 | return `Bonjour ${request.params.name}`;
15 | // Une alternative plus sécurisée serait:
16 | //
17 | // return `Bonjour ${encodeURIComponent(request.params.name)}`;
18 | //
19 | // `encodeURIComponent` échape tous les caractères spéciaux à l’exception
20 | // des suivants: alphabet simple, chiffres décimaux, - _ . ! ~ * ' ( )
21 | // Voir https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
22 | // pour de plus amples détails sur les raisons qui devraient vous faire utiliser
23 | // `encodeURIComponent` pour toute donnée saisie par l’utilisateur.
24 | }
25 | });
26 |
27 | await server.start();
28 |
29 | console.log(`Serveur fonctionnant à: ${server.info.uri}`);
30 | } catch (error) {
31 | console.log(error);
32 | }
33 | })();
34 |
--------------------------------------------------------------------------------
/i18n/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "HAPIで楽をしよう",
3 | "subtitle": "\u001b[23m課題を選択し、\u001b[3mEnter\u001b[23mで始めて下さい",
4 | "menu": {
5 | "credits": "クレジット"
6 | },
7 | "common": {
8 | "exercise": {
9 | "fail": {
10 | "cannot_connect": "エラー。サーバーに接続出来ませんでした。URL-> http://localhost:{{port}}: {{url}}",
11 | "page_not_found": "ページが見つかりません。URL-> {{url}}",
12 | "wrong_status_code": "ステータスコード {{statusCode}} が返されました。URL-> {{url}} (期待値は {{expected}})"
13 | },
14 | "pass": {
15 | "status_code": "ステータスコードは正しい"
16 | }
17 | }
18 | },
19 | "exercise": {
20 | "HELLO_HAPI": "HELLO HAPI",
21 | "ROUTES": "ルート",
22 | "HANDLING": "ハンドリング",
23 | "DIRECTORIES": "ディレクトリ",
24 | "VIEWS": "ビュー",
25 | "PROXIES": "プロキシ",
26 | "HELPING": "ヘルパー",
27 | "STREAMS": "ストリーム",
28 | "VALIDATION": "バリデーション - 1",
29 | "VALIDATION USING JOI OBJECT": "バリデーション - 2",
30 | "UPLOADS": "アップロード",
31 | "COOKIES": "クッキー",
32 | "AUTH": "認証"
33 | },
34 | "exercises": {
35 | "VIEWS": {
36 | "name_param": "the handling"
37 | },
38 | "PROXIES": {
39 | "greeting": "Hello Proxies"
40 | },
41 | "HELPING": {
42 | "name_param": "Helping Hands"
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/exercises/uploads/problem.ko.md:
--------------------------------------------------------------------------------
1 | 서버를 만들어봅시다. 다음 경로로 업로드된 파일을 받아들이는 endpoint를 설정하세요.
2 |
3 | ```
4 | /upload
5 | ```
6 |
7 | endpoint는 description과 file이라는 두 개의 키를 필수로 받아야 합니다. `description` 필드에는 문자열이 들어가야 하며 아무 내용이나 적어도 됩니다. `file` 필드는 업로드된 파일이어야 합니다. endpoint는 아래와 같은 형식의 JSON 객체를 반환해야 합니다.
8 |
9 | ```json
10 | {
11 | description : //form으로부터의 description
12 | file : {
13 | data : //업로드된 파일의 내용
14 | filename: //업로드된 파일의 이름
15 | headers : //hapi가 제공하는 파일 헤더
16 | }
17 | }
18 | ```
19 |
20 | -----------------------------------------------------------------
21 | ## 힌트
22 |
23 | 파일이 정상적으로 업로드되려면, request에 `multipart/form-data` 헤더를 사용해야 합니다.
24 |
25 | 경로 구성에 다음 코드를 추가하여, 파일을 읽기 가능한 스트림으로 만들 수 있습니다.
26 |
27 | ```js
28 |
29 | payload: {
30 | output : 'stream',
31 | parse : true
32 | }
33 | ```
34 |
35 | `file`이라는 매개변수와 함께 파일을 업로드 하면 handler 함수 내에서 다음과 같은 코드로 접근할 수 있습니다.
36 |
37 | ```js
38 | handler: function (request, reply) {
39 | var body = '';
40 | request.payload.file.on('data', function (data){
41 |
42 | body += data;
43 | });
44 |
45 | request.payload.file.on('end', function (){
46 |
47 | console.log(body);
48 | });
49 | }
50 | ```
51 |
52 | 파일 업로드에 관한 자세한 정보는 hapi의 reply 인터페이스에 관한 api를 참조하세요. [API docs](http://hapijs.com/api#reply-interface).
53 |
54 |
--------------------------------------------------------------------------------
/exercises/uploads/solution/solution.js:
--------------------------------------------------------------------------------
1 | (async () => {
2 | try {
3 | const Hapi = require('@hapi/hapi');
4 | const server = new Hapi.Server({
5 | host: 'localhost',
6 | port: Number(process.argv[2] || 8080)
7 | });
8 |
9 | server.route({
10 | method: 'POST',
11 | path: '/upload',
12 | handler: (request, h) => new Promise((resolve, reject) => {
13 | let body = '';
14 |
15 | request.payload.file.on('data', (data) => {
16 | body += data;
17 | });
18 |
19 | request.payload.file.on('end', () => {
20 | const result = {
21 | description: request.payload.description,
22 | file: {
23 | data: body,
24 | filename: request.payload.file.hapi.filename,
25 | headers: request.payload.file.hapi.headers
26 | }
27 | };
28 |
29 | return resolve(JSON.stringify(result));
30 | });
31 |
32 | request.payload.file.on('error', err => reject(err));
33 | }),
34 | options: {
35 | payload: {
36 | output: 'stream',
37 | parse: true,
38 | multipart: true
39 | }
40 | }
41 | });
42 |
43 | await server.start();
44 | } catch (error) {
45 | console.log(error);
46 | }
47 | })();
48 |
--------------------------------------------------------------------------------
/exercises/directories/problem.md:
--------------------------------------------------------------------------------
1 | Create a server which routes requests to the path `/foo/bar/baz/file.html` to a
2 | file in a directory, e.g. `public/file.html`, which contains the following:
3 |
4 | ```html
5 |
6 | Hello Directories
7 |
8 | Hello Directories
9 |
10 |
11 | ```
12 |
13 | -----------------------------------------------------------------
14 | ##HINTS
15 |
16 | You'll need to `require` and `register` the `inert` plugin in this exercise the
17 | same as in the previous exercise in order to serve the static directory's
18 | contents.
19 |
20 | Handlers can be declared as an object with a directory path:
21 |
22 | ```js
23 | handler: {
24 | directory: { path: './public' }
25 | }
26 | ```
27 |
28 | Routes using the directory handler must include a path parameter at the end of
29 | the path string. The path defined for the route does not need to correspond to
30 | the file system directory structure, and the parameter name does not matter.
31 |
32 | ```js
33 | path: "/path/to/somewhere/{param}"
34 | ```
35 |
36 | Be careful: in practice, you'll need to provide an absolute path to a
37 | `public` directory in your program's directory. To achieve this, you'll
38 | probably need the `path` core module, its `join()` function, and the
39 | `__dirname` global variable.
40 |
--------------------------------------------------------------------------------
/exercises/validation/problem.ja.md:
--------------------------------------------------------------------------------
1 | あなたの作るhapiアプリケーションの各エンドポイントに対し、ルート
2 | コンフィギュレーションは様々なカスタマイズ手段を提供します。それらの
3 | 手段の内の一つがバリデーション(検証)です。
4 |
5 | このバリデーションは以下のものが対象となります。
6 |
7 | * パスで渡されるパラメータ
8 | * リクエストのデータ本体
9 | * クライアントに返すレスポンス
10 |
11 | バリデーションオブジェクトは検証フレームワークの'`Joi`'で定義されます。
12 |
13 | 'chikens'というエンドポイントを提供するルートコンフィギュレーションを
14 | 持ったサーバーを作ります。このエンドポイントは具体的には以下です。
15 |
16 | ```
17 | /chickens
18 | ```
19 |
20 | ルートコンフィギュレーションに'`breed`'という名前のパスパラメータを
21 | 追加し、'`breed`'に対しそのコンフィギュレーション中でバリデーションの
22 | 設定も行います。このエクササイズでは、'`breed`'がパス中で提供される
23 | 必要がある、ということ設定するのみで、内容に対する検証は行いません。
24 |
25 | -----------------------------------------------------------------
26 | ## ヒント
27 |
28 | コマンドラインから渡されたポート番号をlistenするサーバーを作ります。
29 | ルートのコンフィギュレーションは以下のようなものになるでしょう。
30 |
31 | ```js
32 | var routeConfig = {
33 | path: '/a/path/{with}/{parameters}',
34 | method: 'GET',
35 | handler: myHandler,
36 | config: {
37 | validate: {
38 | params: {
39 | with: Joi.string().required(),
40 | parameters: Joi.string().required()
41 | }
42 | }
43 | }
44 | }
45 | ```
46 |
47 | ルートに関する情報は以下を参照して下さい。
48 |
49 | https://hapijs.com/api
50 |
51 | Joiの情報は以下にあります。
52 |
53 | {rootdir:/node_modules/joi/README.md}
54 |
55 | Joiのインストールは下記のコマンドです。
56 |
57 | ```sh
58 | npm install joi
59 | ```
60 |
--------------------------------------------------------------------------------
/exercises/uploads/problem.ja.md:
--------------------------------------------------------------------------------
1 | 以下のパスでファイルのアップロードを受け付けるサーバーを作成します。
2 |
3 | ```
4 | /upload
5 | ```
6 |
7 | このエンドポイントは次のキーを受け付けます。
8 |
9 | description : ファイルの説明文となる文字列。内容は何でも構いません。
10 |
11 | file : アップロードするファイル。
12 |
13 | エンドポイントからは、以下のパターンに従ったJSONを返します。
14 |
15 | ```json
16 | {
17 | description : //フォームから送られてきたdescription
18 | file : {
19 | data : //アップロードされたファイルの内容
20 | filename: //アップロードされたファイル名
21 | headers : //hapiから得られるファイルヘッダー
22 | }
23 | }
24 | ```
25 |
26 | -----------------------------------------------------------------
27 | ## ヒント
28 |
29 | ファイルを入力として受け付けるためには、リクエストは```multipart/form-data```
30 | のヘッダーを使用する必要があります。
31 |
32 | 以下をルートのコンフィギュレーションで設定することにより、ファイルを読み取り用
33 | ストリームとして取得することが出来ます。
34 |
35 | ```js
36 |
37 | payload: {
38 | output : 'stream',
39 | parse : true
40 | }
41 | ```
42 |
43 | ファイルを'`file`'パラメータとしてアップロードした場合、ハンドラ関数中では
44 | 以下のようなコードを使用してファイルにアクセスすることが可能です。
45 |
46 | ```js
47 | handler: function (request, reply) {
48 | var body = '';
49 | request.payload.file.on('data', function (data){
50 |
51 | body += data
52 | });
53 |
54 | request.payload.file.on('end', function (){
55 |
56 | console.log(body);
57 | });
58 | }
59 | ```
60 |
61 | ファイルのアップロードに関しては、hapiのreplyインターフェースのドキュメントに
62 | 詳しい情報があります。
63 |
64 | [API docs](http://hapijs.com/api#reply-interface).
65 |
66 |
--------------------------------------------------------------------------------
/exercises/uploads/solution_fr/solution.js:
--------------------------------------------------------------------------------
1 | (async () => {
2 | try {
3 | const Hapi = require('@hapi/hapi');
4 | const server = new Hapi.Server({
5 | host: 'localhost',
6 | port: Number(process.argv[2] || 8080)
7 | });
8 |
9 | server.route({
10 | method: 'POST',
11 | path: '/upload',
12 | config: {
13 | handler: (request, h) => new Promise((resolve, reject) => {
14 | let body = '';
15 |
16 | request.payload.file.on('data', (data) => {
17 | body += data;
18 | });
19 |
20 | request.payload.file.on('end', () => {
21 | const result = {
22 | description: request.payload.description,
23 | file: {
24 | data: body,
25 | filename: request.payload.file.hapi.filename,
26 | headers: request.payload.file.hapi.headers
27 | }
28 | };
29 |
30 | return resolve(JSON.stringify(result));
31 | });
32 |
33 | request.payload.file.on('error', err => reject(err));
34 | }),
35 | payload: {
36 | output: 'stream',
37 | parse: true,
38 | allow: 'multipart/form-data'
39 | }
40 | }
41 | });
42 |
43 | await server.start();
44 | } catch (error) {
45 | console.log(error);
46 | }
47 | })();
48 |
--------------------------------------------------------------------------------
/exercises/auth/problem.md:
--------------------------------------------------------------------------------
1 | Basic Authentication is a simple way to protect access to your application using
2 | only a username and password. There is no need for cookies or sessions, only a
3 | standard HTTP header.
4 |
5 | Create a hapi server that listens on a port passed from the command line and is
6 | protected with Basic Authentication. The authentication username should be
7 | "hapi" and the password "auth" and the server should respond with an HTTP 401
8 | status code when authentication fails.
9 |
10 | --------------------
11 |
12 | ##HINTS
13 |
14 | There is a hapi plugin for handling basic authentication. Install it by running:
15 |
16 | ```sh
17 | npm install @hapi/basic
18 | ```
19 |
20 | You'll need to register the `@hapi/basic` plugin then configure a named
21 | authentication strategy for `basic`. Once authentication is configured, you'll
22 | need to set the `auth` property in the route configuration to the name of the
23 | strategy you configured.
24 |
25 | ```js
26 | server.auth.strategy('simple', 'basic', { validateFunc: validate });
27 | server.auth.default('simple');
28 |
29 | server.route({
30 | method: 'GET',
31 | path: '/',
32 | handler: function (request, h) {
33 |
34 | return 'welcome';
35 | }
36 | });
37 | ```
38 |
39 | Hapi-auth-basic information can be found here:
40 |
41 | {rootdir:/node_modules/@hapi/basic/README.md}
42 |
43 |
--------------------------------------------------------------------------------
/exercises/directories/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur qui route toute requête vers le chemin
2 | `/foo/bar/baz/file.html` vers un fichier présent dans un répertoire, par
3 | exemple `public/file.html`. Ce fichier contiendrait :
4 |
5 | ```html
6 |
7 | Salut les répertoires
8 |
9 | Salut les répertoires
10 |
11 |
12 | ```
13 |
14 | -----------------------------------------------------------------
15 |
16 | ## Conseils
17 |
18 | Les gestionnaires peuvent être un objet avec un chemin vers un répertoire :
19 |
20 | ```js
21 | handler: {
22 | directory: { path: './public' }
23 | }
24 | ```
25 |
26 | Les routes reposant sur un gestionnaire de type `directory` doivent inclure un
27 | paramètre à la fin de la définition de leur chemin d’accès. Ce chemin d’accès
28 | associé à la route n’a pas besoin de correspondre à un quelconque répertoire
29 | présent sur le disque. De même, le nom du paramètre à la fin du chemin n’a pas
30 | d’importance particulière.
31 |
32 | ```js
33 | path: "/chemin/vers/quelquepart/{param}"
34 | ```
35 |
36 | Attention, dans la pratique, vous devrez fournir un chemin absolu pointant
37 | vers un répertoire `public` situé dans le répertoire courant. Vous aurez
38 | sans doute besoin du module noyau `path`, de sa fonction `join()`, et de
39 | la variable globale `__dirname` pour y arriver.
40 |
--------------------------------------------------------------------------------
/i18n/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "HAPI S’OCCUPE DU REST",
3 | "subtitle" : "\u001b[23mSélectionnez un exercice et tapez \u001b[3mEnter\u001b[23m pour démarrer",
4 | "menu": {
5 | "credits": "CRÉDITS"
6 | },
7 | "common": {
8 | "exercise": {
9 | "fail": {
10 | "cannot_connect": "Connexion impossible à http://localhost:{{port}}: {{code}}",
11 | "page_not_found": "Page introuvable à l’adresse {{url}}",
12 | "wrong_status_code": "Code d’état {{statusCode}} renvoyé par l’URL {{url}}, on attendait {{expected}}."
13 | },
14 | "pass": {
15 | "status_code": "le code d'état est correct"
16 | }
17 | }
18 | },
19 | "exercise": {
20 | "HELLO_HAPI": "BONJOUR HAPI",
21 | "ROUTES": "ROUTES",
22 | "HANDLING": "GESTION DE REQUÊTE",
23 | "DIRECTORIES": "RÉPERTOIRES",
24 | "VIEWS": "VUES",
25 | "PROXIES": "PROXIES",
26 | "HELPING": "ASSISTANTS",
27 | "STREAMS": "FLUX",
28 | "VALIDATION": "VALIDATION",
29 | "VALIDATION USING JOI OBJECT": "VALIDATION AVEC UN OBJET JOI",
30 | "UPLOADS": "TÉLÉVERSEMENTS",
31 | "COOKIES": "COOKIES",
32 | "AUTH": "AUTHENTICATION"
33 | },
34 | "exercises": {
35 | "VIEWS": {
36 | "name_param": "les vues"
37 | },
38 | "PROXIES": {
39 | "greeting": "Bonjour les proxies"
40 | },
41 | "HELPING": {
42 | "name_param": "les petites mains"
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [7.1.4](https://github.com/ccarruitero/makemehapi/compare/v7.1.3...v7.1.4) (2020-11-23)
6 |
7 | ### [7.1.3](https://github.com/ccarruitero/makemehapi/compare/v7.1.2...v7.1.3) (2020-05-20)
8 |
9 |
10 | ### Bug Fixes
11 |
12 | * **handling:** add info about setup files base path in server ([f8bae05](https://github.com/ccarruitero/makemehapi/commit/f8bae05cb69c11fb255ffb07a848fb3890e934c4))
13 | * cookies exercise solution ([1b2ec73](https://github.com/ccarruitero/makemehapi/commit/1b2ec73104a9f354dbba59b92acb71350b5d478c))
14 | * kill child process when hyperquest fail fix [#186](https://github.com/ccarruitero/makemehapi/issues/186) ([a014b3a](https://github.com/ccarruitero/makemehapi/commit/a014b3aa877e4e3f711bcf52e3d57c4868c0d0ea))
15 | * linter ([170fdc1](https://github.com/ccarruitero/makemehapi/commit/170fdc1e76bae8ee738962d8cbc604a155f99855))
16 |
17 | ### [7.1.2](https://github.com/ccarruitero/makemehapi/compare/v7.1.1...v7.1.2) (2020-01-13)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * license in package.json ([c078e8f](https://github.com/ccarruitero/makemehapi/commit/c078e8ff454ac8faccaf98e58c5db4644e354687))
23 |
24 | ### [7.1.1](https://github.com/ccarruitero/makemehapi/compare/v7.1.0...v7.1.1) (2019-11-06)
25 |
--------------------------------------------------------------------------------
/exercises/views/problem.ko.md:
--------------------------------------------------------------------------------
1 | `/?name=Handling`이라는 request에 `templates/index.html`에 위치한 템플릿을 사용해서 응답하는 서버를 만드세요. 템플릿이 출력하는 HTML은 다음과 같습니다.
2 |
3 | ```html
4 |
5 | Hello Handling
6 |
7 | Hello Handling
8 |
9 |
10 | ```
11 |
12 | -----------------------------------------------------------------
13 | ## 힌트
14 |
15 | 이번 과제는 `@hapi/vision`이 필요하므로 설치해주세요. 이 모듈은 템플릿을 렌더링하는 hapi 플러그인 입니다. 다음과 같이 플러그인을 등록하세요.
16 |
17 | ```js
18 | var Vision = require('@hapi/vision');
19 |
20 | await server.register(Vision);
21 | ```
22 |
23 | `view` 키는 response를 만드는데 사용되는 템플릿을 지정하는 데 사용됩니다.
24 |
25 | ```js
26 | handler: {
27 | view: "index.html"
28 | }
29 | ```
30 |
31 | `server.views()`는 서버에서 사용되는 템플릿을 구성하기 위한 서버 메서드 입니다.
32 | 이 메서드는 설정 객체를 받아서 파일 확장자에 따라 다른 엔진을 설정할 수 있게 해줍니다. 또한 템플릿의 디렉터리 경로를 설정할 수도 있습니다.
33 |
34 | ```js
35 | server.views({
36 | engines: {
37 | html: require('handlebars')
38 | },
39 | path: Path.join(__dirname, 'templates')
40 | });
41 | ```
42 |
43 | 이번 과제에서는 Handlebars를 사용합니다. 다음 명령어로 설치하세요.
44 |
45 | ```sh
46 | npm install handlebars
47 | ```
48 |
49 | Handlebars 템플릿을 사용하면, HTML 내에서 변수를 렌더링할 수 있습니다. 사용법은 다음과 같습니다. `{{foo}}`. 변수를 중괄호로 두 번 묶어주세요.
50 |
51 | 템플릿은 request로부터 몇 가지 정보를 받습니다. 예를 들어 URL을 통해서 전달되는 쿼리 매개변수가 있습니다. 이 매개변수는 `query` 객체가 되며, 템플릿 내에서 사용할 수 있습니다. 쿼리 매개변수는 자동으로 해석되며, route의 `path`에 선언되지 않습니다.
52 |
53 | ```html
54 | {{query.paramName}}
55 | ```
56 |
--------------------------------------------------------------------------------
/exercises/hello_hapi/problem.md:
--------------------------------------------------------------------------------
1 | Create a hapi server that listens on a port passed from the command line and
2 | replies with "Hello hapi" when an HTTP GET request is sent to `/` .
3 |
4 |
5 | The workshop will execute requests against the server and verify the output.
6 |
7 | -----------------------------------------------------------------
8 | ##HINTS
9 |
10 | Create a server that listens on port `8080` , if none is passed from the command
11 | line, with the following code:
12 |
13 | ```js
14 | var Hapi = require('@hapi/hapi');
15 | var server = Hapi.Server({
16 | host: 'localhost',
17 | port: Number(process.argv[2] || 8080)
18 | });
19 |
20 | ```
21 |
22 | Routes are added via the `route` function:
23 |
24 | ```js
25 | server.route({path: '/', method:'GET', handler: anonOrYourFunction});
26 | ```
27 |
28 | Handlers can be anonymous functions or separately declared (just like in
29 | javascript :P), but all of them should have this signature:
30 |
31 | ```js
32 | function handler(request, h) {
33 |
34 | // Request has all information
35 | // a string can be returned
36 |
37 | return 'a string in the response';
38 | }
39 | ```
40 |
41 | Calling the `start` function gets a server listening on the assigned port. Note
42 | that await is required when calling `start`:
43 |
44 | ```js
45 | await server.start();
46 |
47 | console.log('Server running at:', server.info.uri);
48 | ```
49 | -----------------------------------------------------------------
50 |
--------------------------------------------------------------------------------
/exercises/cookies/solution/solution.js:
--------------------------------------------------------------------------------
1 | const Hapi = require('@hapi/hapi');
2 | const Boom = require('@hapi/boom');
3 |
4 | (async () => {
5 | try {
6 | const server = Hapi.Server({
7 | host: 'localhost',
8 | port: Number(process.argv[2] || 8080)
9 | });
10 |
11 | server.state('session', {
12 | path: '/',
13 | encoding: 'base64json',
14 | ttl: 10,
15 | domain: 'localhost',
16 | isSameSite: false,
17 | isSecure: false,
18 | isHttpOnly: false
19 | });
20 |
21 | server.route({
22 | method: 'GET',
23 | path: '/set-cookie',
24 | handler: (request, h) => h.response({
25 | message: 'success'
26 | }).state('session', { key: 'makemehapi' }),
27 | options: {
28 | state: {
29 | parse: true,
30 | failAction: 'log'
31 | }
32 | }
33 | });
34 |
35 | server.route({
36 | method: 'GET',
37 | path: '/check-cookie',
38 | handler: (request, h) => {
39 | const { session } = request.state;
40 | let result;
41 |
42 | if (session) {
43 | result = { user: 'hapi' };
44 | } else {
45 | result = Boom.unauthorized('Missing authentication');
46 | }
47 |
48 | return result;
49 | },
50 | options: {
51 | state: {
52 | parse: true,
53 | failAction: 'log'
54 | }
55 | }
56 | });
57 |
58 | await server.start();
59 | } catch (error) {
60 | console.log(error);
61 | }
62 | })();
63 |
--------------------------------------------------------------------------------
/exercises/helping/problem.md:
--------------------------------------------------------------------------------
1 | Create a server which responds to requests to `/?name=Helping&suffix=!` using
2 | the template from the VIEWS exercise.
3 |
4 | Instead of placing the query parameter directly in the template, create a helper
5 | at `helpers/helper.js` and use this helper in the template to output the `name`
6 | query parameter.
7 |
8 | ```html
9 |
10 | Hello Helping!
11 |
12 | Hello Helping!
13 |
14 |
15 | ```
16 |
17 | The helper should concatenate the `name` and `suffix` query parameters.
18 |
19 | -----------------------------------------------------------------
20 | ##HINTS
21 |
22 | Be sure to register the `@hapi/vision` plugin when attempting to render the template.
23 |
24 | Helpers are functions used within templates to perform transformations and other
25 | data manipulations using the template context or other inputs.
26 |
27 | You can define a helpers path in the server options. All `.js` files in this
28 | directory will be loaded and the file name will be used as the helper name.
29 |
30 | ```js
31 | var options = {
32 | views: {
33 | ...
34 | helpersPath: 'helpers'
35 | }
36 | };
37 | ```
38 |
39 | Each file must export a single method with the signature `function(context)` and
40 | return a string.
41 |
42 | ```
43 | module.exports = function(context) {
44 | return context.data.root.query.foo;
45 | }
46 | ```
47 |
48 | The helper function can then be used in the template.
49 |
50 | ```html
51 | {{helper}}
52 | ```
53 |
--------------------------------------------------------------------------------
/exercises/validation/problem.md:
--------------------------------------------------------------------------------
1 | Route configuration offers lots of ways to customize each endpoint offered by
2 | your hapi application. One of those ways is through validation.
3 |
4 | Validation can happen in parameters in the path, in inbound payload validation,
5 | and outbound response. Objects for validation are defined in the `Joi`
6 | validation framework.
7 |
8 | Create a server that has a route configuration exposing an endpoint for
9 | chickens. Specifically:
10 |
11 | ```
12 | /chickens
13 | ```
14 |
15 | Within the route, add a path parameter named `breed` which has an attached
16 | validation within the route's configuration. The solution will just check that a
17 | Validation object exists within the configuration for `breed`, not any specific
18 | validation.
19 |
20 | -----------------------------------------------------------------
21 | ##HINTS
22 |
23 | Create a server that listens on port `8080` with the following code:
24 |
25 | ```js
26 | var routeConfig = {
27 | path: '/a/path/{with}/{parameters}',
28 | method: 'GET',
29 | handler: myHandler,
30 | config: {
31 | validate: {
32 | params: {
33 | with: Joi.string().required(),
34 | parameters: Joi.string().required()
35 | }
36 | }
37 | }
38 | }
39 | ```
40 |
41 | All route information can be found [here](https://hapijs.com/api).
42 |
43 | Joi
44 |
45 | Information can be found here:
46 |
47 | {rootdir:/node_modules/joi/README.md}
48 |
49 | To install joi:
50 |
51 | ```sh
52 | npm install joi
53 | ```
54 |
--------------------------------------------------------------------------------
/exercises/validation_using_joi_object/problem.ja.md:
--------------------------------------------------------------------------------
1 | '`Joi`'オブジェクトを使用することにより、パス、リクエストデータ、レスポンスに
2 | 対し、検証ルールを高度にカスタマイズすることが可能です。
3 |
4 | ログインエンドポイントを提供するサーバーを作成し、'`/login`'へのHTTP `POST`
5 | リクエストに対し、"login successful"を返します。
6 |
7 | このエンドポイントはリクエストデータで以下の変数を受け付けます。
8 |
9 | ```isGuest``` (ブール値)
10 | ```username``` (文字列)
11 | ```accessToken``` (英数字)
12 | ```password``` (英数字)
13 |
14 | バリデーションは以下の条件をチェックします。
15 |
16 | i) ```isGuest``` がfalseの場合, ```username```が必要となる。
17 | ii) ```password```と```accessToken```は同時に使用することは出来ない。
18 | iii) 上記で指定されたパラメータ以外のものが送られて来た場合、検証はPASSとする。
19 |
20 | -----------------------------------------------------------------
21 | ## ヒント
22 |
23 | コマンドラインから渡されたポート番号をlistenするサーバーを作ります。
24 | ルートのコンフィギュレーションは以下のようなものになるでしょう。
25 |
26 | ```js
27 |
28 | var routeConfig = {
29 | path: '/a/path/',
30 | method: 'POST',
31 | handler: myHandler,
32 | config: {
33 | validate: {
34 | payload: Joi.object({
35 | username: Joi.string(),
36 | password: Joi.string().alphanum(),
37 | accessToken: Joi.string().alphanum(),
38 | birthyear: Joi.number().integer().min(1900).max(2013),
39 | email: Joi.string().email()
40 | })
41 | .options({allowUnknown: true})
42 | .with('username', 'birthyear')
43 | .without('password', 'accessToken')
44 | }
45 | }
46 | }
47 | ```
48 |
49 | ルートに関する情報は以下を参照して下さい。
50 |
51 | https://hapijs.com/api
52 |
53 | Joiの情報は以下にあります。
54 |
55 | {rootdir:/node_modules/joi/README.md}
56 |
--------------------------------------------------------------------------------
/exercises/cookies/problem.ko.md:
--------------------------------------------------------------------------------
1 | 서버를 만들어봅시다. 'GET' request로 접근 가능한 `set-cookie`와 `check-cookie` 두 개의 endpoint를 라우트 설정에 추가해주세요.
2 |
3 | ```
4 | /set-cookie
5 | ```
6 |
7 | `set-cookie` endpoint는 'session'이라는 키로 `{ key: 'makemehapi' }` 값을 쿠키에 저장할 것입니다. 쿠키는 `base64json`으로 암호화하고, `10 ms`가 지나면 소멸해야 하며, 도메인 범위는 `localhost`여야 합니다. response는 이번 과제에서 별로 중요하지 않으므로, 여러분에게 맡기겠습니다.
8 |
9 | ```
10 | /check-cookie
11 | ```
12 |
13 | `check-cookie` endpoint는 `/set-cookie` endpoint로부터 쿠키를 전달받습니다. 쿠키에 `session` 키가 있다면 `{ user: 'hapi' }`를 반환하고, 없다면 `unauthorized` 접근 오류를 반환하도록 해주세요.
14 |
15 | --------------------
16 |
17 | ## 힌트
18 |
19 | `server.route()` 함수에 다음과 같은 옵션을 추가하세요.
20 |
21 | ```js
22 | config: {
23 | state: {
24 | parse: true,
25 | failAction: 'log'
26 | }
27 | }
28 | ```
29 |
30 | 이 옵션을 사용해서, 서버가 쿠키를 다양한 방법으로 다룰 수 있게 설정할 수 있습니다.
31 |
32 | `hapi`는 특정 URL에 대해 쿠키를 관리하는 기능을 제공합니다.
33 |
34 | ```js
35 | server.state('session', {
36 | path: '/',
37 | });
38 | ```
39 |
40 | request에 응답할 때, 다음과 같이 쿠키를 설정할 수 있습니다.
41 |
42 | ```js
43 | reply('success').state('session', 'session')
44 | ```
45 |
46 | 쿠키값은 서버의 state에 보존되어, 다음과 같은 코드로 접근할 수 있습니다.
47 |
48 | ```js
49 | var session = request.state.session;
50 | ```
51 |
52 | `hapi`에서 쿠키를 다루는 법에 대해 더 알고 싶으시다면, 다음 URL에서 확인하세요. [API](http://hapijs.com/api).
53 |
54 | 이번 과제에서는 필요하지 않지만, Boom 모듈([Boom](https://www.npmjs.com/package/boom))을 사용하면 간단하게 `unauthorized` 오류를 올바른 HTTP 상태 코드와 함께 반환할 수 있습니다. 다음과 같이 말이죠.
55 |
56 | ```js
57 | var Boom = require('boom');
58 | ```
59 |
60 | ```js
61 | reply(Boom.unauthorized('Missing authentication'));
62 | ```
63 |
--------------------------------------------------------------------------------
/exercises/views/problem.ja.md:
--------------------------------------------------------------------------------
1 | '`/?name=Handling`'というリクエストに対し、テンプレート(`templates/index.html`)を
2 | 使ってレスポンスするサーバーを作ります。このテンプレートは以下のHTMLを出力します。
3 |
4 | ```html
5 |
6 | Hello Handling
7 |
8 | Hello Handling
9 |
10 |
11 | ```
12 |
13 | -----------------------------------------------------------------
14 | ## ヒント
15 |
16 | このエクササイズでは、`@hapi/vision`モジュールが必要となります。これはhapiのプラグインで、
17 | テンプレートを処理するためのものです。このモジュールを使用するためには、下記のように
18 | コード中でプラグインを登録する必要があります。
19 |
20 | ```js
21 | var Vision = require('@hapi/vision');
22 |
23 | await server.register(Vision);
24 | ```
25 |
26 | ハンドラのviewキーを使用し、レスポンスの生成に使用するテンプレートを指定します。
27 |
28 | ```js
29 | handler: {
30 | view: "index.html"
31 | }
32 | ```
33 |
34 | サーバーメソッド'`server.views()`'を使い、サーバー中にあるテンプレートの
35 | 設定を行うことが出来ます。このメソッドは設定オブジェクト(configurationオブジェクト)
36 | を引数にとります。このオブジェクトで以下を設定することが出来ます。
37 |
38 | * 各ファイル拡張子に対する、テンプレートエンジン
39 | * テンプレートファイルのあるディレクトリへのパス
40 |
41 | ```js
42 | server.views({
43 | engines: {
44 | html: require('handlebars')
45 | },
46 | path: Path.join(__dirname, 'templates')
47 | });
48 | ```
49 |
50 | このエクササイズではHandlebarsを使用します。以下のコマンドでHanlebarsを
51 | インストールすることが出来ます。
52 |
53 | ```sh
54 | npm install handlebars
55 | ```
56 |
57 | Handlebarsテンプレートを使用することにより、HTML中に変数を直接書くことが
58 | 出来ます。変数は2重の中括弧で囲む形式で記述します。(例: `{{foo}}`)
59 |
60 | テンプレートはリクエストから情報を得ます。例えばURLを通じて渡される
61 | クエリパラメータです。このクエリパラメータは`query`オブジェクトとなり、
62 | テンプレート中で利用可能となります。クエリパラメータは自動的に解釈・処理
63 | されます。ルートの'`path`'で宣言は行いません。
64 |
65 |
66 | ```html
67 | {{query.paramName}}
68 | ```
69 |
--------------------------------------------------------------------------------
/exercises/validation_using_joi_object/problem.ko.md:
--------------------------------------------------------------------------------
1 | `Joi`를 사용하면 경로, request, response에서의 유효성 검사를 고도로 커스터마이징 할 수 있습니다.
2 |
3 | 로그인 endpoint를 제공하고 `/login`에 HTTP `POST` request가 보내졌을 때 "login successful"이라고 응답하는 서버를 만드세요.
4 |
5 | endpoint는 request에서 다음 변수들을 받아들입니다.
6 |
7 | `isGuest` (불리언)
8 | `username` (문자열)
9 | `accessToken` (영숫자)
10 | `password` (영숫자)
11 |
12 | 유효성 검사는 다음 조건으로 구성돼야 합니다.
13 |
14 | i) `isGuest`가 거짓이라면, `username`가 필요하다.
15 | ii) `password`는 `accessToken`과 동시에 사용할 수 없다.
16 | iii) 위의 기재된 매개 변수 이외의 것들이 보내지면 유효성 검사를 통과시킨다.
17 |
18 | 유효성 검사가 성공적이라면, handler는 `login successful`라는 문자열을 반환해야 한다.
19 |
20 | -----------------------------------------------------------------
21 | ## 힌트
22 |
23 | `8080` 포트를 사용하는 서버를 만드세요. route 설정을 다음과 같습니다.
24 |
25 | ```js
26 |
27 | var routeConfig = {
28 | path: '/a/path/',
29 | method: 'POST',
30 | handler: myHandler,
31 | config: {
32 | validate: {
33 | payload: Joi.object({
34 | username: Joi.string(),
35 | password: Joi.string().alphanum(),
36 | accessToken: Joi.string().alphanum(),
37 | birthyear: Joi.number().integer().min(1900).max(2013),
38 | email: Joi.string().email()
39 | })
40 | .options({allowUnknown: true})
41 | .with('username', 'birthyear')
42 | .without('password', 'accessToken')
43 | }
44 | }
45 | }
46 | ```
47 |
48 | route에 관한 자세한 정보는 다음을 참조하세요.
49 |
50 | https://hapijs.com/api
51 |
52 | Joi에 관한 자세한 정보는 다음을 참조하세요.
53 |
54 | {rootdir:/node_modules/joi/README.md}
55 |
--------------------------------------------------------------------------------
/exercises/hello_hapi/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur hapi qui écoute sur le numéro de port passé dans
2 | la ligne de commande, et répond par « Bonjour hapi » aux requêtes
3 | HTTP GET envoyées sur `/`.
4 |
5 | L’exercice enverra des requêtes à votre serveur et vérifiera leurs réponses.
6 |
7 | -----------------------------------------------------------------
8 |
9 | ## Conseils
10 |
11 | Créez pour commencer un serveur qui écoute par défaut sur le port 8080,
12 | sauf si on lui passe un numéro de port explicite via la ligne de commande,
13 | à l’aide du code suivant :
14 |
15 | ```js
16 | var Hapi = require('@hapi/hapi');
17 | var server = Hapi.Server({
18 | host: 'localhost',
19 | port: Number(process.argv[2] || 8080)
20 | });
21 | ```
22 |
23 | On ajoute les routes (les chemins des points d’accès) à l’aide de la
24 | fonction `route()` :
25 |
26 | ```js
27 | server.route({ path: '/', method: 'GET', handler: anonOrYourFunction });
28 | ```
29 |
30 | Les gestionnaires de requête peuvent être des fonctions anonymes, ou des
31 | fonctions déclarées ailleurs (c’est du JavaScript, quoi :P), mais tous
32 | doivent avoir la même signature :
33 |
34 | ```js
35 | function handler(request, reply) {
36 |
37 | // `request` a toutes les informations
38 | // `reply` gère la réponse au client
39 |
40 | reply({ mustFlow: true });
41 | }
42 | ```
43 |
44 | Pour que le serveur commence à écouter sur le port défini au préalable,
45 | appelez la fonction `start()` :
46 |
47 | ```js
48 | await server.start();
49 |
50 | console.log(`Serveur fonctionnant à: ${server.info.uri}`);
51 | ```
52 | -----------------------------------------------------------------
53 |
--------------------------------------------------------------------------------
/exercises/uploads/problem.md:
--------------------------------------------------------------------------------
1 | Create a server with an endpoint that accepts an uploaded file to the following
2 | path:
3 |
4 | ```
5 | /upload
6 | ```
7 |
8 | The endpoint should accept the following keys: description and file. The
9 | ```description``` field should be a string describing whatever you want, and
10 | ```file``` should be an uploaded file. The endpoint should return a JSON object
11 | that follows the following pattern:
12 |
13 | ```json
14 | {
15 | description : //description from form
16 | file : {
17 | data : //content of file uploaded
18 | filename: //name of file uploaded
19 | headers : //file header provided by hapi
20 | }
21 | }
22 | ```
23 |
24 | -----------------------------------------------------------------
25 | ##HINTS
26 |
27 | To accept a file as input, your request should use the ```multipart/form-data```
28 | header.
29 |
30 | We can get a file as readable stream by adding the following in the route
31 | configuration:
32 |
33 | ```js
34 |
35 | payload: {
36 | output : 'stream',
37 | parse : true
38 | }
39 | ```
40 |
41 | If we've uploaded the file with the parameter ```file```, then we can access it
42 | in the handler function using following code:
43 |
44 | ```js
45 | handler: function (request, reply) {
46 | var body = '';
47 | request.payload.file.on('data', function (data){
48 |
49 | body += data
50 | });
51 |
52 | request.payload.file.on('end', function (){
53 |
54 | console.log(body);
55 | });
56 | }
57 | ```
58 |
59 | More information about file uploading can be found in the reply interface of the
60 | hapi [API docs](http://hapijs.com/api#reply-interface).
61 |
62 |
--------------------------------------------------------------------------------
/exercises/cookies/problem.ja.md:
--------------------------------------------------------------------------------
1 | `set-cookie`と`check-cookie`の2つのエンドポイントを提供するサーバーを
2 | 作成します。これらのエンドポイントは`GET`リクエストによりアクセス可能で、
3 | 以下のようにアクセスします。
4 |
5 | ```
6 | /set-cookie
7 | ```
8 |
9 | `set-cookie`エンドポイントは'session'というキーに対し、値として
10 | `{ key: 'makemehapi' }`をクッキーに設定します。クッキーの条件は
11 | 以下の通りです。
12 |
13 | * `base64json`としてエンコードする
14 | * 有効期間は`10 ms`
15 | * スコープのドメインは`localhost`
16 |
17 | レスポンスはこのエクササイズでは特に重要ではありませんので、何でも
18 | 構いません。
19 |
20 | ```
21 | /check-cookie
22 | ```
23 |
24 | `check-cookie`エンドポイントは`/set-cookie`エンドポイントで受け取った
25 | クッキーを使用します。`session`キーがクッキーに存在していれば、
26 | `{ user: 'hapi' }`を返し、それ以外は`unauthorized`のアクセス
27 | エラーを返します。
28 |
29 | --------------------
30 |
31 | ## ヒント
32 |
33 | '`server.route()`'関数では以下のようなオプションが必要となるでしょう。
34 |
35 | ```js
36 | config: {
37 | state: {
38 | parse: true,
39 | failAction: 'log'
40 | }
41 | }
42 | ```
43 |
44 | このオプションを使用することにより、サーバーはクッキーを様々な方法で取り扱う
45 | ことが出来ます。
46 |
47 | `hapi`は特定のURLに対するクッキーを管理する機能を提供します。
48 |
49 | ```js
50 | server.state('session', {
51 | path: '/',
52 | });
53 | ```
54 |
55 | リクエストにリプライする際、以下のようなコードでクッキーをセットする
56 | ことが出来ます。
57 |
58 | ```js
59 | reply('success').state('session', 'session')
60 | ```
61 |
62 | クッキーの値はサーバーのstateに保存され、以下のようなコードでアクセス
63 | することが出来ます。
64 |
65 | ```js
66 | var session = request.state.session;
67 | ```
68 |
69 | クッキーの取り扱いに関しての詳細な情報は以下にあります。
70 |
71 | [API](http://hapijs.com/api).
72 |
73 | このエクササイズでは必要ありませんが、[Boom](https://www.npmjs.com/package/boom)
74 | を使用することにより、より簡単に`unauthorized`エラーを正しいHTTPステータスコードで
75 | 返すことが出来ます。
76 |
77 | ```js
78 | var Boom = require('boom');
79 | ```
80 |
81 | ```js
82 | reply(Boom.unauthorized('Missing authentication'));
83 | ```
84 |
--------------------------------------------------------------------------------
/exercises/extend/problem.md:
--------------------------------------------------------------------------------
1 | Hapi's request lifecycle gives multiple places to add functions to modify an
2 | inbound request. Functions can be tied into the extension points by using the
3 | following syntax:
4 |
5 | ```javascript
6 | server.ext('onRequest', function (request, next) {
7 |
8 | //do some things
9 |
10 | next();
11 | });
12 | ```
13 |
14 | The extension points are listed below:
15 |
16 | * onRequest - initial request, before routing
17 | * onPreAuth - by now, Hapi has:
18 | * looked up the route
19 | * parsed cookies
20 | * onPostAuth - by now, Hapi has:
21 | * authenticated request
22 | * parsed payload
23 | * authenticated payload
24 | * onPreHandler - by now, Hapi has:
25 | * validated path parameters
26 | * validated query
27 | * validated payload
28 | * onPostHandler - by now Hapi has:
29 | * handled route prerequisites
30 | * called the handler
31 | * onPreResponse
32 | * Validates payload
33 |
34 | For this exercise, create a function that will be added to a server on the
35 | 'onRequest' extension point. Have the function log the requested URL path to the
36 | console.
37 |
38 | The workshop will execute requests against the server and verify the output.
39 |
40 | -----------------------------------------------------------------
41 | ##HINTS
42 |
43 | After creating a server, add an extension point by calling the server's
44 | ```ext``` function.
45 |
46 | ```js
47 | server.ext(extensionPoint, function (request, next) { ...
48 | ```
49 |
50 | The path will be part of the inbound request. Ensure to call ```next();```
51 | within the extension handler!
52 | -----------------------------------------------------------------
53 |
--------------------------------------------------------------------------------
/exercises/validation/problem.fr.md:
--------------------------------------------------------------------------------
1 | La configuration des routes nous offre de nombreuses manières de personnaliser
2 | chaque point d’accès de notre application Hapi. Un de ces aspects concerne
3 | la validation des requêtes.
4 |
5 | La validation peut s’intéresser aux paramètres de chemin, au corps de la requête,
6 | ou à la réponse envoyée. Les objets qui pilotent la validation sont définis au
7 | moyen du framework de validation Joi.
8 |
9 | Créez un serveur qui configure une route exposant un point d’accès pour des
10 | poulets. Spécifiquement :
11 |
12 | ```
13 | /chickens
14 | ```
15 |
16 | Au sein de cette route, ajoutez un paramètre de chemin nommé `breed` (race),
17 | lequel dispose d’une validation dédiée dans la configuration de la route. Le
18 | vérificateur s’assurera simplement que l’objet de validation existe dans la
19 | configuration de route pour le paramètre `breed`, sans chercher plus loin.
20 |
21 | -----------------------------------------------------------------
22 |
23 | ## Conseils
24 |
25 | Le code suivant illustre à quoi peut ressembler une telle configuration :
26 |
27 | ```js
28 | var routeConfig = {
29 | path: '/un/chemin/{avec}/{des}/{parametres}',
30 | method: 'GET',
31 | handler: myHandler,
32 | config: {
33 | validate: {
34 | params: {
35 | with: Joi.string().required(),
36 | parameters: Joi.string().required()
37 | }
38 | }
39 | }
40 | }
41 | ```
42 |
43 | Toutes les façons de configurer une route sont consultables [ici](https://hapijs.com/api).
44 |
45 | Les informations sur les objets de validation Joi sont consutlables ici :
46 |
47 | {rootdir:/node_modules/joi/README.md}
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2014, Walmart and other contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * The names of any contributors may not be used to endorse or promote
12 | products derived from this software without specific prior written
13 | permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 | * * *
27 |
28 | The complete list of contributors can be found at: https://github.com/hapijs/makemehapi/graphs/contributors
29 |
--------------------------------------------------------------------------------
/exercises/helping/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur qui répond aux requêtes sur `/?name=assistant&suffix=!` en
2 | utilisant le gabarit de l’exercice VUES.
3 |
4 | Au lieu de placer le paramètre de la *query string* directement dans le template,
5 | créez un helper dans `helpers/helper.js` et utilisez-le dans le template pour
6 | afficher le paramètre de *query string* `name`.
7 |
8 | ```html
9 |
10 | Bonjour assistant!
11 |
12 | Bonjour assistant!
13 |
14 |
15 | ```
16 |
17 | Le helper doit concaténer les paramètres de *query string* `name` et `suffix`.
18 |
19 | -----------------------------------------------------------------
20 |
21 | ## Conseils
22 |
23 | Les helpers sont des fonctions utilisées à l’intérieur des templates pour
24 | effectuer des transformations et des manipulations de données en utilisant le
25 | contexte du template et d’autres sources.
26 |
27 | Vous pouvez définir un chemin de helpers dans les options du serveur. Tous les
28 | fichiers `.js` de ce répertoire seront chargés et le nom de chaque fichier sera
29 | utilisé comme nom du helper fourni.
30 |
31 | ```js
32 | var options = {
33 | views: {
34 | …
35 | helpersPath: 'helpers'
36 | }
37 | };
38 | ```
39 |
40 | Chaque fichier doit exporter une unique fonction avec comme signature
41 | `function(context)`, qui renverra une `String`. La requête que vous
42 | manipuleriez d’habitude dans un gestionnaire de requête est accessible
43 | via `context.data.root`.
44 |
45 | ```
46 | module.exports = function(context) {
47 | return context.data.root.query.foo;
48 | }
49 | ```
50 |
51 | Votre fonction helper peut alors être utilisée dans le template :
52 |
53 | ```html
54 | {{helper}}
55 | ```
56 |
--------------------------------------------------------------------------------
/exercises/handling/problem.md:
--------------------------------------------------------------------------------
1 | Create a server which responds to requests to `/` with a static HTML file named
2 | `index.html` containing the following:
3 |
4 | ```html
5 |
6 | Hello Handling
7 |
8 | Hello Handling
9 |
10 |
11 | ```
12 |
13 | -----------------------------------------------------------------
14 | ##HINTS
15 |
16 | This exercise requires you to install the `@hapi/inert` module, which is a hapi
17 | plugin for serving static files and directories. You'll need to register the
18 | plugin in your code in order to serve static files:
19 |
20 | ```js
21 | var Inert = require('@hapi/inert');
22 |
23 | await server.register(Inert);
24 | ```
25 |
26 | You can declare handlers as objects instead of functions. The object must
27 | contain one of the following: `file` (requires `@hapi/inert` plugin), `directory`
28 | (requires `@hapi/inert` plugin), `proxy` (requires `@hapi/h2o2` plugin), or
29 | `view` (requires `@hapi/vision` plugin).
30 |
31 | For example, `handler` can be assigned an object with the `file` key:
32 |
33 | ```js
34 | handler: {
35 | file: "index.html"
36 | }
37 | ```
38 |
39 | Be careful: in practice, you'll need to provide an absolute path to an
40 | `index.html` file in your program's directory. To achieve this, you'll probably
41 | need the `path` core module, its `join()` function, and the `__dirname` global
42 | variable.
43 |
44 | You can also configure a files base path in your server and just pass relative
45 | paths in the route. This is specially useful if you have multiple routes that
46 | respond with files.
47 |
48 | ## Docs
49 |
50 | - Hapi - Route options: https://hapi.dev/api#route-options
51 | - Hapi - Serving Static Content Tutorial: https://hapi.dev/tutorials/servingfiles
52 |
--------------------------------------------------------------------------------
/exercises/views/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur qui répond aux requêtes sur `/?name=Vues` à l’aide d’un
2 | gabarit (*template*) stocké dans `templates/index.html`, qui produira le HTML
3 | suivant :
4 |
5 | ```html
6 |
7 | Bonjour Vues
8 |
9 | Bonjour Vues
10 |
11 |
12 | ```
13 |
14 | -----------------------------------------------------------------
15 |
16 | ## Conseils
17 |
18 | La propriété `view` permet de définir un template à utiliser pour générer la
19 | réponse.
20 |
21 | ```js
22 | handler: {
23 | view: "index.html"
24 | }
25 | ```
26 |
27 | La méthode `server.views()` configure quant à elle la gestion des templates pour
28 | notre serveur. Elle reçoit en argument un objet de configuration qui permet
29 | d’associer divers moteurs à des extensions spécifiques de fichiers. Cet objet
30 | peut également spécifier le dossier des templates.
31 |
32 | ```js
33 | server.views({
34 | engines: {
35 | html: require('handlebars')
36 | },
37 | path: Path.join(__dirname, 'templates')
38 | });
39 | ```
40 |
41 | Dans cet exercice, nous utiliserons Handlebars. Pour l’installer :
42 |
43 | ```sh
44 | npm install handlebars
45 | ```
46 |
47 | Avec les templates Handlebars, vous pouvez injecter une variable directement
48 | dans le HTML en l’enrobant de double accolades, par exemple `{{foo}}`.
49 |
50 | Le template reçoit des informations issues de la requête. Par exemple, les
51 | paramètres de la *query string* présente dans l’URL lui sont passés via l’objet
52 | `query`. Ces paramètres peuvent alors être utilisés par le template. Ces
53 | paramètres sont automatiquement analysés et n’ont pas besoin d’une déclaration
54 | explicite dans le `path` de la route.
55 |
56 | ```html
57 | {{query.paramName}}
58 | ```
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "makemehapi",
3 | "version": "7.1.4",
4 | "repository": {
5 | "type": "git",
6 | "url": "git://github.com/ccarruitero/makemehapi"
7 | },
8 | "license": "BSD",
9 | "bin": {
10 | "makemehapi": "./bin/makemehapi"
11 | },
12 | "engines": {
13 | "node": ">=0.10.32"
14 | },
15 | "description": "Self guided workshops to teach you about hapi.",
16 | "main": "index.js",
17 | "scripts": {
18 | "start": "node ./bin/makemehapi.js",
19 | "lint": "eslint *.js exercises/**/**/*.js i18n/**/*.json",
20 | "test": "npm run lint && workshopper-adventure-test",
21 | "release": "standard-version"
22 | },
23 | "dependencies": {
24 | "@hapi/basic": "^7.0.0",
25 | "@hapi/boom": "^10.0.0",
26 | "@hapi/h2o2": "^10.0.0",
27 | "@hapi/hapi": "^21.0.0",
28 | "@hapi/inert": "^7.0.0",
29 | "@hapi/joi": "^17.1.1",
30 | "@hapi/vision": "^7.0.0",
31 | "bl": "^5.0.0",
32 | "colors-tmpl": "^1.0.0",
33 | "combined-stream": "^1.0.8",
34 | "form-data": "^4.0.0",
35 | "handlebars": "^4.7.2",
36 | "hyperquest": "^2.1.3",
37 | "rot13-transform": "^1.0.0",
38 | "through2": "^4.0.2",
39 | "workshopper-adventure": "^7.0.0",
40 | "workshopper-exercise": "^3.0.1",
41 | "workshopper-wrappedexec": "^0.1.3"
42 | },
43 | "keywords": [
44 | "hapijs",
45 | "hapi"
46 | ],
47 | "preferGlobal": true,
48 | "devDependencies": {
49 | "eslint": "^8.27.0",
50 | "eslint-config-standard": "^17.0.0",
51 | "eslint-plugin-import": "^2.18.0",
52 | "eslint-plugin-json": "^3.0.0",
53 | "eslint-plugin-node": "^11.0.0",
54 | "eslint-plugin-promise": "^6.1.1",
55 | "eslint-plugin-standard": "^5.0.0",
56 | "standard": "^17.0.0",
57 | "standard-version": "^9.0.0",
58 | "workshopper-adventure-test": "^1.2.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/exercises/uploads/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur avec un point d’accès qui accepte un téléversement (*upload*)
2 | de fichier sur le chemin suivant :
3 |
4 | ```
5 | /upload
6 | ```
7 |
8 | Le point d’accès doit accepter les clés suivantes : `description` et `file`. Le
9 | champ `description` doit contenir une `String` avec le texte que vous voulez,
10 | et `file` est le fichier à envoyer. La réponse doit être un objet JSON avec le
11 | motif suivant :
12 |
13 | ```json
14 | {
15 | description : // La description issue du formulaire
16 | file : {
17 | data : // Le contenu du fichier envoyé
18 | filename: // Le nom du fichier envoyé
19 | headers : // L’en-tête de fichier défini par Hapi
20 | }
21 | }
22 | ```
23 |
24 | -----------------------------------------------------------------
25 |
26 | ## Conseils
27 |
28 | Pour accepter un fichier en entrée, votre requête doit utiliser le type de
29 | contenu `multipart/form-data`, et votre gestionnaire doit décoder le contenu
30 | en fonction.
31 |
32 | On peut consulter ce fichier sous la forme d’un flux en lecture en ajoutant les
33 | réglages suivants à la configuration de notre route :
34 |
35 | ```js
36 |
37 | payload: {
38 | output : 'stream',
39 | parse : true
40 | }
41 | ```
42 |
43 | Si nous avons envoyé un fichier via le paramètre `file`, alors nous pourrons
44 | y accéder au sein du gestionnaire de la façon suivante :
45 |
46 | ```js
47 | handler: function (request, reply) {
48 | var body = '';
49 |
50 | request.payload.file.on('data', function (data){
51 | body += data
52 | });
53 |
54 | request.payload.file.on('end', function (){
55 | console.log(body);
56 | });
57 | }
58 | ```
59 |
60 | Vous trouverez plus d’information sur le téléversement de fichiers dans la
61 | documentation API pour l’interface de réponse:
62 |
63 | [la doc API en ligne](http://hapijs.com/api#reply-interface)
64 |
--------------------------------------------------------------------------------
/exercises/validation_using_joi_object/problem.md:
--------------------------------------------------------------------------------
1 | By using a `Joi` object we can specify highly customizable validation rules in
2 | paths, request payloads, and responses.
3 |
4 | Create a server exposing a login endpoint and reply with "login successful" when
5 | an HTTP `POST` request is sent to `/login`.
6 |
7 | The endpoint will accept following payload variables:
8 |
9 | ```isGuest``` (boolean)
10 | ```username``` (string)
11 | ```accessToken``` (alphanumeric)
12 | ```password``` (alphanumeric)
13 |
14 | Validation should consist of following conditions:
15 |
16 | i) if ```isGuest``` is false, a ```username``` is required.
17 | ii) ```password``` cannot appear together with ```accessToken```.
18 | iii) if any other parameters than specified above are sent, they should pass the validation.
19 |
20 | If the validation is successful, the handler must return a text of `login successful`
21 |
22 | -----------------------------------------------------------------
23 | ##HINTS
24 |
25 | Create a server that listens on port `8080` with the following code:
26 |
27 | ```js
28 |
29 | var routeConfig = {
30 | path: '/a/path/',
31 | method: 'POST',
32 | handler: myHandler,
33 | config: {
34 | validate: {
35 | payload: Joi.object({
36 | username: Joi.string(),
37 | password: Joi.string().alphanum(),
38 | accessToken: Joi.string().alphanum(),
39 | birthyear: Joi.number().integer().min(1900).max(2013),
40 | email: Joi.string().email()
41 | })
42 | .options({allowUnknown: true})
43 | .with('username', 'birthyear')
44 | .without('password', 'accessToken')
45 | }
46 | }
47 | }
48 | ```
49 |
50 | All route information can be found [here](https://hapijs.com/api).
51 |
52 | Joi information can be found here:
53 |
54 | {rootdir:/node_modules/joi/README.md}
55 |
--------------------------------------------------------------------------------
/exercises/cookies/problem.fr.md:
--------------------------------------------------------------------------------
1 | Créez un serveur qui a une configuration de route exposant des points d’accès
2 | `set-cookie` et `check-cookie`, accessibles via des requêtes GET, décrits
3 | ci-après.
4 |
5 | ```
6 | /set-cookie
7 | ```
8 |
9 | Le point d’accès `set-cookie` définit un cookie avec une clé `'session'` et une
10 | valeur `{ key: 'makemehapi' }`. Le cookie doit être encodé au format
11 | `base64json`, expirer au bout de 10ms et être associé au domaine `localhost`.
12 | La réponse à la requête est sans importance pour cette exercice : mettez ce
13 | que vous voulez.
14 |
15 | ```
16 | /check-cookie
17 | ```
18 |
19 | Le point d’accès `check-cookie` doit pouvoir recevoir les cookies définis par
20 | `/set-cookie`. Si la clé `'session'` est présente, renvoyez simplement comme
21 | réponse `{ user: 'hapi' }`, sinon renvoyez une erreur d’accès HTTP
22 | `unauthorized`.
23 |
24 | --------------------
25 |
26 | ## Conseils
27 |
28 | Dans votre appel à `server.route()`, vous utiliserez sans doute les options
29 | suivantes :
30 |
31 | ```js
32 | config: {
33 | state: {
34 | parse: true,
35 | failAction: 'log'
36 | }
37 | }
38 | ```
39 |
40 | Grâce à celles-ci, vous pourrez configurer votre serveur pour traiter les
41 | cookies de la façon qui vous convient.
42 |
43 | Hapi permet une gestion des cookies par chemin d’URL spécifique, par exemple :
44 |
45 | ```js
46 | server.state('session', {
47 | path: '/',
48 | });
49 | ```
50 |
51 | Vous pouvez définir des cookies lors de la réponse, comme ceci :
52 |
53 | ```js
54 | reply('success').state('session', 'session')
55 | ```
56 |
57 | Les valeurs de cookies sont stockées dans l’état serveur, et accessibles avec
58 | du code comme celui-ci :
59 |
60 | ```js
61 | var session = request.state.session;
62 | ```
63 |
64 | Vous trouverez davantage d’informations sur la gestion des cookies dans la
65 | documentation de l’API de Hapi en ligne:
66 |
67 | [Documentation API](http://hapijs.com/api)
68 |
--------------------------------------------------------------------------------
/exercises/views/problem.md:
--------------------------------------------------------------------------------
1 | Create a server which responds to requests to `/?name=Handling` using a template
2 | located at `templates/index.html` which outputs the following HTML:
3 |
4 | ```html
5 |
6 | Hello Handling
7 |
8 | Hello Handling
9 |
10 |
11 | ```
12 |
13 | -----------------------------------------------------------------
14 | ##HINTS
15 |
16 | This exercise requires you to install the `@hapi/vision` module, which is a hapi
17 | plugin for rendering templates. You'll need to register the plugin in your code
18 | in order to render your templates:
19 |
20 | ```js
21 | var Vision = require('@hapi/vision');
22 |
23 | await server.register(Vision);
24 | ```
25 |
26 | The `view` key can be used to define the template to be used to generate the
27 | response.
28 |
29 | ```js
30 | handler: {
31 | view: "index.html"
32 | }
33 | ```
34 |
35 | `server.views()` is the server method that we use to configure the templates
36 | used on our server. This method receives a configuration object in which we can
37 | set different engines based on file extension. This object can also set a
38 | directory path for your templates.
39 |
40 | ```js
41 | server.views({
42 | engines: {
43 | html: require('handlebars')
44 | },
45 | path: Path.join(__dirname, 'templates')
46 | });
47 | ```
48 |
49 | In this exercise, we'll be using Handlebars. To install handlebars:
50 |
51 | ```sh
52 | npm install handlebars
53 | ```
54 |
55 | With Handlebars templates, you can render a variable directly in HTML by
56 | surrounding the variable with curly braces, e.g. `{{foo}}`.
57 |
58 | The template receives some information from the request. For example, the query
59 | parameters that were passed in via the URL are available in the `query` object.
60 | These parameters can then be used in the template. Query params get
61 | automatically parsed and aren't declared in the route `path`.
62 |
63 | ```html
64 | {{query.paramName}}
65 | ```
66 |
--------------------------------------------------------------------------------
/exercises/validation_using_joi_object/problem.fr.md:
--------------------------------------------------------------------------------
1 | En utilisant un objet Joi nous pouvons spécifier des règles de validation
2 | hautement personnalisables pour les chemins, le contenu des requêtes, ou même
3 | les réponses.
4 |
5 | Créez un serveur exposant un point d’accès d’authentification qui répond par
6 | « authentification réussie » quand une requête HTTP POST est envoyée sur
7 | le chemin `/login`.
8 |
9 | Le point d’accès accepte les variables de requête (formulaire) suivantes :
10 |
11 | ```isGuest``` (booléen)
12 | ```username``` (chaîne de caractères)
13 | ```accessToken``` (alphanumérique)
14 | ```password``` (alphanumérique)
15 |
16 | La validation doit exprimer les conditions suivantes :
17 |
18 | 1) Si `isGuest` est `false`, un `username` est requis.
19 | 2) `password` ne peut pas être fourni en même temps que `accessToken`.
20 | 3) Si d’autres paramètres que ceux spécifiés ci-dessus sont fournis, ils sont
21 | acceptés d’office.
22 |
23 | -----------------------------------------------------------------
24 |
25 | ## Conseils
26 |
27 | Le code suivant illustre une validation complexe :
28 |
29 | ```js
30 | var routeConfig = {
31 | path: '/a/path/',
32 | method: 'POST',
33 | handler: myHandler,
34 | config: {
35 | validate: {
36 | payload: Joi.object({
37 | username: Joi.string(),
38 | password: Joi.string().alphanum(),
39 | accessToken: Joi.string().alphanum(),
40 | birthyear: Joi.number().integer().min(1900).max(2013),
41 | email: Joi.string().email()
42 | })
43 | .options({ allowUnknown: true })
44 | .with('username', 'birthyear')
45 | .without('password', 'accessToken')
46 | }
47 | }
48 | }
49 | ```
50 |
51 | Toutes les façons de configurer une route sont consultables [ici](https://hapijs.com/api).
52 |
53 | Les informations sur les objets de validation Joi sont consutlables ici :
54 |
55 | {rootdir:/node_modules/joi/README.md}
56 |
--------------------------------------------------------------------------------
/exercises/cookies/problem.md:
--------------------------------------------------------------------------------
1 | Create a server that has a route configuration exposing an endpoint ``set-
2 | cookie`` and ``check-cookie`` which can be accessed using a `'GET'` request.
3 | Specifically:
4 |
5 | ```
6 | /set-cookie
7 | ```
8 |
9 | The `set-cookie` endpoint will set a cookie with the key 'session' and the value
10 | `{ key: 'makemehapi' }`. The cookie should be `base64json` encoded, should
11 | expire in `10 ms`, and have a domain scope of `localhost`. The response is
12 | unimportant for this exercise, and may be anything you like.
13 |
14 | ```
15 | /check-cookie
16 | ```
17 |
18 | The `check-cookie` endpoint will have cookies received from the `/set-cookie`
19 | endpoint. If the `session` key is present in cookies then simply return
20 | `{ user: 'hapi' }`, otherwise return an `unauthorized` access error.
21 |
22 | --------------------
23 |
24 | ##HINTS
25 |
26 | In your `server.route()` function, you may add the following option:
27 |
28 | ```js
29 | config: {
30 | state: {
31 | parse: true,
32 | failAction: 'log'
33 | }
34 | }
35 | ```
36 |
37 | By using this option, we can configure the server to handle cookies in various ways.
38 |
39 | `hapi` provides a way to manage cookies for a specific url path.
40 |
41 | ```js
42 | server.state('session', {
43 | path: '/',
44 | });
45 | ```
46 |
47 | We can set cookies while replying to request as follows:
48 |
49 | ```js
50 | reply('success').state('session', 'session')
51 | ```
52 |
53 | Cookie values are stored in server state, accessible using following code:
54 |
55 | ```js
56 | var session = request.state.session;
57 | ```
58 |
59 | More information about handling of cookies in `hapi` can be found in the Hapi
60 | documentation here [API](http://hapijs.com/api).
61 |
62 | While not required for this exercise, you may use [Boom](https://www.npmjs.com/package/boom)
63 | to more easily return an `unauthorized` error along with the correct HTTP status
64 | code:
65 |
66 | ```js
67 | var Boom = require('boom');
68 | ```
69 |
70 | ```js
71 | reply(Boom.unauthorized('Missing authentication'));
72 | ```
73 |
--------------------------------------------------------------------------------
/lib/exercise.js:
--------------------------------------------------------------------------------
1 | const through2 = require('through2');
2 | const hyperquest = require('hyperquest');
3 | const bl = require('bl');
4 | let exercise = require('workshopper-exercise')();
5 | const filecheck = require('workshopper-exercise/filecheck');
6 | const execute = require('workshopper-exercise/execute');
7 | const comparestdout = require('workshopper-exercise/comparestdout');
8 | const verifyStatusProcessor = require('./verifyStatusProcessor');
9 | const { rndport } = require('./utils');
10 |
11 | exercise.longCompareOutput = true;
12 |
13 | exercise = filecheck(exercise);
14 |
15 | exercise = execute(exercise);
16 |
17 | exercise.addSetup(function (mode, callback) {
18 | this.submissionPort = rndport();
19 | this.solutionPort = this.submissionPort + 1;
20 |
21 | this.submissionArgs = [this.submissionPort];
22 | this.solutionArgs = [this.solutionPort];
23 |
24 | this.submissionResult = {};
25 |
26 | process.nextTick(callback);
27 | });
28 |
29 | exercise.addProcessor(function (mode, callback) {
30 | this.submissionStdout.pipe(process.stdout);
31 |
32 | // replace stdout with our own streams
33 | this.submissionStdout = through2();
34 | if (mode === 'verify') {
35 | this.solutionStdout = through2();
36 | }
37 |
38 | setTimeout(query.bind(this, mode), 2000);
39 |
40 | process.nextTick(() => {
41 | callback(null, true);
42 | });
43 | });
44 |
45 | exercise = comparestdout(exercise);
46 |
47 | exercise = verifyStatusProcessor(exercise);
48 |
49 | function killChild (exercise) {
50 | [ exercise.submissionChild, exercise.solutionChild ].forEach(function (child) {
51 | if (child && typeof child.kill == 'function') child.kill()
52 | })
53 | }
54 |
55 | // delayed for 2000ms to wait for servers to start so we can start
56 | // playing with them
57 | function query (mode) {
58 | const exercise = this;
59 |
60 | function connect (port, stream, result = null) {
61 | const url = exercise.queryUrl(port);
62 |
63 | function error (err) {
64 | const msg = exercise.__('fail.cannot_connect', { port, code: err.code });
65 | exercise.emit('fail', msg);
66 | killChild(exercise);
67 | }
68 |
69 | const opts = Object.assign({ method: 'GET' }, exercise.requestOpts || {});
70 |
71 | const request = hyperquest(url, opts)
72 | .on('error', error)
73 | .on('response', (res) => {
74 | if (result) {
75 | result.response = res;
76 | result.url = url;
77 | }
78 | });
79 |
80 | const handleInput = exercise.handleInput;
81 | if (handleInput && (typeof handleInput === 'function')) {
82 | handleInput(request, stream, port);
83 | } else {
84 | request.pipe(stream);
85 | }
86 | }
87 |
88 | connect(this.submissionPort, this.submissionStdout, this.submissionResult);
89 |
90 | if (mode === 'verify') {
91 | connect(this.solutionPort, this.solutionStdout);
92 | }
93 | }
94 |
95 | module.exports = exercise;
96 |
--------------------------------------------------------------------------------