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
4 changes: 3 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/bin/templates
/bin/templates
/tests/lib/BaseTester.mjs
/tests/lib/TestFactory.mjs
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
10. Follows security best practices
11. [Docker](https://docs.docker.com/) support
12. Covered with tests (db dependent tests, code coverage etc)
13. Built on top of [express.js](https://expressjs.com/)
13. Built on top of [fastify.js](https://www.fastify.io)
14. User management out of the box **(in progress)**
15. S3 support out of the box even for local development
16. SMTP support (with development and testing mocks)
Expand Down
2 changes: 1 addition & 1 deletion app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async function main() {
// Init Controllers Layer (API)
API.setLogger(logger);

RestAPI.start({ appPort: config.appPort });
await RestAPI.start({ port: config.appPort });
await JsonRPC.start({ wssPort: config.wssPort });

// Init Domain Model Layer
Expand Down
10 changes: 9 additions & 1 deletion bin/generator/BaseGenerator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ class BaseGenerator {
const absolutePath = path.join(process.cwd(), filePath);

if (mode === 'append') {
await fsPromises.appendFile(absolutePath, data);
const fileData = await fsPromises.readFile(absolutePath, 'utf8');

if (!fileData.includes(this.config.replaceTag)) throw new Error(`${absolutePath} file must contain ${this.config.replaceTag} comment for successful execution`);

await fsPromises.writeFile(
absolutePath,
fileData.replace(this.config.replaceTag, `${this.config.replaceTag}\n${data}`)
);
} else if (mode === 'write') {
await fsPromises.writeFile(absolutePath, data);
}
Expand Down Expand Up @@ -102,6 +109,7 @@ class BaseGenerator {
let result = template;

for (const [ templateKey, fillData ] of Object.entries(data)) {
// eslint-disable-next-line security/detect-non-literal-regexp
const regExp = new RegExp(`{{${ templateKey }}}`, 'g');

result = result.replace(regExp, fillData);
Expand Down
17 changes: 11 additions & 6 deletions bin/generator/RestAPIGenerator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,24 @@ class RestAPIGenerator extends Base {
}

#getRoutesTemplate = ({ actions }) => {
let template = '\n// {{MODEL_NAME_PLURAL}}\n';
let template = `\n// {{MODEL_NAME_PLURAL}}
router.register(async (secureRouter) => {
secureRouter.addHook('preHandler', checkSession);\n`;

const actionsMap = {
'c' : 'router.post(\'/{{MODEL_NAME_PLURAL_toLCC}}\', checkSession, controllers.{{MODEL_NAME_PLURAL_toLCC}}.create);',
'r' : 'router.get(\'/{{MODEL_NAME_PLURAL_toLCC}}/:id\', checkSession, controllers.{{MODEL_NAME_PLURAL_toLCC}}.show);',
'u' : 'router.put(\'/{{MODEL_NAME_PLURAL_toLCC}}/:id\', checkSession, controllers.{{MODEL_NAME_PLURAL_toLCC}}.update);',
'd' : 'router.delete(\'/{{MODEL_NAME_PLURAL_toLCC}}/:id\', checkSession, controllers.{{MODEL_NAME_PLURAL_toLCC}}.delete);',
'l' : 'router.get(\'/{{MODEL_NAME_PLURAL_toLCC}}\', checkSession, controllers.{{MODEL_NAME_PLURAL_toLCC}}.list);'
'c' : 'secureRouter.post(\'/\', controllers.{{MODEL_NAME_PLURAL_toLCC}}.create);',
'r' : 'secureRouter.get(\'/:id\', controllers.{{MODEL_NAME_PLURAL_toLCC}}.show);',
'u' : 'secureRouter.put(\'/:id\', controllers.{{MODEL_NAME_PLURAL_toLCC}}.update);',
'd' : 'secureRouter.delete(\'/:id\', controllers.{{MODEL_NAME_PLURAL_toLCC}}.delete);',
'l' : 'secureRouter.get(\'/\', controllers.{{MODEL_NAME_PLURAL_toLCC}}.list);'
};

for (const action of actions) {
template += `${actionsMap[action]}\n`;
}

template += '}, { prefix: \'/{{MODEL_NAME_PLURAL_toLCC}}\' });';

return template;
}
}
Expand Down
9 changes: 4 additions & 5 deletions bin/generator/templates/rest-api/router
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import express from 'express';
import controllers from './controllers/index.mjs';

const router = express.Router();
export default async (router) => {
const checkSession = controllers.sessions.check;

const checkSession = controllers.sessions.check;

export default router;
// {{GENERATE_NEW_ROUTS_BELOW}}
};
14 changes: 9 additions & 5 deletions bin/generator/templates/rest-api/routes
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@

// {{MODEL_NAME_PLURAL}}
router.post('/{{MODEL_NAME_PLURAL}}', checkSession, controllers.{{MODEL_NAME_PLURAL}}.create);
router.get('/{{MODEL_NAME_PLURAL}}/:id', checkSession, controllers.{{MODEL_NAME_PLURAL}}.show);
router.get('/{{MODEL_NAME_PLURAL}}', checkSession, controllers.{{MODEL_NAME_PLURAL}}.list);
router.put('/{{MODEL_NAME_PLURAL}}/:id', checkSession, controllers.{{MODEL_NAME_PLURAL}}.update);
router.delete('/{{MODEL_NAME_PLURAL}}/:id', checkSession, controllers.{{MODEL_NAME_PLURAL}}.delete);
router.register(async (secureRouter) => {
secureRouter.addHook('preHandler', checkSession);

secureRouter.post('/', controllers.{{MODEL_NAME_PLURAL}}.create);
secureRouter.get('/:id', controllers.{{MODEL_NAME_PLURAL}}.show);
secureRouter.get('/', controllers.{{MODEL_NAME_PLURAL}}.list);
secureRouter.put('/:id', controllers.{{MODEL_NAME_PLURAL}}.update);
secureRouter.delete('/:id', controllers.{{MODEL_NAME_PLURAL}}.delete);
}, { prefix: '/{{MODEL_NAME_PLURAL}}' });
3 changes: 2 additions & 1 deletion generator.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export default {
dumpsFile : 'lib/use-cases/utils/dumps.mjs',
testsFolder : 'tests',
templatesFolder : 'bin/generator/templates',
migrationsFolder : 'migrations'
migrationsFolder : 'migrations',
replaceTag : '{{GENERATE_NEW_DATA_BELOW}}'
};
6 changes: 2 additions & 4 deletions lib/api/rest-api/admin/controllers/sessions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SessionsCheck from '../../../../use-cases/admin/sessions/Check.mjs';
export default {
create : chista.makeUseCaseRunner(SessionsCreate, req => req.body),

async check(req, res, next) {
async check(req, res) {
const promise = chista.runUseCase(SessionsCheck, {
params : { token: req.headers.authorization }
});
Expand All @@ -16,13 +16,11 @@ export default {

/* eslint no-param-reassign: 0 */
// eslint-disable-next-line require-atomic-updates
req.session = {
return req.session = {
context : {
adminId : adminData.id
}
};

return next();
} catch (e) {
return chista.renderPromiseAsJson(req, res, promise);
}
Expand Down
44 changes: 25 additions & 19 deletions lib/api/rest-api/admin/router.mjs
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import express from 'express';
import controllers from './controllers/index.mjs';

const router = express.Router();
export default async (router) => {
const checkSession = controllers.sessions.check;

const checkSession = controllers.sessions.check;
// Admins
router.register(async (secureRouter) => {
secureRouter.addHook('preHandler', checkSession);

// Admins
router.post('/admins', checkSession, controllers.admins.create);
router.get('/admins/:id/resetPassword', checkSession, controllers.admins.resetPassword);
router.get('/admins/:id', checkSession, controllers.admins.show);
router.get('/admins', checkSession, controllers.admins.list);
router.delete('/admins/:id', checkSession, controllers.admins.delete);
secureRouter.post('/', controllers.admins.create);
secureRouter.get('/:id/resetPassword', controllers.admins.resetPassword);
secureRouter.get('/:id', controllers.admins.show);
secureRouter.get('/', controllers.admins.list);
secureRouter.delete('/:id', controllers.admins.delete);
}, { prefix: '/admins' });

// Sessions
router.post('/sessions', controllers.sessions.create);
// Users
router.register(async (secureRouter) => {
secureRouter.addHook('preHandler', checkSession);

// Users
router.post('/users', checkSession, controllers.users.create);
router.get('/users/:id/resetPassword', checkSession, controllers.users.resetPassword);
router.get('/users/:id', checkSession, controllers.users.show);
router.get('/users', checkSession, controllers.users.list);
router.put('/users/:id', checkSession, controllers.users.update);
router.delete('/users/:id', checkSession, controllers.users.delete);
secureRouter.post('/', controllers.users.create);
secureRouter.get('/:id/resetPassword', controllers.users.resetPassword);
secureRouter.get('/:id', controllers.users.show);
secureRouter.get('/', controllers.users.list);
secureRouter.put('/:id', controllers.users.update);
secureRouter.delete('/:id', controllers.users.delete);
}, { prefix: '/users' });

// Sessions
router.post('/sessions', controllers.sessions.create);
};

export default router;
39 changes: 19 additions & 20 deletions lib/api/rest-api/app.mjs
Original file line number Diff line number Diff line change
@@ -1,40 +1,39 @@
/* eslint import/imports-first:0 import/newline-after-import:0 */
import express from 'express';
import { promisify } from '../../../packages.mjs';
import Fastify from 'fastify';

import logger from '../logger.mjs';
import middlewares from './middlewares.mjs';
import adminRouter from './admin/router.mjs';
import mainRouter from './main/router.mjs';

// Init app
const app = express();
const app = Fastify();

app.use(middlewares.json);
app.use(middlewares.clsMiddleware);
app.use(middlewares.urlencoded);
app.use(middlewares.cors);
app.use(middlewares.include);
app.use('/api/v1/admin', adminRouter);
app.use('/api/v1', mainRouter);
export async function start(appPort) {
await app.register(import('@fastify/middie'));
app.register(import('@fastify/formbody'));
app.register(import('@fastify/multipart'));

let server = null;
// middlewares init
app.use(middlewares.cors);
app.use(middlewares.include);

export function start({ appPort }) {
server = app.listen(appPort, () => {
const { port, address } = server.address();
// routes init
app.register(adminRouter, { prefix: '/api/v1/admin' });
app.register(mainRouter, { prefix: '/api/v1' });

global.REST_API_PORT = port; // For tests. TODO: export app and use it tests
logger.info(`[RestApiApp] STARTING AT PORT [${port}] ADDRESS [${address}]`);
});
await app.listen(appPort);

server.closeAsync = promisify(server.close);
const { port, address } = app.addresses().find(({ family }) => family === 'IPv4');

global.REST_API_PORT = port; // For tests. TODO: export app and use it tests
logger.info(`[RestApiApp] STARTING AT PORT [${port}] ADDRESS [${address}]`);
}

export async function stop() {
if (!server) return;
if (!app) return;
logger.info('[RestApiApp] Closing server');
await server.closeAsync();
await app.close();
}

export default app;
20 changes: 8 additions & 12 deletions lib/api/rest-api/main/controllers/files.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ import chista from '../../chista.mjs';
import Upload from '../../../../use-cases/main/files/Create.mjs';

export default {
create(req, res) {
async create(req, res) {
try {
req.busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
const promise = chista.runUseCase(Upload, { params : {
...req.params,
filename,
encoding,
file,
mimetype
} });
const data = await req.file();

chista.renderPromiseAsJson(req, res, promise);
});
const promise = chista.runUseCase(Upload, { params : {
...req.params,
...data,
file : await data.toBuffer()
} });

req.pipe(req.busboy);
return chista.renderPromiseAsJson(req, res, promise);
} catch (error) {
console.error(error);
res.send({
Expand Down
6 changes: 2 additions & 4 deletions lib/api/rest-api/main/controllers/sessions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SessionsCheck from '../../../../use-cases/main/sessions/Check.mjs';
export default {
create : chista.makeUseCaseRunner(SessionsCreate, req => req.body),

async check(req, res, next) {
async check(req, res) {
const promise = chista.runUseCase(SessionsCheck, {
params : { token: req.headers.authorization }
});
Expand All @@ -15,14 +15,12 @@ export default {

/* eslint no-param-reassign: 0 */
// eslint-disable-next-line require-atomic-updates
req.session = {
return req.session = {
context : {
userId : userData.id
// userStatus : userData.status
}
};

return next();
} catch (e) {
return chista.renderPromiseAsJson(req, res, promise);
}
Expand Down
44 changes: 25 additions & 19 deletions lib/api/rest-api/main/router.mjs
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import express from 'express';
import middlewares from '../middlewares.mjs';
import controllers from './controllers/index.mjs';

const router = express.Router();
export default async (router) => {
const checkSession = controllers.sessions.check;

const checkSession = controllers.sessions.check;
const busboy = middlewares.busboy;
router.register(async (secureRouter) => {
secureRouter.addHook('preHandler', checkSession);

// Actions
router.post('/actions/:id', controllers.actions.submit);
secureRouter.get('/:id', controllers.users.show);
secureRouter.get('/', controllers.users.list);
secureRouter.put('/:id', controllers.users.update);
secureRouter.delete('/:id', controllers.users.delete);
}, { prefix: '/users' });

// Sessions
router.post('/sessions', controllers.sessions.create);
// Actions
router.post('/actions/:id', controllers.actions.submit);

// Users
router.post('/users', controllers.users.create);
router.post('/users/resetPassword', controllers.users.resetPassword);
router.get('/users/:id', checkSession, controllers.users.show);
router.get('/users', checkSession, controllers.users.list);
router.put('/users/:id', checkSession, controllers.users.update);
router.delete('/users/:id', checkSession, controllers.users.delete);
// Sessions
router.post('/sessions', controllers.sessions.create);

// Files
router.post('/files/:type/', checkSession, busboy, controllers.files.create);
// Users
router.post('/users', controllers.users.create);
router.post('/users/resetPassword', controllers.users.resetPassword);

export default router;
// Files
router.register(async (secureRouter) => {
secureRouter.addHook('preHandler', checkSession);

secureRouter.post('/:type', controllers.files.create);
}, { prefix: '/files' });

// {{GENERATE_NEW_DATA_BELOW}}
};
Loading