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
1 change: 0 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#airbnb
root = true

[*]
Expand Down
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PORT=5000
BE_URL=http://localhost:5000
FE_URL=http://localhost:5173
14 changes: 9 additions & 5 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
module.exports = {
extends: '@mate-academy/eslint-config',
env: {
jest: true
},
rules: {
'no-proto': 0
'no-proto': 0,
},
plugins: ['jest']
overrides: [
{
files: ['**/*.ts'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended'],
},
],
};
27 changes: 14 additions & 13 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
name: Test

on:
push:
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test
24 changes: 24 additions & 0 deletions .github/workflows/test.yml-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Test

on:
push:
branches: [master]
pull_request:
branches: [master]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

# Node
node_modules
dist

# MacOS
.DS_Store

.env
84 changes: 84 additions & 0 deletions dist/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_http_1 = require("node:http");
const node_path_1 = __importDefault(require("node:path"));
const router_1 = require("./router");
const utils_1 = require("./utils/utils");
const PUBLIC_DIR = node_path_1.default.join(__dirname, 'public');
const DEFAULT_FILE = 'index.html';
const CONTENT_TYPES = {
'.css': 'text/css; charset=utf-8',
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml; charset=utf-8',
};
function sendNoContent(request, response) {
(0, utils_1.setCorsHeaders)(request, response);
response.writeHead(204, {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
'Content-Length': '0',
});
response.end();
}
function resolvePublicPath(urlPathname) {
const normalizedPath = urlPathname === '/' ? `/${DEFAULT_FILE}` : urlPathname;
const safeRelativePath = node_path_1.default
.normalize(normalizedPath)
.replace(/^(\.\.[/\\])+/, '');
return node_path_1.default.join(PUBLIC_DIR, safeRelativePath);
}
async function serveStaticFile(request, response, pathname) {
const filePath = resolvePublicPath(pathname);
const relativePath = node_path_1.default.relative(PUBLIC_DIR, filePath);
if (relativePath.startsWith('..') || node_path_1.default.isAbsolute(relativePath)) {
(0, utils_1.sendJson)(request, response, 403, { error: 'Forbidden' });
return true;
}
try {
const fileStat = await (0, promises_1.stat)(filePath);
if (!fileStat.isFile()) {
return false;
}
const extension = node_path_1.default.extname(filePath);
const contentType = CONTENT_TYPES[extension] || 'application/octet-stream';
response.writeHead(200, {
'Content-Type': contentType,
});
(0, node_fs_1.createReadStream)(filePath).pipe(response);
return true;
}
catch {
return false;
}
}
async function requestListener(request, response) {
const method = request.method || 'GET';
const url = new URL(request.url || '/', 'http://localhost');
const { pathname } = url;
if (method === 'OPTIONS') {
sendNoContent(request, response);
return;
}
if (method === 'GET') {
const fileServed = await serveStaticFile(request, response, pathname);
if (fileServed) {
return;
}
}
const handled = await (0, router_1.router)(method, pathname, request, response);
if (handled) {
return;
}
(0, utils_1.sendJson)(request, response, 404, { error: 'Not found' });
}
const app = (0, node_http_1.createServer)((request, response) => {
void requestListener(request, response);
});
exports.default = app;
11 changes: 11 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
require("dotenv/config");
const app_1 = __importDefault(require("./app"));
const PORT = Number(process.env.PORT || 5000);
app_1.default.listen(PORT, () => {
process.stdout.write(`Server listening on http://localhost:${PORT}\n`);
});
5 changes: 5 additions & 0 deletions nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"watch": ["src"],
"ext": "ts",
"exec": "node --import tsx src/index.ts"
}
Loading
Loading