Skip to content

Commit

Permalink
Copy 9-logger example
Browse files Browse the repository at this point in the history
  • Loading branch information
DemianParkhomenko committed Sep 14, 2023
1 parent 378843f commit 79b703a
Show file tree
Hide file tree
Showing 24 changed files with 470 additions and 15 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"ignorePatterns": ["**/static/*.js"],
"rules": {}
}
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"semi": false,
"semi": true,
"trailingComma": "es5",
"singleQuote": true
}
12 changes: 0 additions & 12 deletions index.js

This file was deleted.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "1.0.0",
"description": "Learn Node.js with fun🤪",
"main": "index.js",
"type": "module",
"type": "commonjs",
"scripts": {
"start": "node index.js",
"start": "cd ./src; node ./main.js",
"test": "echo no test specified",
"lint:check": "eslint .",
"lint:write": "eslint --fix .",
Expand All @@ -20,6 +20,10 @@
},
"author": "",
"license": "MIT",
"dependencies": {
"pg": "^8.8.0",
"ws": "^8.12.0"
},
"devDependencies": {
"@flydotio/dockerfile": "^0.4.9",
"eslint": "^8.49.0",
Expand Down
12 changes: 12 additions & 0 deletions src/api/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"parserOptions": {
"sourceType": "module"
},
"rules": {
"strict": ["error", "never"]
},
"globals": {
"db": "readonly",
"common": "readonly"
}
}
1 change: 1 addition & 0 deletions src/api/city.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
db('city');
13 changes: 13 additions & 0 deletions src/api/country.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const country = db('country');

({
async read(id) {
console.log({ db });
return await country.read(id);
},

async find(mask) {
const sql = 'SELECT * from country where name like $1';
return await country.query(sql, [mask]);
},
});
24 changes: 24 additions & 0 deletions src/api/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
({
async read(id) {
return await db('users').read(id, ['id', 'login']);
},

async create({ login, password }) {
const passwordHash = await common.hash(password);
return await db('users').create({ login, password: passwordHash });
},

async update(id, { login, password }) {
const passwordHash = await common.hash(password);
return await db('users').update(id, { login, password: passwordHash });
},

async delete(id) {
return await db('users').delete(id);
},

async find(mask) {
const sql = 'SELECT login from users where login like $1';
return await db('users').query(sql, [mask]);
},
});
59 changes: 59 additions & 0 deletions src/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

const pg = require('pg');

const pool = new pg.Pool({
host: '127.0.0.1',
port: 5432,
database: 'example',
user: 'marcus',
password: 'marcus',
});

module.exports = (table) => ({
async query(sql, args) {
return await pool.query(sql, args);
},

async read(id, fields = ['*']) {
const names = fields.join(', ');
const sql = `SELECT ${names} FROM ${table}`;
if (!id) return pool.query(sql);
return await pool.query(`${sql} WHERE id = $1`, [id]);
},

async create({ ...record }) {
const keys = Object.keys(record);
const nums = new Array(keys.length);
const data = new Array(keys.length);
let i = 0;
for (const key of keys) {
data[i] = record[key];
nums[i] = `$${++i}`;
}
const fields = '"' + keys.join('", "') + '"';
const params = nums.join(', ');
const sql = `INSERT INTO "${table}" (${fields}) VALUES (${params})`;
return await pool.query(sql, data);
},

async update(id, { ...record }) {
const keys = Object.keys(record);
const updates = new Array(keys.length);
const data = new Array(keys.length);
let i = 0;
for (const key of keys) {
data[i] = record[key];
updates[i] = `${key} = $${++i}`;
}
const delta = updates.join(', ');
const sql = `UPDATE ${table} SET ${delta} WHERE id = $${++i}`;
data.push(id);
return await pool.query(sql, data);
},

async delete(id) {
const sql = `DELETE FROM ${table} WHERE id = $1`;
return await pool.query(sql, [id]);
},
});
25 changes: 25 additions & 0 deletions src/db/data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
INSERT INTO "users" ("login", "password") VALUES
('admin', 'ypMEd9FwvtlOjcvH94iICQ==:V6LnSOVwXzENxeLCJk59Toadea7oaA1IxYulAOtKkL9tBxjEPOw085vYalEdLDoe8xbrXQlhh7QRGzrSe8Bthw=='),
('marcus', 'dpHw0OUNBz76nuqrXZbeYQ==:wpvUVgi8Yp9rJ0yZyBWecaWP2EL/ahpxZY74KOVfhAYbAZSq6mWqjsQwtCvIPcSKZqUVpVb13JcSciB2fA+6Tg=='),
('user', 'r8zb8AdrlPSh5wNy6hqOxg==:HyO5rvOFLtwzU+OZ9qFi3ADXlVccDJWGSfUS8mVq43spJ6sxyliUdW3i53hOPdkFAtDn3EAQMttOlIoJap1lTQ=='),
('iskandar', 'aqX1O4bKXiwC/Jh2EKNIYw==:bpE4TARNg09vb2Libn1c00YRxcvoklB9zVSbD733LwQQFUuAm7WHP85PbZXwEbbeOVPIFHgflR4cvEmvYkr76g==');

-- Examples login/password
-- admin/123456
-- marcus/marcus
-- user/nopassword
-- iskandar/zulqarnayn

INSERT INTO "country" ("name") VALUES
('Soviet Union'),
('People''s Republic of China'),
('Vietnam'),
('Cuba');

INSERT INTO "city" ("name", "country") VALUES
('Beijing', 2),
('Wuhan', 2),
('Kiev', 1),
('Havana', 4),
('Hanoi', 3),
('Kaliningrad', 1);
5 changes: 5 additions & 0 deletions src/db/install.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DROP DATABASE IF EXISTS example;
DROP USER IF EXISTS marcus;
CREATE USER marcus WITH PASSWORD 'marcus';
CREATE DATABASE example OWNER marcus;

3 changes: 3 additions & 0 deletions src/db/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
psql -f install.sql -U postgres
PGPASSWORD=marcus psql -d example -f structure.sql -U marcus
PGPASSWORD=marcus psql -d example -f data.sql -U marcus
41 changes: 41 additions & 0 deletions src/db/structure.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
CREATE TABLE "users" (
"id" bigint generated always as identity,
"login" varchar NOT NULL,
"password" varchar NOT NULL
);

ALTER TABLE "users" ADD CONSTRAINT pkUsers PRIMARY KEY (id);
CREATE UNIQUE INDEX akUsersLogin ON "users" (login);

CREATE TABLE "session" (
"id" bigint generated always as identity,
"user" integer NOT NULL,
"token" varchar(64) NOT NULL,
"ip" varchar(45) NOT NULL,
"data" text
);

ALTER TABLE "session" ADD CONSTRAINT pkSession PRIMARY KEY (id);
CREATE UNIQUE INDEX akSession ON "session" (token);
ALTER TABLE "session" ADD CONSTRAINT fkSessionUserId FOREIGN KEY ("user") REFERENCES "users" (id) ON DELETE CASCADE;

CREATE TABLE "country" (
"id" bigint generated always as identity,
"name" varchar NOT NULL
);

ALTER TABLE "country" ADD CONSTRAINT "pkCountry" PRIMARY KEY ("id");

CREATE UNIQUE INDEX "akCountry" ON "country" ("name");

CREATE TABLE "city" (
"id" bigint generated always as identity,
"name" varchar NOT NULL,
"country" bigint NOT NULL
);

ALTER TABLE "city" ADD CONSTRAINT "pkCity" PRIMARY KEY ("id");

CREATE UNIQUE INDEX "akCity" ON "city" ("name");

ALTER TABLE "city" ADD CONSTRAINT "fkCityCountry" FOREIGN KEY ("country") REFERENCES "country" ("id") ON DELETE CASCADE;
14 changes: 14 additions & 0 deletions src/hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

const crypto = require('node:crypto');

const hash = (password) =>
new Promise((resolve, reject) => {
const salt = crypto.randomBytes(16).toString('base64');
crypto.scrypt(password, salt, 64, (err, result) => {
if (err) reject(err);
resolve(salt + ':' + result.toString('base64'));
});
});

module.exports = hash;
33 changes: 33 additions & 0 deletions src/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

const http = require('node:http');

const receiveArgs = async (req) => {
const buffers = [];
for await (const chunk of req) buffers.push(chunk);
const data = Buffer.concat(buffers).toString();
return JSON.parse(data);
};

module.exports = (routing, port) => {
http
.createServer(async (req, res) => {
const { url, socket } = req;
const [name, method, id] = url.substring(1).split('/');
const entity = routing[name];
if (!entity) return void res.end('Not found');
const handler = entity[method];
if (!handler) return void res.end('Not found');
const src = handler.toString();
const signature = src.substring(0, src.indexOf(')'));
const args = [];
if (signature.includes('(id')) args.push(id);
if (signature.includes('{')) args.push(await receiveArgs(req));
console.log(`${socket.remoteAddress} ${method} ${url}`);
const result = await handler(...args);
res.end(JSON.stringify(result.rows));
})
.listen(port);

console.log(`API on port ${port}`);
};
15 changes: 15 additions & 0 deletions src/load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const fs = require('node:fs').promises;
const vm = require('node:vm');

const RUN_OPTIONS = { timeout: 5000, displayErrors: false };

module.exports = async (filePath, sandbox) => {
const src = await fs.readFile(filePath, 'utf8');
const code = `'use strict';\n${src}`;
const script = new vm.Script(code);
const context = vm.createContext(Object.freeze({ ...sandbox }));
const exported = script.runInContext(context, RUN_OPTIONS);
return exported;
};
Empty file added src/log/.gitkeep
Empty file.
71 changes: 71 additions & 0 deletions src/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';

const fs = require('node:fs');
const util = require('node:util');
const path = require('node:path');

const COLORS = {
info: '\x1b[1;37m',
debug: '\x1b[1;33m',
error: '\x1b[0;31m',
system: '\x1b[1;34m',
access: '\x1b[1;38m',
};

const DATETIME_LENGTH = 19;

class Logger {
constructor(logPath) {
this.path = logPath;
const date = new Date().toISOString().substring(0, 10);
const filePath = path.join(logPath, `${date}.log`);
this.stream = fs.createWriteStream(filePath, { flags: 'a' });
this.regexp = new RegExp(path.dirname(this.path), 'g');
}

close() {
return new Promise((resolve) => this.stream.end(resolve));
}

write(type = 'info', s) {
const now = new Date().toISOString();
const date = now.substring(0, DATETIME_LENGTH);
const color = COLORS[type];
const line = date + '\t' + s;
console.log(color + line + '\x1b[0m');
const out = line.replace(/[\n\r]\s*/g, '; ') + '\n';
this.stream.write(out);
}

log(...args) {
const msg = util.format(...args);
this.write('info', msg);
}

dir(...args) {
const msg = util.inspect(...args);
this.write('info', msg);
}

debug(...args) {
const msg = util.format(...args);
this.write('debug', msg);
}

error(...args) {
const msg = util.format(...args).replace(/[\n\r]{2,}/g, '\n');
this.write('error', msg.replace(this.regexp, ''));
}

system(...args) {
const msg = util.format(...args);
this.write('system', msg);
}

access(...args) {
const msg = util.format(...args);
this.write('access', msg);
}
}

module.exports = new Logger('./log');
Loading

0 comments on commit 79b703a

Please sign in to comment.