SlideShare a Scribd company logo
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
LITTLE
BIG
ПРОЕКТ
Акт 1
:
Нам нужна игра
Нажмите любую клавишу
15 декабря 2020
Раз в год мы делаем конференцию
.
.
.
Все устали от онлайна, но мы хотим:
Затащить всех в онлайн на неделю


Наполнить чат людьми и сообщениями


Вернуть всех в день конференции
CTF
CTF — Capture The F
l
ag
Соревнования по поиску уязвимостей


Task-Based, Web
-
only


Каждая задача приносит флаг


Кто первым взял все флаги — победил
Привет. Предлагаю
сделать CTF на


Я💛фронтенд
А когда он будет?
В феврале
Я в деле!
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
Концепция
Делаем силами 2-3 человек + дизайнеры + редактор


Только фронтенд
-
технологии


Клиент: CRA


Бэкенд: Node.js


Задачи(загадки)
:
На чём быстрее
7 февраля 2021
Ща бэк буду
деплоить первый
раз
Вроде работает,
начинаем пилить
Акт 2
:
Успеть за 2 недели
Нажмите любую клавишу
https:
/
/
twitter.com/bobuk/status/636252417089212416
Метод Микеланджело
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
DevOps
Хостинг: VPS (так привычнее)


Флоу разработки: Trunk Based Development (так быстрее)


Автоматизация: GitHub Actions (так дешевле)


Демонизация Node.js: PM2 (так проще)


SSL
:
Certbot (быстро и бесплатно!)
Автоматизация и тесты!
@bluecoders
William Erhel
Монорепозиторий
Шеринг контрактов


Общий конфиг


Единовременный деплой


Простота локальной разработки
Монорепозиторий это просто! (Папочки и никакого тулинга)
./backend


./frontend


./tasks


./nginx
:
)
Главный контракт — это файл конфигурации
{


"initialLevelName": "home.morging",


"levels": {


"home.morging": {


"keys": ["god", "plague", "hacker"],


"redirectURL": "/",


"folder": "level_1",


"nextLevel": "off
i
ce.afternoon"


},


.
.
.


}
Главный контракт — это файл конфигурации
{


"initialLevelName": "home.morging",


"levels": {


"home.morging": {


"keys": ["god", "plague", "hacker"],


"redirectURL": "/",


"folder": "level_1",


"nextLevel": "off
i
ce.afternoon"


},


.
.
.


}
Главный контракт — это файл конфигурации
{


"initialLevelName": "home.morging",


"levels": {


"home.morging": {


"keys": ["god", "plague", "hacker"],


"redirectURL": "/",


"folder": "level_1",


"nextLevel": "off
i
ce.afternoon"


},


.
.
.


}
Главный контракт — это файл конфигурации
{


"initialLevelName": "home.morging",


"levels": {


"home.morging": {


"keys": ["god", "plague", "hacker"],


"redirectURL": "/",


"folder": "level_1",


"nextLevel": "off
i
ce.afternoon"


},


.
.
.


}
Главный контракт — это файл конфигурации
{


"initialLevelName": "home.morging",


"levels": {


"home.morging": {


"keys": ["god", "plague", "hacker"],


"redirectURL": "/",


"folder": "level_1",


"nextLevel": "off
i
ce.afternoon"


},


.
.
.


}
Главный контракт — это файл конфигурации
{


"initialLevelName": "home.morging",


"levels": {


"home.morging": {


"keys": ["god", "plague", "hacker"],


"redirectURL": "/",


"folder": "level_1",


"nextLevel": "off
i
ce.afternoon"


},


.
.
.


}
Главный контракт — это файл конфигурации
{


"initialLevelName": "home.morging",


"levels": {


"home.morging": {


"keys": ["god", "plague", "hacker"],


"redirectURL": "/",


"folder": "level_1",


"nextLevel": "off
i
ce.afternoon"


},


.
.
.


}
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
Фронтенд
Каждый уровень — SPA


Выдача заданий


Проверка флагов
Бэкенд
Выдача нужного уровня (и защита от читерства)


Проверка флагов


Логирование


Метрики
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
Уровень (дешёвое решение)
Независимое SPA


Не знает о текущем состоянии приложения


Не знает о пользователе


Бизнес
-
логика на сервере
Борис
Решил все задачи первый
Богдан
Получил ссылку от Бориса
на последний уровень
Никаких состояний в URL
Защита
Все уровни выдаются по одному URL (корневому)


На одном URL выдаются разные SPA


Сервер решает, какой index.html отдать


Перескочить между уровнями подменой URL невозможно
Где будем хранить состояние?
В базе данных — дорого
:
(


Делать авторизацию — долго
:
(


Cookie — дёшево и быстро! =)
Борис
Решил все задачи первый
Богдан
Получил cookie от Бориса
и попал на последний
уровень
Cookie легко сломать
Решение в лоб
Хранение отпечатка браузера — дорого и хрупко
:
(


Мусорные Cookie
-
обманки


Cookie
-
метка и запись в логах
@Get('')


	
getLevel(@Req() req: Request, @Res() res: Response) {


	
	
const levelCookie = req.cookies[LEVEL_COOKIE];


	
	
const userCookie = req.cookies[USER_COOKIE];


	
	
if (!userCookie) { res.cookie(USER_COOKIE, uuidv4()); }


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/index.html`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
	
})


	
}


Бизнес
-
логика выдачи уровня
@Get('')


	
getLevel(@Req() req: Request, @Res() res: Response) {


	
	
const levelCookie = req.cookies[LEVEL_COOKIE];


	
	
const userCookie = req.cookies[USER_COOKIE];


	
	
if (!userCookie) { res.cookie(USER_COOKIE, uuidv4()); }


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/index.html`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
	
})


	
}


Бизнес
-
логика выдачи уровня
@Get('')


	
getLevel(@Req() req: Request, @Res() res: Response) {


	
	
const levelCookie = req.cookies[LEVEL_COOKIE];


	
	
const userCookie = req.cookies[USER_COOKIE];


	
	
if (!userCookie) { res.cookie(USER_COOKIE, uuidv4()); }


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/index.html`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
	
})


	
}


Бизнес
-
логика выдачи уровня
@Get('')


	
getLevel(@Req() req: Request, @Res() res: Response) {


	
	
const levelCookie = req.cookies[LEVEL_COOKIE];


	
	
const userCookie = req.cookies[USER_COOKIE];


	
	
if (!userCookie) { res.cookie(USER_COOKIE, uuidv4()); }


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/index.html`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
	
})


	
}


Бизнес
-
логика выдачи уровня
@Get('')


	
getLevel(@Req() req: Request, @Res() res: Response) {


	
	
const levelCookie = req.cookies[LEVEL_COOKIE];


	
	
const userCookie = req.cookies[USER_COOKIE];


	
	
if (!userCookie) { res.cookie(USER_COOKIE, uuidv4()); }


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/index.html`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
	
})


	
}


Бизнес
-
логика выдачи уровня
@Get('')


	
getLevel(@Req() req: Request, @Res() res: Response) {


	
	
const levelCookie = req.cookies[LEVEL_COOKIE];


	
	
const userCookie = req.cookies[USER_COOKIE];


	
	
if (!userCookie) { res.cookie(USER_COOKIE, uuidv4()); }


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/index.html`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
	
})


	
}


Бизнес
-
логика выдачи уровня
@Get('')


	
getLevel(@Req() req: Request, @Res() res: Response) {


	
	
const levelCookie = req.cookies[LEVEL_COOKIE];


	
	
const userCookie = req.cookies[USER_COOKIE];


	
	
if (!userCookie) { res.cookie(USER_COOKIE, uuidv4()); }


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/index.html`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
	
})


	
}


Бизнес
-
логика выдачи уровня
Статика (классический вариант)
ctf.ilovefrontend.ru
Node.js
SSR HTML
FS
JS, CSS, etc.
Nginx
@Get('static/:type/:f
i
lename')


	
getStatic(


	
	
@Res() res: Response,


	
	
@Param('type') type: string,


	
	
@Param('f
i
lename') f
i
lename: string


	
) {


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/static/${type}/${f
i
lename}`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
})}


Статика (наш случай)
@Get('static/:type/:f
i
lename')


	
getStatic(


	
	
@Res() res: Response,


	
	
@Param('type') type: string,


	
	
@Param('f
i
lename') f
i
lename: string


	
) {


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/static/${type}/${f
i
lename}`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
})}


Статика (наш случай)
@Get('static/:type/:f
i
lename')


	
getStatic(


	
	
@Res() res: Response,


	
	
@Param('type') type: string,


	
	
@Param('f
i
lename') f
i
lename: string


	
) {


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/static/${type}/${f
i
lename}`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
})}


Статика (наш случай)
@Get('static/:type/:f
i
lename')


	
getStatic(


	
	
@Res() res: Response,


	
	
@Param('type') type: string,


	
	
@Param('f
i
lename') f
i
lename: string


	
) {


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/static/${type}/${f
i
lename}`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
})}


Статика (наш случай)
@Get('static/:type/:f
i
lename')


	
getStatic(


	
	
@Res() res: Response,


	
	
@Param('type') type: string,


	
	
@Param('f
i
lename') f
i
lename: string


	
) {


	
	
const levelFolder = this.levelService.getLevelFolder(levelCookie);


	
	
const f
i
lepath = `${levelFolder}/static/${type}/${f
i
lename}`;


	
	
fs.access(f
i
lepath, fs.constants.F_OK, (err)
=
>
{


	
	
	
fs.createReadStream(f
i
lepath).pipe(res);


	
})}


Статика (наш случай)
API
/check
-
key Проверка отдельного флага


/check
-
level
-
done Проверка всех флагов уровня
Задания
Nginx
a.ilovefrontend.ru


b.ilovefrontend.ru
Задание B
HTML, CSS, etc.
Задание A
HTML, CSS, etc.
Задания
«Чистый» HTML/CSS


Логика на Express/Fastify (не интересно)


Логика на Nginx (быстро и дёшево!)
location / {


if ($arg_keyword = qwerty) {


return 302 /zbfg56ffh03445561hd.html;


}


try_f
i
les $uri $uri/ =404;


root /var/
w
w
w
/tasks/mona/mona
-
main;


}


«Serverless» на Nginx
location / {


if ($arg_keyword = qwerty) {


return 302 /zbfg56ffh03445561hd.html;


}


try_f
i
les $uri $uri/ =404;


root /var/
w
w
w
/tasks/mona/mona
-
main;


}


«Serverless» на Nginx
location / {


if ($arg_keyword = qwerty) {


return 302 /zbfg56ffh03445561hd.html;


}


try_f
i
les $uri $uri/ =404;


root /var/
w
w
w
/tasks/mona/mona
-
main;


}


«Serverless» на Nginx
map $http_user_agent $too_new {


default 1;


"~MSIE [1-9]." 0;


"~MSIE 10" 0;


"~rv:11.0" 0;


}


location / {


if ($too_new = 1) {


rewrite ^ /browser.html redirect;


}


}


Проверка браузера на Nginx (Вход только для IE)
map $http_user_agent $too_new {


default 1;


"~MSIE [1-9]." 0;


"~MSIE 10" 0;


"~rv:11.0" 0;


}


location / {


if ($too_new = 1) {


rewrite ^ /browser.html redirect;


}


}


Проверка браузера на Nginx (Вход только для IE)
Акт 3
:
Игровые механики
Нажмите любую клавишу
Задачи
Подсадить пользователя на крючок первых уровней


Не дать заскучать однообразными заданиями


Задержать прокачанных в CTF шустриков


Дать удовольствие от сложности
userService
authenticationService
authenticationService
userService
контент
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
Акт 4
:
Запуск и последствия
Нажмите любую клавишу
20 февраля 2021
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
Самая дешёвая метрика — это логи
tail
-
f ctf
-
back
-
out.log
Наблюдаем в реальном времени
grep
-
r "flag" . | wc
-
l
Считаем взятия флага
this.logger.log(`user: ${userCookie} keys: ${keys.toString()}`);
Размечаем событие в логах
27 февраля 2021
384 раз


игра была пройдена
* На момент времени: 26 февраля, пятница, 20
:
45, 2021 год
Статистика прохождений по уровням
"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov
Какой доклад был самым интересным?


Разбор заданий Capture the flag и награждение победителей
Фронтендеры — вы невероятные!
Отдельное спасибо Никите Прокопову
{flag_see_you_space_cowboy}
Теперь питание компьютера
можно отключить

More Related Content

KEY
Node.js - Best practices
PDF
Introduction to Nodejs
PDF
Node.js - A Quick Tour
PDF
神に近づくx/net/context (Finding God with x/net/context)
PPTX
Windows PowerShell
PPT
PowerShell Technical Overview
KEY
Building a real life application in node js
Node.js - Best practices
Introduction to Nodejs
Node.js - A Quick Tour
神に近づくx/net/context (Finding God with x/net/context)
Windows PowerShell
PowerShell Technical Overview
Building a real life application in node js

What's hot (20)

KEY
A million connections and beyond - Node.js at scale
PPT
Node js presentation
PDF
Web Crawling with NodeJS
ODP
My app is secure... I think
PPT
Building your first Node app with Connect & Express
PDF
Introduction to Flask Micro Framework
PDF
Future Decoded - Node.js per sviluppatori .NET
PDF
Node js introduction
PPTX
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
PDF
Using Node.js to Build Great Streaming Services - HTML5 Dev Conf
PPT
Why and How Powershell will rule the Command Line - Barcamp LA 4
PDF
Understanding the Node.js Platform
PDF
Pycon - Python for ethical hackers
PDF
Testing Backbone applications with Jasmine
PDF
Beyond Phoenix
PDF
Asynchronous programming done right - Node.js
PPTX
ES6 is Nigh
PPTX
Avoiding callback hell in Node js using promises
PDF
PDF
Nodejs Explained with Examples
A million connections and beyond - Node.js at scale
Node js presentation
Web Crawling with NodeJS
My app is secure... I think
Building your first Node app with Connect & Express
Introduction to Flask Micro Framework
Future Decoded - Node.js per sviluppatori .NET
Node js introduction
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Using Node.js to Build Great Streaming Services - HTML5 Dev Conf
Why and How Powershell will rule the Command Line - Barcamp LA 4
Understanding the Node.js Platform
Pycon - Python for ethical hackers
Testing Backbone applications with Jasmine
Beyond Phoenix
Asynchronous programming done right - Node.js
ES6 is Nigh
Avoiding callback hell in Node js using promises
Nodejs Explained with Examples
Ad

More from Fwdays (20)

PDF
"Mastering UI Complexity: State Machines and Reactive Patterns at Grammarly",...
PDF
"Effect, Fiber & Schema: tactical and technical characteristics of Effect.ts"...
PPTX
"Computer Use Agents: From SFT to Classic RL", Maksym Shamrai
PPTX
"Як ми переписали Сільпо на Angular", Євген Русаков
PDF
"AI Transformation: Directions and Challenges", Pavlo Shaternik
PDF
"Validation and Observability of AI Agents", Oleksandr Denisyuk
PPTX
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
PDF
"Beyond English: Navigating the Challenges of Building a Ukrainian-language R...
PPTX
"Co-Authoring with a Machine: What I Learned from Writing a Book on Generativ...
PPTX
"Human-AI Collaboration Models for Better Decisions, Faster Workflows, and Cr...
PDF
"AI is already here. What will happen to your team (and your role) tomorrow?"...
PPTX
"Is it worth investing in AI in 2025?", Alexander Sharko
PDF
''Taming Explosive Growth: Building Resilience in a Hyper-Scaled Financial Pl...
PDF
"Scaling in space and time with Temporal", Andriy Lupa.pdf
PDF
"Database isolation: how we deal with hundreds of direct connections to the d...
PDF
"Scaling in space and time with Temporal", Andriy Lupa .pdf
PPTX
"Provisioning via DOT-Chain: from catering to drone marketplaces", Volodymyr ...
PPTX
" Observability with Elasticsearch: Best Practices for High-Load Platform", A...
PPTX
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
PPTX
"Istio Ambient Mesh in production: our way from Sidecar to Sidecar-less",Hlib...
"Mastering UI Complexity: State Machines and Reactive Patterns at Grammarly",...
"Effect, Fiber & Schema: tactical and technical characteristics of Effect.ts"...
"Computer Use Agents: From SFT to Classic RL", Maksym Shamrai
"Як ми переписали Сільпо на Angular", Євген Русаков
"AI Transformation: Directions and Challenges", Pavlo Shaternik
"Validation and Observability of AI Agents", Oleksandr Denisyuk
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
"Beyond English: Navigating the Challenges of Building a Ukrainian-language R...
"Co-Authoring with a Machine: What I Learned from Writing a Book on Generativ...
"Human-AI Collaboration Models for Better Decisions, Faster Workflows, and Cr...
"AI is already here. What will happen to your team (and your role) tomorrow?"...
"Is it worth investing in AI in 2025?", Alexander Sharko
''Taming Explosive Growth: Building Resilience in a Hyper-Scaled Financial Pl...
"Scaling in space and time with Temporal", Andriy Lupa.pdf
"Database isolation: how we deal with hundreds of direct connections to the d...
"Scaling in space and time with Temporal", Andriy Lupa .pdf
"Provisioning via DOT-Chain: from catering to drone marketplaces", Volodymyr ...
" Observability with Elasticsearch: Best Practices for High-Load Platform", A...
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
"Istio Ambient Mesh in production: our way from Sidecar to Sidecar-less",Hlib...
Ad

Recently uploaded (20)

PDF
Machine learning based COVID-19 study performance prediction
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
cuic standard and advanced reporting.pdf
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
A Presentation on Artificial Intelligence
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Modernizing your data center with Dell and AMD
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
NewMind AI Weekly Chronicles - August'25 Week I
Machine learning based COVID-19 study performance prediction
Diabetes mellitus diagnosis method based random forest with bat algorithm
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Advanced methodologies resolving dimensionality complications for autism neur...
Review of recent advances in non-invasive hemoglobin estimation
cuic standard and advanced reporting.pdf
Spectral efficient network and resource selection model in 5G networks
A Presentation on Artificial Intelligence
The Rise and Fall of 3GPP – Time for a Sabbatical?
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
CIFDAQ's Market Insight: SEC Turns Pro Crypto
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
20250228 LYD VKU AI Blended-Learning.pptx
Modernizing your data center with Dell and AMD
Chapter 3 Spatial Domain Image Processing.pdf
“AI and Expert System Decision Support & Business Intelligence Systems”
NewMind AI Weekly Chronicles - August'25 Week I

"The little big project. From zero to hero in two weeks with 3 front-end engineers only" Andrey Melikhov