Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@

Код приложения лежит в папке `spa`, собранная версия уже лежит в папке `spa/build`. Для выполнения задания трогать код приложения не потребуется, но если захочешь что-то поменять, не забудь установить зависимости (`npm install` в папке `spa`) и собрать новую версию приложения (`npm run build`).

0. Поставь зависимости и запусти сервер.
+++0. Поставь зависимости и запусти сервер.

- Для этого перейди в директорию задачи и выполни команду `npm install`.
- После установки зависимостей, выполни команду `npm run start`.
- После запуска, перейди по адресу [localhost:3000](http://localhost:3000)

1. Сделай так, чтобы сервер смог отдавать статические файлы из директории `spa/build`. В express для этого есть middleware `express.static`. Подробнее можно прочитать [здесь](https://expressjs.com/en/starter/static-files.html)
+++1. Сделай так, чтобы сервер смог отдавать статические файлы из директории `spa/build`. В express для этого есть middleware `express.static`. Подробнее можно прочитать [здесь](https://expressjs.com/en/starter/static-files.html)

2. Сделай так, чтобы при заходе на любой неизвестный адрес, сервер возвращал файл `spa/build/index.html`. В этом помогут специальные символы [в путях](https://expressjs.com/en/guide/routing.html#route-paths)
+++2. Сделай так, чтобы при заходе на любой неизвестный адрес, сервер возвращал файл `spa/build/index.html`. В этом помогут специальные символы [в путях](https://expressjs.com/en/guide/routing.html#route-paths)

3. Сделай так, чтобы наш сайт работал по https. В этом поможет [этот небольшой пост](https://timonweb.com/posts/running-expressjs-server-over-https/). Сертификат уже сгенерирован и лежит в папке `/certs`.
+++3. Сделай так, чтобы наш сайт работал по https. В этом поможет [этот небольшой пост](https://timonweb.com/posts/running-expressjs-server-over-https/). Сертификат уже сгенерирован и лежит в папке `/certs`.

Обрати внимание, что придётся разрешить Chrome работать с само-подписанными сертификатами для localhost. Это можно сделать включив флаг `chrome://flags/#allow-insecure-localhost`.

4. Изучи файл `client.mjs`. В нём лежит заготовка клиента, который будет делать запросы на сервер.
+++4. Изучи файл `client.mjs`. В нём лежит заготовка клиента, который будет делать запросы на сервер.

**!!! В этом интенсиве сначала надо реализовать логинизацию. Без этого остальные странички не будут отображаться**

Expand All @@ -36,19 +36,19 @@

Отправлять ответ можно с помощью [res.json](https://expressjs.com/en/4x/api.html#res.json).

5. Сохрани имя пользователя в [cookie](https://expressjs.com/en/4x/api.html#req.cookies) (не забудь подключить `cookie-parser` [middleware](https://expressjs.com/en/resources/middleware/cookie-parser.html)).
+++5. Сохрани имя пользователя в [cookie](https://expressjs.com/en/4x/api.html#req.cookies) (не забудь подключить `cookie-parser` [middleware](https://expressjs.com/en/resources/middleware/cookie-parser.html)).

Сделай так, чтобы методы `.getUser()`, `.loginUser()`, `.logoutUser()` работали с cookie

6. Сделай так, чтобы cookie с именем пользователя была `HttpOnly`, `Secure`, и имела `SameSite` политику `Strict`. В этом помогут дополнительные опции [res.cookie](https://expressjs.com/en/4x/api.html#res.cookie).
+++6. Сделай так, чтобы cookie с именем пользователя была `HttpOnly`, `Secure`, и имела `SameSite` политику `Strict`. В этом помогут дополнительные опции [res.cookie](https://expressjs.com/en/4x/api.html#res.cookie).

7. Сделай так, чтобы при заходе на любой роут приложения, кроме api, статики и `/login` без cookie происходил редирект на страницу `/login`.
+++7. Сделай так, чтобы при заходе на любой роут приложения, кроме api, статики и `/login` без cookie происходил редирект на страницу `/login`.

Для этого придётся написать `middleware` и проверять наличие cookie в запросе. Как написать узнай [здесь](https://expressjs.com/en/guide/writing-middleware.html).

Сделай так, чтобы middleware применялось только для путей, которые непосредственно отдают `index.html`

8. Оживи остальные страницы кроме `/sendToMars`. А именно `About`, `History`, `Rockets`, `Roadster`.
+++8. Оживи остальные страницы кроме `/sendToMars`. А именно `About`, `History`, `Rockets`, `Roadster`.

В качестве источника данных используй [публичное API](https://docs.spacexdata.com/). Методы в нём названы похожим образом.

Expand Down
27 changes: 17 additions & 10 deletions client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export class Client {
* @return {Promise<string | null>} username
* */
async getUser() {
throw new Error("Not implemented");
return fetch('api/user').then(async res=>{
return (await res.json())['username'];
})
}

/**
Expand All @@ -16,8 +18,13 @@ export class Client {
* @param {string} username
* @return {Promise<string | null>} username
* */
async loginUser(username) {
throw new Error("Not implemented");
async loginUser(username) {
return fetch(`api/login?username=${username}`).then(async res=>{
let json = await res.json();
let username = json['username'];
console.log("client/login: "+username)
return username;
})
}

/**
Expand All @@ -26,7 +33,7 @@ export class Client {
* @return {void}
* */
async logoutUser() {
throw new Error("Not implemented");
await fetch('api/logout');
}

/**
Expand All @@ -50,7 +57,7 @@ export class Client {
* @return {Promise<About>}
* */
async getInfo() {
throw new Error("Not implemented");
return (await fetch('https://api.spacexdata.com/v3/info')).json();
}

/**
Expand All @@ -63,7 +70,7 @@ export class Client {
* @return {Promise<EventBrief[]>}
* */
async getHistory() {
throw new Error("Not implemented");
return (await fetch('https://api.spacexdata.com/v3/history')).json();
}

/**
Expand All @@ -80,7 +87,7 @@ export class Client {
* @return {Promise<EventFull>}
* */
async getHistoryEvent(id) {
throw new Error("Not implemented");
return (await fetch(`https://api.spacexdata.com/v3/history/${id}`)).json();
}

/**
Expand All @@ -93,7 +100,7 @@ export class Client {
* @return {Promise<RocketBrief[]>}
* */
async getRockets() {
throw new Error("Not implemented");
return (await fetch('https://api.spacexdata.com/v3/rockets')).json();
}

/**
Expand All @@ -118,7 +125,7 @@ export class Client {
* @return {Promise<RocketFull>}
* */
async getRocket(id) {
throw new Error("Not implemented");
return (await fetch(`https://api.spacexdata.com/v3/rocket/${id}`)).json();
}

/**
Expand All @@ -135,7 +142,7 @@ export class Client {
* @return {Promise<Roadster>}
* */
async getRoadster() {
throw new Error("Not implemented");
return (await fetch('https://api.spacexdata.com/v3/roadster')).json();
}

/**
Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,18 @@
"devDependencies": {
"better-npm-run": "0.1.1",
"prettier": "^2.2.1"
}
},
"description": "В задании будем делать серверную часть и клиенсткие запросы для приложения SpaceY. Приложение представляет собой SPA (Single Page Application), которое умеет работать без перезагрузки страниц браузером.",
"version": "1.0.0",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/Irit-Basic-JS/9-space-y.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/Irit-Basic-JS/9-space-y/issues"
},
"homepage": "https://github.com/Irit-Basic-JS/9-space-y#readme"
}
51 changes: 47 additions & 4 deletions server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ import fetch from "node-fetch";
const rootDir = process.cwd();
const port = 3000;
const app = express();
const notRedirectingUrls = ['login', 'api', 'static'];

app.use(express.static('spa/build'))
app.use(cookieParser());

app.get('/*', (req, res, next) => {
const root = req.url.split('/')[1];
const shouldBeSkipped = notRedirectingUrls.includes(root);
const isFile = root.split('.').length > 1;
const haveCookie = typeof(req.cookies.username) !== 'undefined';

if (!shouldBeSkipped && !isFile && !haveCookie)
res.redirect('/login');
else next();
});

app.get("/client.mjs", (_, res) => {
res.header("Cache-Control", "private, no-cache, no-store, must-revalidate");
Expand All @@ -18,10 +33,38 @@ app.get("/client.mjs", (_, res) => {
});
});

app.get("/", (_, res) => {
res.send(":)");
app.get("/api/login", (req,res)=>{
console.log("api/login: start" );
let username = req.query.username;
console.log("api/login: " + username);
res.cookie('username', username, {httpOnly : true, secure : true, sameSite : "strict"});
res.json({'username':username});
});

app.listen(port, () => {
console.log(`App listening on port ${port}`);
app.get("/api/user", (req, res) => {
let username = req.cookies.username;
console.log("api/user: " + username);
res.json({'username':username});
});

app.get("/api/logout", (req, res)=>{
console.log('api/logout: start');
res.clearCookie('username');
res.redirect('/');
});

app.get('/*', (_, res) => {
res.redirect('/');
});

https
.createServer(
{
key: fs.readFileSync("certs/server.key"),
cert: fs.readFileSync("certs/server.cert"),
},
app
)
.listen(port, function() {
console.log(`Port is ${port}`)
})