Adapt code style for new repository
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "thevahidal",
|
||||
"name": "Vahid Al",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20302825?v=4",
|
||||
"profile": "http://linktr.ee/thevahidal",
|
||||
"contributions": [
|
||||
"code",
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AbegaM",
|
||||
"name": "Abenezer Melkamu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/70259638?v=4",
|
||||
"profile": "https://github.com/AbegaM",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "IanMayo",
|
||||
"name": "Ian Mayo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1108513?v=4",
|
||||
"profile": "https://github.com/IanMayo",
|
||||
"contributions": [
|
||||
"code",
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "HanzCEO",
|
||||
"name": "Hanz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40712686?v=4",
|
||||
"profile": "https://godot.id",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "KoenDG",
|
||||
"name": "Koen De Groote",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1440619?v=4",
|
||||
"profile": "https://github.com/KoenDG",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "TahaKhanAbdalli",
|
||||
"name": "Muhammad Taha Khan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/50602678?v=4",
|
||||
"profile": "https://github.com/TahaKhanAbdalli",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "thoughtsunificator",
|
||||
"name": "Romain Lebesle",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/85041649?v=4",
|
||||
"profile": "http://thoughtsunificator.me",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true,
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"projectName": "soul",
|
||||
"projectOwner": "thevahidal"
|
||||
}
|
||||
@@ -1,26 +1,18 @@
|
||||
# EditorConfig is awesome: https://github.com/editorconfig/editorconfig
|
||||
|
||||
# Top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*.{js,jsx,ts,tsx}]
|
||||
quote_type = single
|
||||
|
||||
[*]
|
||||
# Set default charset to utf-8
|
||||
charset = utf-8
|
||||
# Set default indentation to spaces
|
||||
indent_style = space
|
||||
# Linux-style newlines with a newline ending every file
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
# Remove whitespace characters preceding newline characters
|
||||
charset = utf-8
|
||||
|
||||
[*.{js,ts,json,cjs,mjs,tsx}]
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = true
|
||||
tab_width = 4
|
||||
|
||||
# Two space indentation for JavaScript files
|
||||
[*.{js,json}]
|
||||
[*.json]
|
||||
tab_width = 2
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Disable trimming trailing whitespaces so that double space newlines work
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
46
.eslintrc.js
46
.eslintrc.js
@@ -1,24 +1,24 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
jest: true,
|
||||
},
|
||||
extends: ["eslint:recommended", "prettier"],
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
files: [".eslintrc.{js,cjs}"],
|
||||
parserOptions: {
|
||||
sourceType: "script",
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
},
|
||||
rules: {},
|
||||
};
|
||||
env: {
|
||||
node: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
jest: true
|
||||
},
|
||||
extends: ['eslint:recommended', 'prettier'],
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
files: ['.eslintrc.{js,cjs}'],
|
||||
parserOptions: {
|
||||
sourceType: 'script'
|
||||
}
|
||||
}
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest'
|
||||
},
|
||||
rules: {}
|
||||
}
|
||||
|
||||
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# Contributing
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before making a change.
|
||||
email, or any other method with the owners of this repository before making a change.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
build.
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
variables, exposed ports, useful file locations and container parameters.
|
||||
3. Increase the version numbers in any examples files and the README.md to the new version that this
|
||||
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
|
||||
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
|
||||
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
|
||||
do not have permission to do that, you may request the second reviewer to merge it for you.
|
||||
|
||||
@@ -18,8 +18,8 @@ Using the following Dockerfile:
|
||||
|
||||
<details>
|
||||
<summary>Dockerfile</summary>
|
||||
|
||||
```nginx
|
||||
|
||||
```nginx
|
||||
# node:19-alpine amd64
|
||||
FROM node@sha256:d0ba7111bc031323ce2706f8e424afc868db289ba40ff55b05561cf59c123be1 AS node
|
||||
|
||||
@@ -34,7 +34,8 @@ RUN apk update && apk add python3=3.11.10-r1 build-base=0.5-r3 && npm ci
|
||||
COPY . .
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
```
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can proceed [to building the application](https://docs.docker.com/get-started/workshop/02_our_app/#build-the-apps-image).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
---
|
||||
@import "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown.min.css";
|
||||
|
||||
@import 'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown.min.css';
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
@@ -6,7 +6,6 @@ Soul extensions are a way to extend the functionality of Soul. Extensions are wr
|
||||
|
||||
- API Extensions: Add new endpoints to Soul
|
||||
|
||||
|
||||
## Setup Environment
|
||||
|
||||
To follow the below examples we need to download a sample database and also install Soul CLI.
|
||||
@@ -18,6 +17,7 @@ wget https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDa
|
||||
```
|
||||
|
||||
### Using Soul CLI
|
||||
|
||||
```bash
|
||||
npm install -g soul-cli
|
||||
soul -d ./Chinook_Sqlite.sqlite -p 8000 -e "/absolute/path/to/_extensions/"
|
||||
@@ -32,8 +32,8 @@ npm install # Install dependencies
|
||||
npm link # might need `sudo`
|
||||
soul -d ./Chinook_Sqlite.sqlite -p 8000 -e "/absolute/path/to/_extensions/"
|
||||
```
|
||||
</details>
|
||||
|
||||
</details>
|
||||
|
||||
## Creating an API extension
|
||||
|
||||
@@ -45,65 +45,64 @@ const hello = {
|
||||
path: '/api/hello-soul',
|
||||
handler: (req, res, db) => {
|
||||
res.status(200).json({
|
||||
message: 'Hello Soul!'
|
||||
});
|
||||
},
|
||||
};
|
||||
message: 'Hello Soul!'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const timestamp = {
|
||||
method: 'GET',
|
||||
path: '/api/timestamp',
|
||||
handler: (req, res, db) => {
|
||||
res.status(200).json({
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
},
|
||||
};
|
||||
timestamp: Date.now()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const greetings = {
|
||||
method: 'POST',
|
||||
path: '/api/greetings/:name',
|
||||
handler: (req, res, db) => {
|
||||
const { name } = req.params;
|
||||
const { greeting } = req.body;
|
||||
const { name } = req.params
|
||||
const { greeting } = req.body
|
||||
res.status(200).json({
|
||||
message: `${greeting} ${name}!`,
|
||||
});
|
||||
},
|
||||
message: `${greeting} ${name}!`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const searchTables = {
|
||||
method: 'GET',
|
||||
path: '/api/search-tables',
|
||||
handler: (req, res, db) => {
|
||||
const { q } = req.query;
|
||||
const { q } = req.query
|
||||
const sql = `
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table'
|
||||
AND name LIKE $searchQuery
|
||||
`;
|
||||
`
|
||||
try {
|
||||
const tables = db.prepare(sql).all({
|
||||
searchQuery: `%${q}%`,
|
||||
});
|
||||
searchQuery: `%${q}%`
|
||||
})
|
||||
res.status(200).json({
|
||||
tables,
|
||||
});
|
||||
tables
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
});
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hello,
|
||||
timestamp,
|
||||
greetings,
|
||||
searchTables,
|
||||
};
|
||||
|
||||
searchTables
|
||||
}
|
||||
```
|
||||
|
||||
Alright, now we can test if the extension is working:
|
||||
|
||||
@@ -22,14 +22,14 @@ In this guide, we will demonstrate how to host a static `React Admin` applicatio
|
||||
|
||||
```js
|
||||
const reactAdminApp = {
|
||||
method: "GET",
|
||||
path: "/api/client",
|
||||
method: 'GET',
|
||||
path: '/api/client',
|
||||
handler: (req, res, db) => {
|
||||
const clientPath = path.join(__dirname, "../dist", "index.html");
|
||||
res.app.use(express.static(path.join(__dirname, "../dist")));
|
||||
res.sendFile(clientPath);
|
||||
},
|
||||
};
|
||||
const clientPath = path.join(__dirname, '../dist', 'index.html')
|
||||
res.app.use(express.static(path.join(__dirname, '../dist')))
|
||||
res.sendFile(clientPath)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Build your React Admin client:
|
||||
|
||||
138
package.json
138
package.json
@@ -1,71 +1,71 @@
|
||||
{
|
||||
"name": "soul-cli",
|
||||
"version": "0.8.1",
|
||||
"description": "A SQLite REST and Realtime server",
|
||||
"main": "src/server.js",
|
||||
"bin": {
|
||||
"soul": "./src/server.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"dev": "npm run swagger-autogen && cross-env NO_CLI=true nodemon src/server.js",
|
||||
"cli": "nodemon src/server.js --database foobar.db",
|
||||
"swagger-autogen": "cross-env NO_CLI=true node src/swagger/index.js",
|
||||
"test": "cross-env CI=true NODE_ENV=test NO_CLI=true DB=test.db CORE_PORT=8001 jest --testTimeout=10000",
|
||||
"prepare": "husky install",
|
||||
"lint": "eslint . --fix --max-warnings=0",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thevahidal/soul.git"
|
||||
},
|
||||
"author": "Vahid Al @thevahidal",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/thevahidal/soul/issues"
|
||||
},
|
||||
"homepage": "https://github.com/thevahidal/soul#readme",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"better-sqlite3": "^8.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"check-password-strength": "^2.0.7",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"express-winston": "^4.2.0",
|
||||
"joi": "^17.8.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"soul-studio": "^0.0.1",
|
||||
"swagger-ui-express": "^4.6.1",
|
||||
"winston": "^3.8.2",
|
||||
"ws": "^8.12.1",
|
||||
"yargs": "^17.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.4.3",
|
||||
"nodemon": "^3.1.3",
|
||||
"prettier": "3.1.0",
|
||||
"supertest": "^6.3.3",
|
||||
"swagger-autogen": "^2.23.1"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"coveragePathIgnorePatterns": [
|
||||
"/node_modules/"
|
||||
],
|
||||
"globalSetup": "./src/tests/globalSetup.js",
|
||||
"globalTeardown": "./src/tests/globalTeardown.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": "eslint --fix",
|
||||
"*.{js,css,md,html,json}": "prettier --write"
|
||||
}
|
||||
"name": "soul-cli",
|
||||
"version": "0.8.1",
|
||||
"description": "A SQLite REST and Realtime server",
|
||||
"main": "src/server.js",
|
||||
"bin": {
|
||||
"soul": "./src/server.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"dev": "npm run swagger-autogen && cross-env NO_CLI=true nodemon src/server.js",
|
||||
"cli": "nodemon src/server.js --database foobar.db",
|
||||
"swagger-autogen": "cross-env NO_CLI=true node src/swagger/index.js",
|
||||
"test": "cross-env CI=true NODE_ENV=test NO_CLI=true DB=test.db CORE_PORT=8001 jest --testTimeout=10000",
|
||||
"prepare": "husky install",
|
||||
"lint": "eslint . --fix --max-warnings=0",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thevahidal/soul.git"
|
||||
},
|
||||
"author": "Vahid Al @thevahidal",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/thevahidal/soul/issues"
|
||||
},
|
||||
"homepage": "https://github.com/thevahidal/soul#readme",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"better-sqlite3": "^8.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"check-password-strength": "^2.0.7",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"express-winston": "^4.2.0",
|
||||
"joi": "^17.8.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"soul-studio": "^0.0.1",
|
||||
"swagger-ui-express": "^4.6.1",
|
||||
"winston": "^3.8.2",
|
||||
"ws": "^8.12.1",
|
||||
"yargs": "^17.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.4.3",
|
||||
"nodemon": "^3.1.3",
|
||||
"prettier": "3.1.0",
|
||||
"supertest": "^6.3.3",
|
||||
"swagger-autogen": "^2.23.1"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"coveragePathIgnorePatterns": [
|
||||
"/node_modules/"
|
||||
],
|
||||
"globalSetup": "./src/tests/globalSetup.js",
|
||||
"globalTeardown": "./src/tests/globalTeardown.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": "eslint --fix",
|
||||
"*.{js,css,md,html,json}": "prettier --write"
|
||||
}
|
||||
}
|
||||
|
||||
226
src/cli.js
226
src/cli.js
@@ -1,122 +1,122 @@
|
||||
const yargs = require('yargs');
|
||||
const yargs = require('yargs')
|
||||
|
||||
const usage = `
|
||||
Soul | REST and realtime server for SQLite
|
||||
Usage: soul [options]
|
||||
`;
|
||||
`
|
||||
|
||||
let options = undefined;
|
||||
let options = undefined
|
||||
if (process.env.NO_CLI !== 'true') {
|
||||
options = yargs
|
||||
.usage(usage)
|
||||
.option('d', {
|
||||
alias: 'database',
|
||||
describe: 'SQLite database file or :memory:',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('p', {
|
||||
alias: 'port',
|
||||
describe: 'Port to listen on',
|
||||
type: 'number',
|
||||
demandOption: false,
|
||||
})
|
||||
.option('r', {
|
||||
alias: 'rate-limit-enabled',
|
||||
describe: 'Enable rate limiting',
|
||||
type: 'boolean',
|
||||
demandOption: false,
|
||||
})
|
||||
.option('c', {
|
||||
alias: 'cors',
|
||||
describe: 'CORS whitelist origins',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.option('V', {
|
||||
alias: 'verbose',
|
||||
describe: 'Enable verbose logging',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
choices: ['console', null],
|
||||
})
|
||||
.options('e', {
|
||||
alias: 'extensions',
|
||||
describe: 'Extensions directory path to load',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('env', {
|
||||
alias: 'envpath',
|
||||
describe: 'Environment variable file path to load',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('a', {
|
||||
alias: 'auth',
|
||||
describe: 'Enable authentication and authorization',
|
||||
type: 'boolean',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('ts', {
|
||||
alias: 'tokensecret',
|
||||
describe: 'JWT secret for the access and refresh tokens',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('atet', {
|
||||
alias: 'accesstokenexpirationtime',
|
||||
describe: 'JWT expiration time for access token',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('rtet', {
|
||||
alias: 'refreshtokenexpirationtime',
|
||||
describe: 'JWT expiration time for refresh token',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('iuu', {
|
||||
alias: 'initialuserusername',
|
||||
describe: 'Initial superuser username',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('iup', {
|
||||
alias: 'initialuserpassword',
|
||||
describe: 'Initial superuser password',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.options('S', {
|
||||
alias: 'studio',
|
||||
describe: 'Start Soul Studio in parallel',
|
||||
type: 'boolean',
|
||||
demandOption: false,
|
||||
})
|
||||
.command('updatesuperuser', 'Update a superuser', (yargs) => {
|
||||
return yargs
|
||||
.option('id', {
|
||||
describe: 'The ID of the superuser you want to update',
|
||||
type: 'number',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('password', {
|
||||
describe: 'The new password for the superuser you want to update',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.option('is_superuser', {
|
||||
describe: 'The role of the superuser you want to update',
|
||||
type: 'boolean',
|
||||
demandOption: false,
|
||||
});
|
||||
})
|
||||
.help(true).argv;
|
||||
options = yargs
|
||||
.usage(usage)
|
||||
.option('d', {
|
||||
alias: 'database',
|
||||
describe: 'SQLite database file or :memory:',
|
||||
type: 'string',
|
||||
demandOption: true
|
||||
})
|
||||
.option('p', {
|
||||
alias: 'port',
|
||||
describe: 'Port to listen on',
|
||||
type: 'number',
|
||||
demandOption: false
|
||||
})
|
||||
.option('r', {
|
||||
alias: 'rate-limit-enabled',
|
||||
describe: 'Enable rate limiting',
|
||||
type: 'boolean',
|
||||
demandOption: false
|
||||
})
|
||||
.option('c', {
|
||||
alias: 'cors',
|
||||
describe: 'CORS whitelist origins',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.option('V', {
|
||||
alias: 'verbose',
|
||||
describe: 'Enable verbose logging',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
choices: ['console', null]
|
||||
})
|
||||
.options('e', {
|
||||
alias: 'extensions',
|
||||
describe: 'Extensions directory path to load',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.options('env', {
|
||||
alias: 'envpath',
|
||||
describe: 'Environment variable file path to load',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.options('a', {
|
||||
alias: 'auth',
|
||||
describe: 'Enable authentication and authorization',
|
||||
type: 'boolean',
|
||||
demandOption: false
|
||||
})
|
||||
.options('ts', {
|
||||
alias: 'tokensecret',
|
||||
describe: 'JWT secret for the access and refresh tokens',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.options('atet', {
|
||||
alias: 'accesstokenexpirationtime',
|
||||
describe: 'JWT expiration time for access token',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.options('rtet', {
|
||||
alias: 'refreshtokenexpirationtime',
|
||||
describe: 'JWT expiration time for refresh token',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.options('iuu', {
|
||||
alias: 'initialuserusername',
|
||||
describe: 'Initial superuser username',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.options('iup', {
|
||||
alias: 'initialuserpassword',
|
||||
describe: 'Initial superuser password',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.options('S', {
|
||||
alias: 'studio',
|
||||
describe: 'Start Soul Studio in parallel',
|
||||
type: 'boolean',
|
||||
demandOption: false
|
||||
})
|
||||
.command('updatesuperuser', 'Update a superuser', (yargs) => {
|
||||
return yargs
|
||||
.option('id', {
|
||||
describe: 'The ID of the superuser you want to update',
|
||||
type: 'number',
|
||||
demandOption: true
|
||||
})
|
||||
.option('password', {
|
||||
describe: 'The new password for the superuser you want to update',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
})
|
||||
.option('is_superuser', {
|
||||
describe: 'The role of the superuser you want to update',
|
||||
type: 'boolean',
|
||||
demandOption: false
|
||||
})
|
||||
})
|
||||
.help(true).argv
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
yargs,
|
||||
usage,
|
||||
options,
|
||||
};
|
||||
yargs,
|
||||
usage,
|
||||
options
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
const { yargs } = require('./cli');
|
||||
const { updateSuperuser } = require('./controllers/auth');
|
||||
const { yargs } = require('./cli')
|
||||
const { updateSuperuser } = require('./controllers/auth')
|
||||
|
||||
const { argv } = yargs;
|
||||
const { argv } = yargs
|
||||
|
||||
const runCLICommands = () => {
|
||||
// if the updatesuperuser command is passed from the CLI execute the updatesuperuser function
|
||||
if (argv._.includes('updatesuperuser')) {
|
||||
const { id, password, is_superuser } = argv;
|
||||
if (!password && is_superuser === undefined) {
|
||||
console.log(
|
||||
'Please provide either the --password or --is_superuser flag when using the updateuser command.',
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
updateSuperuser({ id, password, is_superuser });
|
||||
}
|
||||
}
|
||||
};
|
||||
// if the updatesuperuser command is passed from the CLI execute the updatesuperuser function
|
||||
if (argv._.includes('updatesuperuser')) {
|
||||
const { id, password, is_superuser } = argv
|
||||
if (!password && is_superuser === undefined) {
|
||||
console.log(
|
||||
'Please provide either the --password or --is_superuser flag when using the updateuser command.'
|
||||
)
|
||||
process.exit(1)
|
||||
} else {
|
||||
updateSuperuser({ id, password, is_superuser })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { runCLICommands };
|
||||
module.exports = { runCLICommands }
|
||||
|
||||
@@ -1,143 +1,131 @@
|
||||
const dotenv = require('dotenv');
|
||||
const Joi = require('joi');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv')
|
||||
const Joi = require('joi')
|
||||
const path = require('path')
|
||||
|
||||
const { yargs } = require('../cli');
|
||||
const { yargs } = require('../cli')
|
||||
|
||||
const { argv } = yargs;
|
||||
const { argv } = yargs
|
||||
|
||||
dotenv.config({ path: argv.envpath || path.join(__dirname, '../../.env') });
|
||||
dotenv.config({ path: argv.envpath || path.join(__dirname, '../../.env') })
|
||||
|
||||
const envVarsSchema = Joi.object()
|
||||
.keys({
|
||||
CORE_PORT: Joi.number().positive().default(8000),
|
||||
.keys({
|
||||
CORE_PORT: Joi.number().positive().default(8000),
|
||||
|
||||
NODE_ENV: Joi.string()
|
||||
.valid('production', 'development', 'test')
|
||||
.default('production'),
|
||||
NODE_ENV: Joi.string().valid('production', 'development', 'test').default('production'),
|
||||
|
||||
DB: Joi.string().required(),
|
||||
VERBOSE: Joi.string().valid('console', null).default(null),
|
||||
DB: Joi.string().required(),
|
||||
VERBOSE: Joi.string().valid('console', null).default(null),
|
||||
|
||||
CORS_ORIGIN_WHITELIST: Joi.string().default('*'),
|
||||
AUTH: Joi.boolean(),
|
||||
CORS_ORIGIN_WHITELIST: Joi.string().default('*'),
|
||||
AUTH: Joi.boolean(),
|
||||
|
||||
RATE_LIMIT_ENABLED: Joi.boolean().default(false),
|
||||
RATE_LIMIT_WINDOW_MS: Joi.number().positive().default(1000),
|
||||
RATE_LIMIT_MAX_REQUESTS: Joi.number().positive().default(10),
|
||||
RATE_LIMIT_ENABLED: Joi.boolean().default(false),
|
||||
RATE_LIMIT_WINDOW_MS: Joi.number().positive().default(1000),
|
||||
RATE_LIMIT_MAX_REQUESTS: Joi.number().positive().default(10),
|
||||
|
||||
EXTENSIONS: Joi.string().default(null),
|
||||
EXTENSIONS: Joi.string().default(null),
|
||||
|
||||
START_WITH_STUDIO: Joi.boolean().default(false),
|
||||
START_WITH_STUDIO: Joi.boolean().default(false),
|
||||
|
||||
INITIAL_USER_USERNAME: Joi.string(),
|
||||
INITIAL_USER_PASSWORD: Joi.string(),
|
||||
INITIAL_USER_USERNAME: Joi.string(),
|
||||
INITIAL_USER_PASSWORD: Joi.string(),
|
||||
|
||||
TOKEN_SECRET: Joi.string(),
|
||||
ACCESS_TOKEN_EXPIRATION_TIME: Joi.string(),
|
||||
REFRESH_TOKEN_EXPIRATION_TIME: Joi.string(),
|
||||
})
|
||||
.unknown();
|
||||
TOKEN_SECRET: Joi.string(),
|
||||
ACCESS_TOKEN_EXPIRATION_TIME: Joi.string(),
|
||||
REFRESH_TOKEN_EXPIRATION_TIME: Joi.string()
|
||||
})
|
||||
.unknown()
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
};
|
||||
...process.env
|
||||
}
|
||||
|
||||
if (argv.port) {
|
||||
env.CORE_PORT = argv.port;
|
||||
env.CORE_PORT = argv.port
|
||||
}
|
||||
|
||||
if (argv.verbose) {
|
||||
env.VERBOSE = argv.verbose;
|
||||
env.VERBOSE = argv.verbose
|
||||
}
|
||||
|
||||
if (argv.database) {
|
||||
env.DB = argv.database;
|
||||
env.DB = argv.database
|
||||
}
|
||||
|
||||
if (argv.cors) {
|
||||
env.CORS_ORIGIN_WHITELIST = argv.cors;
|
||||
env.CORS_ORIGIN_WHITELIST = argv.cors
|
||||
}
|
||||
|
||||
if (argv.auth) {
|
||||
env.AUTH = argv.auth;
|
||||
env.AUTH = argv.auth
|
||||
}
|
||||
|
||||
if (argv['rate-limit-enabled']) {
|
||||
env.RATE_LIMIT_ENABLED = argv['rate-limit-enabled'];
|
||||
env.RATE_LIMIT_ENABLED = argv['rate-limit-enabled']
|
||||
}
|
||||
|
||||
if (argv.tokensecret) {
|
||||
env.TOKEN_SECRET = argv.tokensecret;
|
||||
env.TOKEN_SECRET = argv.tokensecret
|
||||
}
|
||||
|
||||
if (argv.accesstokenexpirationtime) {
|
||||
env.ACCESS_TOKEN_EXPIRATION_TIME = argv.accesstokenexpirationtime;
|
||||
env.ACCESS_TOKEN_EXPIRATION_TIME = argv.accesstokenexpirationtime
|
||||
}
|
||||
|
||||
if (argv.refreshtokenexpirationtime) {
|
||||
env.REFRESH_TOKEN_EXPIRATION_TIME = argv.refreshtokenexpirationtime;
|
||||
env.REFRESH_TOKEN_EXPIRATION_TIME = argv.refreshtokenexpirationtime
|
||||
}
|
||||
|
||||
if (argv.initialuserusername) {
|
||||
env.INITIAL_USER_USERNAME = argv.initialuserusername;
|
||||
env.INITIAL_USER_USERNAME = argv.initialuserusername
|
||||
}
|
||||
|
||||
if (argv.initialuserpassword) {
|
||||
env.INITIAL_USER_PASSWORD = argv.initialuserpassword;
|
||||
env.INITIAL_USER_PASSWORD = argv.initialuserpassword
|
||||
}
|
||||
|
||||
const { value: envVars, error } = envVarsSchema
|
||||
.prefs({ errors: { label: 'key' } })
|
||||
.validate(env);
|
||||
const { value: envVars, error } = envVarsSchema.prefs({ errors: { label: 'key' } }).validate(env)
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Config validation error: ${error.message}`);
|
||||
throw new Error(`Config validation error: ${error.message}`)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
env: envVars.NODE_ENV,
|
||||
env: envVars.NODE_ENV,
|
||||
|
||||
isProduction: envVars.NODE_ENV === 'production',
|
||||
isDevelopment: envVars.NODE_ENV === 'development',
|
||||
isTest: envVars.NODE_ENV === 'test',
|
||||
isProduction: envVars.NODE_ENV === 'production',
|
||||
isDevelopment: envVars.NODE_ENV === 'development',
|
||||
isTest: envVars.NODE_ENV === 'test',
|
||||
|
||||
port: argv.port || envVars.CORE_PORT,
|
||||
verbose: argv['verbose'] || envVars.VERBOSE,
|
||||
port: argv.port || envVars.CORE_PORT,
|
||||
verbose: argv['verbose'] || envVars.VERBOSE,
|
||||
|
||||
db: {
|
||||
filename: argv.database || envVars.DB || ':memory:',
|
||||
},
|
||||
cors: {
|
||||
origin: argv.cors?.split(',') ||
|
||||
envVars.CORS_ORIGIN_WHITELIST?.split(',') || ['*'],
|
||||
},
|
||||
db: {
|
||||
filename: argv.database || envVars.DB || ':memory:'
|
||||
},
|
||||
cors: {
|
||||
origin: argv.cors?.split(',') || envVars.CORS_ORIGIN_WHITELIST?.split(',') || ['*']
|
||||
},
|
||||
|
||||
auth: argv.auth || envVars.AUTH || false,
|
||||
tokenSecret: argv.tokensecret || envVars.TOKEN_SECRET || null,
|
||||
accessTokenExpirationTime:
|
||||
argv.accesstokenexpirationtime ||
|
||||
envVars.ACCESS_TOKEN_EXPIRATION_TIME ||
|
||||
'5H',
|
||||
refreshTokenExpirationTime:
|
||||
argv.refreshtokenexpirationtime ||
|
||||
envVars.REFRESH_TOKEN_EXPIRATION_TIME ||
|
||||
'3D',
|
||||
auth: argv.auth || envVars.AUTH || false,
|
||||
tokenSecret: argv.tokensecret || envVars.TOKEN_SECRET || null,
|
||||
accessTokenExpirationTime: argv.accesstokenexpirationtime || envVars.ACCESS_TOKEN_EXPIRATION_TIME || '5H',
|
||||
refreshTokenExpirationTime:
|
||||
argv.refreshtokenexpirationtime || envVars.REFRESH_TOKEN_EXPIRATION_TIME || '3D',
|
||||
|
||||
initialUserUsername:
|
||||
argv.initialuserusername || envVars.INITIAL_USER_USERNAME,
|
||||
initialUserPassword:
|
||||
argv.initialuserpassword || envVars.INITIAL_USER_PASSWORD,
|
||||
initialUserUsername: argv.initialuserusername || envVars.INITIAL_USER_USERNAME,
|
||||
initialUserPassword: argv.initialuserpassword || envVars.INITIAL_USER_PASSWORD,
|
||||
|
||||
rateLimit: {
|
||||
enabled: argv['rate-limit-enabled'] || envVars.RATE_LIMIT_ENABLED,
|
||||
windowMs: envVars.RATE_LIMIT_WINDOW,
|
||||
max: envVars.RATE_LIMIT_MAX,
|
||||
},
|
||||
rateLimit: {
|
||||
enabled: argv['rate-limit-enabled'] || envVars.RATE_LIMIT_ENABLED,
|
||||
windowMs: envVars.RATE_LIMIT_WINDOW,
|
||||
max: envVars.RATE_LIMIT_MAX
|
||||
},
|
||||
|
||||
extensions: {
|
||||
path: argv.extensions || envVars.EXTENSIONS,
|
||||
},
|
||||
extensions: {
|
||||
path: argv.extensions || envVars.EXTENSIONS
|
||||
},
|
||||
|
||||
startWithStudio: argv.studio || envVars.START_WITH_STUDIO,
|
||||
};
|
||||
startWithStudio: argv.studio || envVars.START_WITH_STUDIO
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
module.exports = {
|
||||
authEndpoints: ['_users', '_roles', '_roles_permissions', '_users_roles'],
|
||||
baseTableUrl: '/api/tables',
|
||||
universalAccessEndpoints: ['/api/auth/change-password'],
|
||||
authEndpoints: ['_users', '_roles', '_roles_permissions', '_users_roles'],
|
||||
baseTableUrl: '/api/tables',
|
||||
universalAccessEndpoints: ['/api/auth/change-password'],
|
||||
|
||||
DEFAULT_PAGE_LIMIT: 10,
|
||||
DEFAULT_PAGE_INDEX: 0,
|
||||
PASSWORD: {
|
||||
TOO_WEAK: 'Too weak',
|
||||
WEAK: 'Weak',
|
||||
},
|
||||
DEFAULT_PAGE_LIMIT: 10,
|
||||
DEFAULT_PAGE_INDEX: 0,
|
||||
PASSWORD: {
|
||||
TOO_WEAK: 'Too weak',
|
||||
WEAK: 'Weak'
|
||||
},
|
||||
|
||||
httpVerbs: {
|
||||
POST: 'POST',
|
||||
GET: 'GET',
|
||||
PUT: 'PUT',
|
||||
DELETE: 'DELETE',
|
||||
},
|
||||
httpVerbs: {
|
||||
POST: 'POST',
|
||||
GET: 'GET',
|
||||
PUT: 'PUT',
|
||||
DELETE: 'DELETE'
|
||||
},
|
||||
|
||||
httpMethodDefinitions: {
|
||||
POST: 'CREATE',
|
||||
GET: 'READ',
|
||||
PUT: 'UPDATE',
|
||||
DELETE: 'DELETE',
|
||||
},
|
||||
};
|
||||
httpMethodDefinitions: {
|
||||
POST: 'CREATE',
|
||||
GET: 'READ',
|
||||
PUT: 'UPDATE',
|
||||
DELETE: 'DELETE'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
SALT_ROUNDS: 10,
|
||||
ACCESS_TOKEN_SUBJECT: 'accessToken',
|
||||
REFRESH_TOKEN_SUBJECT: 'refreshToken',
|
||||
REVOKED_REFRESH_TOKENS_REMOVAL_TIME_RANGE: 3 * 24 * 60 * 60 * 1000, // 3 days * 24 hours * 60 minutes * 60 seconds * 1000 milliseconds =
|
||||
};
|
||||
SALT_ROUNDS: 10,
|
||||
ACCESS_TOKEN_SUBJECT: 'accessToken',
|
||||
REFRESH_TOKEN_SUBJECT: 'refreshToken',
|
||||
REVOKED_REFRESH_TOKENS_REMOVAL_TIME_RANGE: 3 * 24 * 60 * 60 * 1000 // 3 days * 24 hours * 60 minutes * 60 seconds * 1000 milliseconds =
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const dbConstants = require('./tables');
|
||||
const apiConstants = require('./api');
|
||||
const constantRoles = require('./roles');
|
||||
const responseMessages = require('./messages');
|
||||
const authConstants = require('./auth');
|
||||
const dbConstants = require('./tables')
|
||||
const apiConstants = require('./api')
|
||||
const constantRoles = require('./roles')
|
||||
const responseMessages = require('./messages')
|
||||
const authConstants = require('./auth')
|
||||
|
||||
module.exports = {
|
||||
dbConstants,
|
||||
apiConstants,
|
||||
constantRoles,
|
||||
responseMessages,
|
||||
authConstants,
|
||||
};
|
||||
dbConstants,
|
||||
apiConstants,
|
||||
constantRoles,
|
||||
responseMessages,
|
||||
authConstants
|
||||
}
|
||||
|
||||
@@ -1,43 +1,41 @@
|
||||
module.exports = {
|
||||
successMessage: {
|
||||
SUCCESS: 'Success',
|
||||
ROW_INSERTED: 'Row Inserted',
|
||||
PASSWORD_UPDATE_SUCCESS: 'Password updated successfully',
|
||||
USER_UPDATE_SUCCESS: 'User updated successfully',
|
||||
INITIAL_USER_CREATED_SUCCESS: 'Initial user created successfully',
|
||||
LOGOUT_MESSAGE: 'Logout successful',
|
||||
},
|
||||
successMessage: {
|
||||
SUCCESS: 'Success',
|
||||
ROW_INSERTED: 'Row Inserted',
|
||||
PASSWORD_UPDATE_SUCCESS: 'Password updated successfully',
|
||||
USER_UPDATE_SUCCESS: 'User updated successfully',
|
||||
INITIAL_USER_CREATED_SUCCESS: 'Initial user created successfully',
|
||||
LOGOUT_MESSAGE: 'Logout successful'
|
||||
},
|
||||
|
||||
errorMessage: {
|
||||
USERNAME_TAKEN_ERROR: 'This username is taken',
|
||||
WEAK_PASSWORD_ERROR:
|
||||
'This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters',
|
||||
DEFAULT_ROLE_NOT_CREATED_ERROR:
|
||||
'Please restart soul so a default role can be created',
|
||||
INVALID_USERNAME_PASSWORD_ERROR: 'Invalid username or password',
|
||||
INVALID_REFRESH_TOKEN_ERROR: 'Invalid refresh token',
|
||||
INVALID_ACCESS_TOKEN_ERROR: 'Invalid access token',
|
||||
USER_NOT_FOUND_ERROR: 'User not found',
|
||||
INVALID_CURRENT_PASSWORD_ERROR: 'Invalid current password',
|
||||
NOT_AUTHORIZED_ERROR: 'Not authorized',
|
||||
PERMISSION_NOT_DEFINED_ERROR: 'Permission not defined for this role',
|
||||
ROLE_NOT_FOUND_ERROR: 'Role not found for this user',
|
||||
AUTH_SET_TO_FALSE_ERROR:
|
||||
'You can not access this endpoint while AUTH is set to false',
|
||||
RESERVED_TABLE_NAME_ERROR:
|
||||
'The table name is reserved. Please choose a different name for the table.',
|
||||
SERVER_ERROR: 'Server error',
|
||||
errorMessage: {
|
||||
USERNAME_TAKEN_ERROR: 'This username is taken',
|
||||
WEAK_PASSWORD_ERROR:
|
||||
'This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters',
|
||||
DEFAULT_ROLE_NOT_CREATED_ERROR: 'Please restart soul so a default role can be created',
|
||||
INVALID_USERNAME_PASSWORD_ERROR: 'Invalid username or password',
|
||||
INVALID_REFRESH_TOKEN_ERROR: 'Invalid refresh token',
|
||||
INVALID_ACCESS_TOKEN_ERROR: 'Invalid access token',
|
||||
USER_NOT_FOUND_ERROR: 'User not found',
|
||||
INVALID_CURRENT_PASSWORD_ERROR: 'Invalid current password',
|
||||
NOT_AUTHORIZED_ERROR: 'Not authorized',
|
||||
PERMISSION_NOT_DEFINED_ERROR: 'Permission not defined for this role',
|
||||
ROLE_NOT_FOUND_ERROR: 'Role not found for this user',
|
||||
AUTH_SET_TO_FALSE_ERROR: 'You can not access this endpoint while AUTH is set to false',
|
||||
RESERVED_TABLE_NAME_ERROR:
|
||||
'The table name is reserved. Please choose a different name for the table.',
|
||||
SERVER_ERROR: 'Server error',
|
||||
|
||||
INITIAL_USER_USERNAME_NOT_PASSED_ERROR:
|
||||
'Error: You should pass the initial users username either from the CLI with the --iuu or from the environment variable using the INITIAL_USER_USERNAME flag',
|
||||
INITIAL_USER_PASSWORD_NOT_PASSED_ERROR:
|
||||
'Error: You should pass the initial users password either from the CLI with the --iup or from the environment variable using the INITIAL_USER_PASSWORD flag',
|
||||
INITIAL_USER_USERNAME_NOT_PASSED_ERROR:
|
||||
'Error: You should pass the initial users username either from the CLI with the --iuu or from the environment variable using the INITIAL_USER_USERNAME flag',
|
||||
INITIAL_USER_PASSWORD_NOT_PASSED_ERROR:
|
||||
'Error: You should pass the initial users password either from the CLI with the --iup or from the environment variable using the INITIAL_USER_PASSWORD flag',
|
||||
|
||||
USERNAME_REQUIRED_ERROR: 'username is required',
|
||||
PASSWORD_REQUIRED_ERROR: 'password is required',
|
||||
},
|
||||
USERNAME_REQUIRED_ERROR: 'username is required',
|
||||
PASSWORD_REQUIRED_ERROR: 'password is required'
|
||||
},
|
||||
|
||||
infoMessage: {
|
||||
INITIAL_USER_ALREADY_CREATED: 'Initial user is already created',
|
||||
},
|
||||
};
|
||||
infoMessage: {
|
||||
INITIAL_USER_ALREADY_CREATED: 'Initial user is already created'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
DEFAULT_ROLE: 'default',
|
||||
};
|
||||
DEFAULT_ROLE: 'default'
|
||||
}
|
||||
|
||||
@@ -1,54 +1,49 @@
|
||||
const USERS_TABLE = '_users';
|
||||
const ROLES_TABLE = '_roles';
|
||||
const USERS_ROLES_TABLE = '_users_roles';
|
||||
const ROLES_PERMISSIONS_TABLE = '_roles_permissions';
|
||||
const REVOKED_REFRESH_TOKENS_TABLE = '_revoked_refresh_tokens';
|
||||
const USERS_TABLE = '_users'
|
||||
const ROLES_TABLE = '_roles'
|
||||
const USERS_ROLES_TABLE = '_users_roles'
|
||||
const ROLES_PERMISSIONS_TABLE = '_roles_permissions'
|
||||
const REVOKED_REFRESH_TOKENS_TABLE = '_revoked_refresh_tokens'
|
||||
|
||||
module.exports = {
|
||||
// db table names
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
// db table names
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
|
||||
reservedTableNames: [
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
],
|
||||
reservedTableNames: [USERS_TABLE, ROLES_TABLE, USERS_ROLES_TABLE, ROLES_PERMISSIONS_TABLE],
|
||||
|
||||
constraints: {
|
||||
UNIQUE_USERS_ROLE: 'unique_users_role',
|
||||
UNIQUE_ROLES_TABLE: 'unique_ROLES_TABLE',
|
||||
},
|
||||
constraints: {
|
||||
UNIQUE_USERS_ROLE: 'unique_users_role',
|
||||
UNIQUE_ROLES_TABLE: 'unique_ROLES_TABLE'
|
||||
},
|
||||
|
||||
tableFields: {
|
||||
ID: 'id',
|
||||
tableFields: {
|
||||
ID: 'id',
|
||||
|
||||
// _role fields
|
||||
ROLE_NAME: 'name',
|
||||
// _role fields
|
||||
ROLE_NAME: 'name',
|
||||
|
||||
// _user fields
|
||||
USERNAME: 'username',
|
||||
HASHED_PASSWORD: 'hashed_password',
|
||||
SALT: 'salt',
|
||||
IS_SUPERUSER: 'is_superuser',
|
||||
// _user fields
|
||||
USERNAME: 'username',
|
||||
HASHED_PASSWORD: 'hashed_password',
|
||||
SALT: 'salt',
|
||||
IS_SUPERUSER: 'is_superuser',
|
||||
|
||||
// _roles_permissions fields
|
||||
ROLE_ID: 'role_id',
|
||||
TABLE_NAME: 'table_name',
|
||||
CREATE: 'create',
|
||||
READ: 'read',
|
||||
UPDATE: 'update',
|
||||
DELETE: 'delete',
|
||||
// _roles_permissions fields
|
||||
ROLE_ID: 'role_id',
|
||||
TABLE_NAME: 'table_name',
|
||||
CREATE: 'create',
|
||||
READ: 'read',
|
||||
UPDATE: 'update',
|
||||
DELETE: 'delete',
|
||||
|
||||
// _users_roles fields
|
||||
USER_ID: 'user_id',
|
||||
// _users_roles fields
|
||||
USER_ID: 'user_id',
|
||||
|
||||
//_revoked_refresh_tokens
|
||||
REFRESH_TOKEN: 'refresh_token',
|
||||
EXPIRES_AT: 'expires_at',
|
||||
},
|
||||
};
|
||||
//_revoked_refresh_tokens
|
||||
REFRESH_TOKEN: 'refresh_token',
|
||||
EXPIRES_AT: 'expires_at'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,372 +1,364 @@
|
||||
const supertest = require('supertest');
|
||||
const supertest = require('supertest')
|
||||
|
||||
const app = require('../index');
|
||||
const config = require('../config');
|
||||
const { generateToken } = require('../utils');
|
||||
const { testData } = require('../tests/testData');
|
||||
const app = require('../index')
|
||||
const config = require('../config')
|
||||
const { generateToken } = require('../utils')
|
||||
const { testData } = require('../tests/testData')
|
||||
|
||||
const requestWithSupertest = supertest(app);
|
||||
const requestWithSupertest = supertest(app)
|
||||
|
||||
describe('Auth Endpoints', () => {
|
||||
describe('User Endpoints', () => {
|
||||
it('POST /tables/_users/rows should register a user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', userId: 1, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
describe('User Endpoints', () => {
|
||||
it('POST /tables/_users/rows should register a user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', userId: 1, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword,
|
||||
},
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(201);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.status).toEqual(201)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Row Inserted');
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Row Inserted')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('POST /tables/_users/rows should throw 400 error if username is not passed', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('POST /tables/_users/rows should throw 400 error if username is not passed', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: { password: testData.strongPassword },
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: { password: testData.strongPassword }
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.body.message).toBe('username is required');
|
||||
expect(res.status).toEqual(400)
|
||||
expect(res.body.message).toBe('username is required')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('POST /tables/_users/rows should throw 400 error if the password is not strong', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('POST /tables/_users/rows should throw 400 error if the password is not strong', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user2.username,
|
||||
password: testData.weakPassword,
|
||||
},
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user2.username,
|
||||
password: testData.weakPassword
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.body.message).toBe(
|
||||
'This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters',
|
||||
);
|
||||
expect(res.status).toEqual(400)
|
||||
expect(res.body.message).toBe(
|
||||
'This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters'
|
||||
)
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('POST /tables/_users/rows should throw 409 error if the username is taken', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('POST /tables/_users/rows should throw 409 error if the username is taken', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword,
|
||||
},
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(409);
|
||||
expect(res.body.message).toBe('This username is taken');
|
||||
expect(res.status).toEqual(409)
|
||||
expect(res.body.message).toBe('This username is taken')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('GET /tables/_users/rows should return list of users', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/_users/rows should return list of users', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/_users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('username');
|
||||
expect(res.body.data[0]).toHaveProperty('is_superuser');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('username')
|
||||
expect(res.body.data[0]).toHaveProperty('is_superuser')
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt')
|
||||
|
||||
expect(res.body.data[0]).not.toHaveProperty('password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('hashed_password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body.data[0]).not.toHaveProperty('password')
|
||||
expect(res.body.data[0]).not.toHaveProperty('hashed_password')
|
||||
expect(res.body.data[0]).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('GET /tables/_users/rows/:id should retrive a single user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/_users/rows/:id should retrive a single user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/_users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/_users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('username');
|
||||
expect(res.body.data[0]).toHaveProperty('is_superuser');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('username')
|
||||
expect(res.body.data[0]).toHaveProperty('is_superuser')
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt')
|
||||
|
||||
expect(res.body.data[0]).not.toHaveProperty('password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('hashed_password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body.data[0]).not.toHaveProperty('password')
|
||||
expect(res.body.data[0]).not.toHaveProperty('hashed_password')
|
||||
expect(res.body.data[0]).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('PUT /tables/_users/rows/:id should update a user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('PUT /tables/_users/rows/:id should update a user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/tables/_users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user3.username,
|
||||
},
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/tables/_users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user3.username
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Row updated');
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Row updated')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('DELETE /tables/_users/rows/:id should remove a user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('DELETE /tables/_users/rows/:id should remove a user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.delete('/api/tables/_users/rows/2')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.delete('/api/tables/_users/rows/2')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.body.message).toBe('FOREIGN KEY constraint failed');
|
||||
expect(res.status).toEqual(400)
|
||||
expect(res.body.message).toBe('FOREIGN KEY constraint failed')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Obtain Access Token Endpoint', () => {
|
||||
it('POST /auth/token/obtain should return an access token and refresh token values and a success message', async () => {
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/auth/token/obtain')
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword,
|
||||
},
|
||||
});
|
||||
describe('Obtain Access Token Endpoint', () => {
|
||||
it('POST /auth/token/obtain should return an access token and refresh token values and a success message', async () => {
|
||||
const res = await requestWithSupertest.post('/api/auth/token/obtain').send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(201);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Success');
|
||||
expect(res.status).toEqual(201)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Success')
|
||||
|
||||
expect(res.headers['set-cookie']).toBeDefined();
|
||||
expect(res.headers['set-cookie']).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('refreshToken='),
|
||||
expect.stringContaining('accessToken='),
|
||||
]),
|
||||
);
|
||||
expect(res.headers['set-cookie']).toBeDefined()
|
||||
expect(res.headers['set-cookie']).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('refreshToken='),
|
||||
expect.stringContaining('accessToken=')
|
||||
])
|
||||
)
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('POST /auth/token/obtain should throw a 401 error if the username does not exist in the DB', async () => {
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/auth/token/obtain')
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.invalidUsername,
|
||||
password: testData.strongPassword,
|
||||
},
|
||||
});
|
||||
it('POST /auth/token/obtain should throw a 401 error if the username does not exist in the DB', async () => {
|
||||
const res = await requestWithSupertest.post('/api/auth/token/obtain').send({
|
||||
fields: {
|
||||
username: testData.invalidUsername,
|
||||
password: testData.strongPassword
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Invalid username or password');
|
||||
expect(res.status).toEqual(401)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Invalid username or password')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
|
||||
it('POST /auth/token/obtain should throw a 401 error if the password is invalid', async () => {
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/auth/token/obtain')
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.invalidPassword,
|
||||
},
|
||||
});
|
||||
it('POST /auth/token/obtain should throw a 401 error if the password is invalid', async () => {
|
||||
const res = await requestWithSupertest.post('/api/auth/token/obtain').send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.invalidPassword
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Invalid username or password');
|
||||
expect(res.status).toEqual(401)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Invalid username or password')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Refresh Access Token Endpoint', () => {
|
||||
it('GET /auth/token/refresh should refresh the access and refresh tokens', async () => {
|
||||
const refreshToken = await generateToken(
|
||||
{ username: 'John', userId: 1, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
describe('Refresh Access Token Endpoint', () => {
|
||||
it('GET /auth/token/refresh should refresh the access and refresh tokens', async () => {
|
||||
const refreshToken = await generateToken(
|
||||
{ username: 'John', userId: 1, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/auth/token/refresh')
|
||||
.set('Cookie', [`refreshToken=${refreshToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/auth/token/refresh')
|
||||
.set('Cookie', [`refreshToken=${refreshToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Success');
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Success')
|
||||
|
||||
expect(res.headers['set-cookie']).toBeDefined();
|
||||
expect(res.headers['set-cookie']).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('refreshToken='),
|
||||
expect.stringContaining('accessToken='),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(res.headers['set-cookie']).toBeDefined()
|
||||
expect(res.headers['set-cookie']).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('refreshToken='),
|
||||
expect.stringContaining('accessToken=')
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Change Password Endpoint', () => {
|
||||
it('PUT /auth/change-password/ should change a password', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', userId: 2, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
describe('Change Password Endpoint', () => {
|
||||
it('PUT /auth/change-password/ should change a password', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', userId: 2, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/auth/change-password')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
currentPassword: testData.strongPassword,
|
||||
newPassword: testData.strongPassword2,
|
||||
},
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/auth/change-password')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
currentPassword: testData.strongPassword,
|
||||
newPassword: testData.strongPassword2
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Password updated successfully');
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Password updated successfully')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
|
||||
// check if the password is really updated
|
||||
const res2 = await requestWithSupertest
|
||||
.post('/api/auth/token/obtain')
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword2,
|
||||
},
|
||||
});
|
||||
// check if the password is really updated
|
||||
const res2 = await requestWithSupertest.post('/api/auth/token/obtain').send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword2
|
||||
}
|
||||
})
|
||||
|
||||
expect(res2.status).toEqual(201);
|
||||
expect(res2.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res2.body).toHaveProperty('message');
|
||||
expect(res2.body.message).toBe('Success');
|
||||
});
|
||||
expect(res2.status).toEqual(201)
|
||||
expect(res2.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res2.body).toHaveProperty('message')
|
||||
expect(res2.body.message).toBe('Success')
|
||||
})
|
||||
|
||||
it('PUT /auth/change-password/ should throw 401 error if the current password is not valid', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', userId: 2, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('PUT /auth/change-password/ should throw 401 error if the current password is not valid', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', userId: 2, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/auth/change-password')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
currentPassword: testData.invalidPassword,
|
||||
newPassword: testData.strongPassword2,
|
||||
},
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/auth/change-password')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
currentPassword: testData.invalidPassword,
|
||||
newPassword: testData.strongPassword2
|
||||
}
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Invalid current password');
|
||||
expect(res.status).toEqual(401)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body.message).toBe('Invalid current password')
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(res.body).not.toHaveProperty('password')
|
||||
expect(res.body).not.toHaveProperty('hashed_password')
|
||||
expect(res.body).not.toHaveProperty('salt')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
const { rowService } = require('../../services');
|
||||
const { dbConstants } = require('../../constants');
|
||||
const { rowService } = require('../../services')
|
||||
const { dbConstants } = require('../../constants')
|
||||
|
||||
const { USERS_TABLE } = dbConstants;
|
||||
const { USERS_TABLE } = dbConstants
|
||||
|
||||
const isUsernameTaken = (username) => {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: 'WHERE username=?',
|
||||
whereStringValues: [username],
|
||||
});
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: 'WHERE username=?',
|
||||
whereStringValues: [username]
|
||||
})
|
||||
|
||||
return users.length > 0;
|
||||
};
|
||||
return users.length > 0
|
||||
}
|
||||
|
||||
const checkAuthConfigs = ({ auth, tokenSecret }) => {
|
||||
if (auth && !tokenSecret) {
|
||||
throw new Error(
|
||||
'You need to provide a token secret either from the CLI or from your environment variables',
|
||||
);
|
||||
}
|
||||
};
|
||||
if (auth && !tokenSecret) {
|
||||
throw new Error(
|
||||
'You need to provide a token secret either from the CLI or from your environment variables'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { isUsernameTaken, checkAuthConfigs };
|
||||
module.exports = { isUsernameTaken, checkAuthConfigs }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const users = require('./user');
|
||||
const token = require('./token');
|
||||
const tables = require('./tables');
|
||||
const { checkAuthConfigs } = require('./common');
|
||||
const users = require('./user')
|
||||
const token = require('./token')
|
||||
const tables = require('./tables')
|
||||
const { checkAuthConfigs } = require('./common')
|
||||
|
||||
module.exports = { ...users, ...token, ...tables, checkAuthConfigs };
|
||||
module.exports = { ...users, ...token, ...tables, checkAuthConfigs }
|
||||
|
||||
@@ -1,108 +1,97 @@
|
||||
const { tableService, rowService } = require('../../services');
|
||||
const { constantRoles, dbConstants } = require('../../constants');
|
||||
const schema = require('../../db/schema');
|
||||
const { tableService, rowService } = require('../../services')
|
||||
const { constantRoles, dbConstants } = require('../../constants')
|
||||
const schema = require('../../db/schema')
|
||||
|
||||
const {
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
constraints,
|
||||
tableFields,
|
||||
} = dbConstants;
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
constraints,
|
||||
tableFields
|
||||
} = dbConstants
|
||||
|
||||
const createDefaultTables = async () => {
|
||||
let roleId;
|
||||
let roleId
|
||||
|
||||
// check if the default tables are already created
|
||||
const roleTable = tableService.checkTableExists(ROLES_TABLE);
|
||||
const usersTable = tableService.checkTableExists(USERS_TABLE);
|
||||
const rolesPermissionTable = tableService.checkTableExists(
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
);
|
||||
const usersRolesTable = tableService.checkTableExists(USERS_ROLES_TABLE);
|
||||
const revokedRefreshTokensTable = tableService.checkTableExists(
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
);
|
||||
// check if the default tables are already created
|
||||
const roleTable = tableService.checkTableExists(ROLES_TABLE)
|
||||
const usersTable = tableService.checkTableExists(USERS_TABLE)
|
||||
const rolesPermissionTable = tableService.checkTableExists(ROLES_PERMISSIONS_TABLE)
|
||||
const usersRolesTable = tableService.checkTableExists(USERS_ROLES_TABLE)
|
||||
const revokedRefreshTokensTable = tableService.checkTableExists(REVOKED_REFRESH_TOKENS_TABLE)
|
||||
|
||||
// create _users table
|
||||
if (!usersTable) {
|
||||
tableService.createTable(USERS_TABLE, schema.userSchema);
|
||||
}
|
||||
// create _users table
|
||||
if (!usersTable) {
|
||||
tableService.createTable(USERS_TABLE, schema.userSchema)
|
||||
}
|
||||
|
||||
// create _users_roles table
|
||||
if (!usersRolesTable) {
|
||||
tableService.createTable(
|
||||
USERS_ROLES_TABLE,
|
||||
// create _users_roles table
|
||||
if (!usersRolesTable) {
|
||||
tableService.createTable(
|
||||
USERS_ROLES_TABLE,
|
||||
|
||||
schema.usersRoleSchema,
|
||||
{
|
||||
multipleUniqueConstraints: {
|
||||
name: constraints.UNIQUE_USERS_ROLE,
|
||||
fields: [tableFields.USER_ID, tableFields.USER_ID],
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
schema.usersRoleSchema,
|
||||
{
|
||||
multipleUniqueConstraints: {
|
||||
name: constraints.UNIQUE_USERS_ROLE,
|
||||
fields: [tableFields.USER_ID, tableFields.USER_ID]
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// create _roles table
|
||||
if (!roleTable) {
|
||||
tableService.createTable(ROLES_TABLE, schema.roleSchema);
|
||||
// create _roles table
|
||||
if (!roleTable) {
|
||||
tableService.createTable(ROLES_TABLE, schema.roleSchema)
|
||||
|
||||
// create a default role in the _roles table
|
||||
const role = rowService.save({
|
||||
tableName: ROLES_TABLE,
|
||||
fields: { name: constantRoles.DEFAULT_ROLE },
|
||||
});
|
||||
roleId = role.lastInsertRowid;
|
||||
}
|
||||
// create a default role in the _roles table
|
||||
const role = rowService.save({
|
||||
tableName: ROLES_TABLE,
|
||||
fields: { name: constantRoles.DEFAULT_ROLE }
|
||||
})
|
||||
roleId = role.lastInsertRowid
|
||||
}
|
||||
|
||||
// create _roles_permissions table
|
||||
if (!rolesPermissionTable && roleId) {
|
||||
tableService.createTable(
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
schema.rolePermissionSchema,
|
||||
{
|
||||
multipleUniqueConstraints: {
|
||||
name: constraints.UNIQUE_ROLES_TABLE,
|
||||
fields: [tableFields.ROLE_ID, tableFields.TABLE_NAME],
|
||||
},
|
||||
},
|
||||
);
|
||||
// create _roles_permissions table
|
||||
if (!rolesPermissionTable && roleId) {
|
||||
tableService.createTable(ROLES_PERMISSIONS_TABLE, schema.rolePermissionSchema, {
|
||||
multipleUniqueConstraints: {
|
||||
name: constraints.UNIQUE_ROLES_TABLE,
|
||||
fields: [tableFields.ROLE_ID, tableFields.TABLE_NAME]
|
||||
}
|
||||
})
|
||||
|
||||
// fetch all DB tables
|
||||
const tables = tableService.listTables();
|
||||
// fetch all DB tables
|
||||
const tables = tableService.listTables()
|
||||
|
||||
// add permission for the default role (for each db table)
|
||||
const permissions = [];
|
||||
for (const table of tables) {
|
||||
permissions.push({
|
||||
role_id: roleId,
|
||||
table_name: table.name,
|
||||
create: 0,
|
||||
read: 1,
|
||||
update: 0,
|
||||
delete: 0,
|
||||
});
|
||||
}
|
||||
// add permission for the default role (for each db table)
|
||||
const permissions = []
|
||||
for (const table of tables) {
|
||||
permissions.push({
|
||||
role_id: roleId,
|
||||
table_name: table.name,
|
||||
create: 0,
|
||||
read: 1,
|
||||
update: 0,
|
||||
delete: 0
|
||||
})
|
||||
}
|
||||
|
||||
// store the permissions in the db
|
||||
rowService.bulkWrite({
|
||||
tableName: ROLES_PERMISSIONS_TABLE,
|
||||
fields: permissions,
|
||||
});
|
||||
}
|
||||
// store the permissions in the db
|
||||
rowService.bulkWrite({
|
||||
tableName: ROLES_PERMISSIONS_TABLE,
|
||||
fields: permissions
|
||||
})
|
||||
}
|
||||
|
||||
// create _revoked_refresh_tokens table
|
||||
if (!revokedRefreshTokensTable) {
|
||||
tableService.createTable(
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
schema.revokedRefreshTokensSchema,
|
||||
);
|
||||
}
|
||||
};
|
||||
// create _revoked_refresh_tokens table
|
||||
if (!revokedRefreshTokensTable) {
|
||||
tableService.createTable(REVOKED_REFRESH_TOKENS_TABLE, schema.revokedRefreshTokensSchema)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createDefaultTables,
|
||||
};
|
||||
createDefaultTables
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
const { authService } = require('../../services');
|
||||
const { responseMessages, authConstants } = require('../../constants');
|
||||
const config = require('../../config');
|
||||
const {
|
||||
comparePasswords,
|
||||
generateToken,
|
||||
decodeToken,
|
||||
toBoolean,
|
||||
} = require('../../utils');
|
||||
const { authService } = require('../../services')
|
||||
const { responseMessages, authConstants } = require('../../constants')
|
||||
const config = require('../../config')
|
||||
const { comparePasswords, generateToken, decodeToken, toBoolean } = require('../../utils')
|
||||
|
||||
const { successMessage, errorMessage } = responseMessages;
|
||||
const { successMessage, errorMessage } = responseMessages
|
||||
|
||||
const obtainAccessToken = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Obtain Access Token'
|
||||
#swagger.summary = 'Obtain Access Token'
|
||||
#swagger.description = 'Endpoint to generate access and refresh tokens'
|
||||
|
||||
#swagger.parameters['body'] = {
|
||||
@@ -24,28 +19,24 @@ const obtainAccessToken = async (req, res) => {
|
||||
}
|
||||
*/
|
||||
|
||||
// extract payload
|
||||
const { username, password } = req.body.fields;
|
||||
// extract payload
|
||||
const { username, password } = req.body.fields
|
||||
|
||||
try {
|
||||
// check if the username exists in the Db
|
||||
const users = authService.getUsersByUsername({ username });
|
||||
try {
|
||||
// check if the username exists in the Db
|
||||
const users = authService.getUsersByUsername({ username })
|
||||
|
||||
if (users.length <= 0) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.INVALID_USERNAME_PASSWORD_ERROR });
|
||||
}
|
||||
if (users.length <= 0) {
|
||||
return res.status(401).send({ message: errorMessage.INVALID_USERNAME_PASSWORD_ERROR })
|
||||
}
|
||||
|
||||
// check if the password is valid
|
||||
const user = users[0];
|
||||
const isMatch = await comparePasswords(password, user.hashed_password);
|
||||
// check if the password is valid
|
||||
const user = users[0]
|
||||
const isMatch = await comparePasswords(password, user.hashed_password)
|
||||
|
||||
if (!isMatch) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.INVALID_USERNAME_PASSWORD_ERROR });
|
||||
/*
|
||||
if (!isMatch) {
|
||||
return res.status(401).send({ message: errorMessage.INVALID_USERNAME_PASSWORD_ERROR })
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'Invalid username or password error',
|
||||
schema: {
|
||||
@@ -53,61 +44,53 @@ const obtainAccessToken = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
let roleIds;
|
||||
let roleIds
|
||||
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
try {
|
||||
const roleData = getUsersRoleAndPermission({
|
||||
userId: user.id,
|
||||
res,
|
||||
});
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
try {
|
||||
const roleData = getUsersRoleAndPermission({
|
||||
userId: user.id,
|
||||
res
|
||||
})
|
||||
|
||||
roleIds = roleData.roleIds;
|
||||
} catch (err) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.ROLE_NOT_FOUND_ERROR });
|
||||
}
|
||||
}
|
||||
roleIds = roleData.roleIds
|
||||
} catch (err) {
|
||||
return res.status(401).send({ message: errorMessage.ROLE_NOT_FOUND_ERROR })
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds,
|
||||
};
|
||||
const payload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds
|
||||
}
|
||||
|
||||
// generate an access token
|
||||
const accessToken = await generateToken(
|
||||
{ subject: authConstants.ACCESS_TOKEN_SUBJECT, ...payload },
|
||||
config.tokenSecret,
|
||||
config.accessTokenExpirationTime,
|
||||
);
|
||||
// generate an access token
|
||||
const accessToken = await generateToken(
|
||||
{ subject: authConstants.ACCESS_TOKEN_SUBJECT, ...payload },
|
||||
config.tokenSecret,
|
||||
config.accessTokenExpirationTime
|
||||
)
|
||||
|
||||
// generate a refresh token
|
||||
const refreshToken = await generateToken(
|
||||
{ subject: authConstants.REFRESH_TOKEN_SUBJECT, ...payload },
|
||||
config.tokenSecret,
|
||||
config.refreshTokenExpirationTime,
|
||||
);
|
||||
// generate a refresh token
|
||||
const refreshToken = await generateToken(
|
||||
{ subject: authConstants.REFRESH_TOKEN_SUBJECT, ...payload },
|
||||
config.tokenSecret,
|
||||
config.refreshTokenExpirationTime
|
||||
)
|
||||
|
||||
// set the token in the cookie
|
||||
let cookieOptions = { httpOnly: true, secure: false, Path: '/' };
|
||||
res.cookie(authConstants.ACCESS_TOKEN_SUBJECT, accessToken, cookieOptions);
|
||||
res.cookie(
|
||||
authConstants.REFRESH_TOKEN_SUBJECT,
|
||||
refreshToken,
|
||||
cookieOptions,
|
||||
);
|
||||
// set the token in the cookie
|
||||
let cookieOptions = { httpOnly: true, secure: false, Path: '/' }
|
||||
res.cookie(authConstants.ACCESS_TOKEN_SUBJECT, accessToken, cookieOptions)
|
||||
res.cookie(authConstants.REFRESH_TOKEN_SUBJECT, refreshToken, cookieOptions)
|
||||
|
||||
res
|
||||
.status(201)
|
||||
.send({ message: successMessage.SUCCESS, data: { userId: user.id } });
|
||||
res.status(201).send({ message: successMessage.SUCCESS, data: { userId: user.id } })
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[201] = {
|
||||
description: 'Access token and Refresh token generated',
|
||||
schema: {
|
||||
@@ -115,45 +98,38 @@ const obtainAccessToken = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return res.status(500).json({
|
||||
message: errorMessage.SERVER_ERROR,
|
||||
});
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return res.status(500).json({
|
||||
message: errorMessage.SERVER_ERROR
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const refreshAccessToken = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Refresh Access Token'
|
||||
#swagger.summary = 'Refresh Access Token'
|
||||
#swagger.description = 'Endpoint to refresh access and refresh tokens'
|
||||
*/
|
||||
const refreshTokenFromCookies = req.cookies.refreshToken;
|
||||
const refreshTokenFromCookies = req.cookies.refreshToken
|
||||
|
||||
try {
|
||||
// check if the refresh token is revoked
|
||||
if (isRefreshTokenRevoked({ refreshToken: refreshTokenFromCookies })) {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ message: errorMessage.INVALID_REFRESH_TOKEN_ERROR });
|
||||
}
|
||||
try {
|
||||
// check if the refresh token is revoked
|
||||
if (isRefreshTokenRevoked({ refreshToken: refreshTokenFromCookies })) {
|
||||
return res.status(403).send({ message: errorMessage.INVALID_REFRESH_TOKEN_ERROR })
|
||||
}
|
||||
|
||||
// extract the payload from the token and verify it
|
||||
const payload = await decodeToken(
|
||||
refreshTokenFromCookies,
|
||||
config.tokenSecret,
|
||||
);
|
||||
// extract the payload from the token and verify it
|
||||
const payload = await decodeToken(refreshTokenFromCookies, config.tokenSecret)
|
||||
|
||||
// find the user
|
||||
const users = authService.getUsersById({ userId: payload.userId });
|
||||
// find the user
|
||||
const users = authService.getUsersById({ userId: payload.userId })
|
||||
|
||||
if (users.length <= 0) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.USER_NOT_FOUND_ERROR });
|
||||
if (users.length <= 0) {
|
||||
return res.status(401).send({ message: errorMessage.USER_NOT_FOUND_ERROR })
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'User not found error',
|
||||
schema: {
|
||||
@@ -161,61 +137,53 @@ const refreshAccessToken = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
let roleIds;
|
||||
const user = users[0];
|
||||
let roleIds
|
||||
const user = users[0]
|
||||
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
try {
|
||||
const roleData = getUsersRoleAndPermission({
|
||||
userId: user.id,
|
||||
});
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
try {
|
||||
const roleData = getUsersRoleAndPermission({
|
||||
userId: user.id
|
||||
})
|
||||
|
||||
roleIds = roleData.roleIds;
|
||||
} catch (err) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.ROLE_NOT_FOUND_ERROR });
|
||||
}
|
||||
}
|
||||
roleIds = roleData.roleIds
|
||||
} catch (err) {
|
||||
return res.status(401).send({ message: errorMessage.ROLE_NOT_FOUND_ERROR })
|
||||
}
|
||||
}
|
||||
|
||||
const newPayload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds,
|
||||
};
|
||||
const newPayload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds
|
||||
}
|
||||
|
||||
// generate an access token
|
||||
const accessToken = await generateToken(
|
||||
{ subject: authConstants.ACCESS_TOKEN_SUBJECT, ...newPayload },
|
||||
config.tokenSecret,
|
||||
config.accessTokenExpirationTime,
|
||||
);
|
||||
// generate an access token
|
||||
const accessToken = await generateToken(
|
||||
{ subject: authConstants.ACCESS_TOKEN_SUBJECT, ...newPayload },
|
||||
config.tokenSecret,
|
||||
config.accessTokenExpirationTime
|
||||
)
|
||||
|
||||
// generate a refresh token
|
||||
const refreshToken = await generateToken(
|
||||
{ subject: authConstants.REFRESH_TOKEN_SUBJECT, ...newPayload },
|
||||
config.tokenSecret,
|
||||
config.refreshTokenExpirationTime,
|
||||
);
|
||||
// generate a refresh token
|
||||
const refreshToken = await generateToken(
|
||||
{ subject: authConstants.REFRESH_TOKEN_SUBJECT, ...newPayload },
|
||||
config.tokenSecret,
|
||||
config.refreshTokenExpirationTime
|
||||
)
|
||||
|
||||
// set the token in the cookie
|
||||
let cookieOptions = { httpOnly: true, secure: false, Path: '/' };
|
||||
res.cookie(authConstants.ACCESS_TOKEN_SUBJECT, accessToken, cookieOptions);
|
||||
res.cookie(
|
||||
authConstants.REFRESH_TOKEN_SUBJECT,
|
||||
refreshToken,
|
||||
cookieOptions,
|
||||
);
|
||||
// set the token in the cookie
|
||||
let cookieOptions = { httpOnly: true, secure: false, Path: '/' }
|
||||
res.cookie(authConstants.ACCESS_TOKEN_SUBJECT, accessToken, cookieOptions)
|
||||
res.cookie(authConstants.REFRESH_TOKEN_SUBJECT, refreshToken, cookieOptions)
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.send({ message: successMessage.SUCCESS, data: { userId: user.id } });
|
||||
res.status(200).send({ message: successMessage.SUCCESS, data: { userId: user.id } })
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[200] = {
|
||||
description: 'Access token refreshed',
|
||||
schema: {
|
||||
@@ -223,9 +191,9 @@ const refreshAccessToken = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
res.status(403).send({ message: errorMessage.INVALID_REFRESH_TOKEN_ERROR });
|
||||
/*
|
||||
} catch (error) {
|
||||
res.status(403).send({ message: errorMessage.INVALID_REFRESH_TOKEN_ERROR })
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'Invalid refresh token error',
|
||||
schema: {
|
||||
@@ -233,37 +201,35 @@ const refreshAccessToken = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const removeTokens = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Remove Tokens'
|
||||
#swagger.description = 'Endpoint to remove access and refresh tokens'
|
||||
*/
|
||||
|
||||
const refreshToken = req.cookies.refreshToken;
|
||||
const refreshToken = req.cookies.refreshToken
|
||||
|
||||
try {
|
||||
// decode the token
|
||||
const payload = await decodeToken(refreshToken, config.tokenSecret);
|
||||
try {
|
||||
// decode the token
|
||||
const payload = await decodeToken(refreshToken, config.tokenSecret)
|
||||
|
||||
// store the refresh token in the _revoked_refresh_tokens table
|
||||
authService.saveRevokedRefreshToken({
|
||||
refreshToken,
|
||||
expiresAt: payload.exp,
|
||||
});
|
||||
// store the refresh token in the _revoked_refresh_tokens table
|
||||
authService.saveRevokedRefreshToken({
|
||||
refreshToken,
|
||||
expiresAt: payload.exp
|
||||
})
|
||||
|
||||
// remove the token from the cookie
|
||||
res.clearCookie(authConstants.ACCESS_TOKEN_SUBJECT);
|
||||
res.clearCookie(authConstants.REFRESH_TOKEN_SUBJECT);
|
||||
// remove the token from the cookie
|
||||
res.clearCookie(authConstants.ACCESS_TOKEN_SUBJECT)
|
||||
res.clearCookie(authConstants.REFRESH_TOKEN_SUBJECT)
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.send({ message: responseMessages.successMessage.LOGOUT_MESSAGE });
|
||||
res.status(200).send({ message: responseMessages.successMessage.LOGOUT_MESSAGE })
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[200] = {
|
||||
description: 'Tokens Removed',
|
||||
schema: {
|
||||
@@ -271,40 +237,40 @@ const removeTokens = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR });
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR })
|
||||
}
|
||||
}
|
||||
|
||||
const removeRevokedRefreshTokens = () => {
|
||||
authService.deleteRevokedRefreshTokens({
|
||||
lookupField: `WHERE expires_at < CURRENT_TIMESTAMP`,
|
||||
});
|
||||
};
|
||||
authService.deleteRevokedRefreshTokens({
|
||||
lookupField: `WHERE expires_at < CURRENT_TIMESTAMP`
|
||||
})
|
||||
}
|
||||
|
||||
const getUsersRoleAndPermission = ({ userId }) => {
|
||||
const userRoles = authService.getUserRoleByUserId({ userId });
|
||||
const userRoles = authService.getUserRoleByUserId({ userId })
|
||||
|
||||
if (userRoles.length <= 0) {
|
||||
throw new Error(errorMessage.ROLE_NOT_FOUND_ERROR);
|
||||
}
|
||||
if (userRoles.length <= 0) {
|
||||
throw new Error(errorMessage.ROLE_NOT_FOUND_ERROR)
|
||||
}
|
||||
|
||||
const roleIds = userRoles.map((role) => role.role_id);
|
||||
const roleIds = userRoles.map((role) => role.role_id)
|
||||
|
||||
// get the permission of the role
|
||||
const permissions = authService.getPermissionByRoleIds({ roleIds });
|
||||
// get the permission of the role
|
||||
const permissions = authService.getPermissionByRoleIds({ roleIds })
|
||||
|
||||
return { userRoles, roleIds, permissions };
|
||||
};
|
||||
return { userRoles, roleIds, permissions }
|
||||
}
|
||||
|
||||
const isRefreshTokenRevoked = ({ refreshToken }) => {
|
||||
const tokens = authService.getRevokedRefreshToken({ refreshToken });
|
||||
return tokens.length > 0;
|
||||
};
|
||||
const tokens = authService.getRevokedRefreshToken({ refreshToken })
|
||||
return tokens.length > 0
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
obtainAccessToken,
|
||||
refreshAccessToken,
|
||||
removeTokens,
|
||||
removeRevokedRefreshTokens,
|
||||
};
|
||||
obtainAccessToken,
|
||||
refreshAccessToken,
|
||||
removeTokens,
|
||||
removeRevokedRefreshTokens
|
||||
}
|
||||
|
||||
@@ -1,87 +1,75 @@
|
||||
const { rowService, authService } = require('../../services');
|
||||
const {
|
||||
apiConstants,
|
||||
dbConstants,
|
||||
responseMessages,
|
||||
authConstants,
|
||||
} = require('../../constants');
|
||||
const config = require('../../config');
|
||||
const {
|
||||
hashPassword,
|
||||
checkPasswordStrength,
|
||||
comparePasswords,
|
||||
} = require('../../utils');
|
||||
const { rowService, authService } = require('../../services')
|
||||
const { apiConstants, dbConstants, responseMessages, authConstants } = require('../../constants')
|
||||
const config = require('../../config')
|
||||
const { hashPassword, checkPasswordStrength, comparePasswords } = require('../../utils')
|
||||
|
||||
const { USERS_TABLE, USERS_ROLES_TABLE, tableFields } = dbConstants;
|
||||
const { USERS_TABLE, USERS_ROLES_TABLE, tableFields } = dbConstants
|
||||
|
||||
const { SALT_ROUNDS } = authConstants;
|
||||
const { SALT_ROUNDS } = authConstants
|
||||
|
||||
const { successMessage, errorMessage, infoMessage } = responseMessages;
|
||||
const { successMessage, errorMessage, infoMessage } = responseMessages
|
||||
|
||||
const updateSuperuser = async (fields) => {
|
||||
const { id, password, is_superuser } = fields;
|
||||
let newHashedPassword, newSalt;
|
||||
let fieldsString = '';
|
||||
const { id, password, is_superuser } = fields
|
||||
let newHashedPassword, newSalt
|
||||
let fieldsString = ''
|
||||
|
||||
try {
|
||||
// find the user by using the id field
|
||||
const users = authService.getUsersById({ userId: id });
|
||||
try {
|
||||
// find the user by using the id field
|
||||
const users = authService.getUsersById({ userId: id })
|
||||
|
||||
// abort if the id is invalid
|
||||
if (users.length === 0) {
|
||||
console.log(errorMessage.USER_NOT_FOUND_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
// abort if the id is invalid
|
||||
if (users.length === 0) {
|
||||
console.log(errorMessage.USER_NOT_FOUND_ERROR)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// check if the is_superuser field is passed
|
||||
if (is_superuser !== undefined) {
|
||||
fieldsString = `${tableFields.IS_SUPERUSER} = '${is_superuser}'`;
|
||||
}
|
||||
// check if the is_superuser field is passed
|
||||
if (is_superuser !== undefined) {
|
||||
fieldsString = `${tableFields.IS_SUPERUSER} = '${is_superuser}'`
|
||||
}
|
||||
|
||||
// if the password is sent from the CLI, update it
|
||||
if (password) {
|
||||
// check if the password is weak
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password),
|
||||
)
|
||||
) {
|
||||
console.log(errorMessage.WEAK_PASSWORD_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
// if the password is sent from the CLI, update it
|
||||
if (password) {
|
||||
// check if the password is weak
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password)
|
||||
)
|
||||
) {
|
||||
console.log(errorMessage.WEAK_PASSWORD_ERROR)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
//hash the password
|
||||
const { hashedPassword, salt } = await hashPassword(
|
||||
password,
|
||||
SALT_ROUNDS,
|
||||
);
|
||||
newHashedPassword = hashedPassword;
|
||||
newSalt = salt;
|
||||
//hash the password
|
||||
const { hashedPassword, salt } = await hashPassword(password, SALT_ROUNDS)
|
||||
newHashedPassword = hashedPassword
|
||||
newSalt = salt
|
||||
|
||||
fieldsString = `${fieldsString ? fieldsString + ', ' : ''} ${
|
||||
tableFields.HASHED_PASSWORD
|
||||
} = '${newHashedPassword}', ${tableFields.SALT} = '${newSalt}'`;
|
||||
}
|
||||
fieldsString = `${fieldsString ? fieldsString + ', ' : ''} ${
|
||||
tableFields.HASHED_PASSWORD
|
||||
} = '${newHashedPassword}', ${tableFields.SALT} = '${newSalt}'`
|
||||
}
|
||||
|
||||
// update the user
|
||||
rowService.update({
|
||||
tableName: USERS_TABLE,
|
||||
lookupField: tableFields.ID,
|
||||
fieldsString,
|
||||
pks: `${id}`,
|
||||
});
|
||||
// update the user
|
||||
rowService.update({
|
||||
tableName: USERS_TABLE,
|
||||
lookupField: tableFields.ID,
|
||||
fieldsString,
|
||||
pks: `${id}`
|
||||
})
|
||||
|
||||
console.log(successMessage.USER_UPDATE_SUCCESS);
|
||||
process.exit(1);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
console.log(successMessage.USER_UPDATE_SUCCESS)
|
||||
process.exit(1)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const registerUser = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Register User'
|
||||
#swagger.summary = 'Register User'
|
||||
#swagger.description = 'Endpoint to signup'
|
||||
|
||||
#swagger.parameters['username'] = {
|
||||
@@ -92,30 +80,24 @@ const registerUser = async (req, res) => {
|
||||
}
|
||||
*/
|
||||
|
||||
const { username, password, ...optionalFields } = req.body.fields;
|
||||
const { username, password, ...optionalFields } = req.body.fields
|
||||
|
||||
try {
|
||||
if (!username) {
|
||||
return res
|
||||
.status(400)
|
||||
.send({ message: errorMessage.USERNAME_REQUIRED_ERROR });
|
||||
}
|
||||
try {
|
||||
if (!username) {
|
||||
return res.status(400).send({ message: errorMessage.USERNAME_REQUIRED_ERROR })
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
return res
|
||||
.status(400)
|
||||
.send({ message: errorMessage.PASSWORD_REQUIRED_ERROR });
|
||||
}
|
||||
if (!password) {
|
||||
return res.status(400).send({ message: errorMessage.PASSWORD_REQUIRED_ERROR })
|
||||
}
|
||||
|
||||
// check if the username is taken
|
||||
const users = authService.getUsersByUsername({ username });
|
||||
// check if the username is taken
|
||||
const users = authService.getUsersByUsername({ username })
|
||||
|
||||
if (users.length > 0) {
|
||||
return res
|
||||
.status(409)
|
||||
.send({ message: errorMessage.USERNAME_TAKEN_ERROR });
|
||||
if (users.length > 0) {
|
||||
return res.status(409).send({ message: errorMessage.USERNAME_TAKEN_ERROR })
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[409] = {
|
||||
description: 'Username taken error',
|
||||
schema: {
|
||||
@@ -123,19 +105,19 @@ const registerUser = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// check if the password is weak
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password),
|
||||
)
|
||||
) {
|
||||
return res.status(400).send({
|
||||
message: errorMessage.WEAK_PASSWORD_ERROR,
|
||||
});
|
||||
// check if the password is weak
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password)
|
||||
)
|
||||
) {
|
||||
return res.status(400).send({
|
||||
message: errorMessage.WEAK_PASSWORD_ERROR
|
||||
})
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[400] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
@@ -143,31 +125,31 @@ const registerUser = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(password, SALT_ROUNDS);
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(password, SALT_ROUNDS)
|
||||
|
||||
// create the user
|
||||
const newUser = rowService.save({
|
||||
tableName: USERS_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
salt,
|
||||
hashed_password: hashedPassword,
|
||||
is_superuser: 'false',
|
||||
...optionalFields,
|
||||
},
|
||||
});
|
||||
// create the user
|
||||
const newUser = rowService.save({
|
||||
tableName: USERS_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
salt,
|
||||
hashed_password: hashedPassword,
|
||||
is_superuser: 'false',
|
||||
...optionalFields
|
||||
}
|
||||
})
|
||||
|
||||
// find the default role from the DB
|
||||
const defaultRole = authService.getDefaultRole();
|
||||
// find the default role from the DB
|
||||
const defaultRole = authService.getDefaultRole()
|
||||
|
||||
if (defaultRole.length <= 0) {
|
||||
return res.status(500).send({
|
||||
message: errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR,
|
||||
});
|
||||
/*
|
||||
if (defaultRole.length <= 0) {
|
||||
return res.status(500).send({
|
||||
message: errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR
|
||||
})
|
||||
/*
|
||||
#swagger.responses[500] = {
|
||||
description: 'Server error',
|
||||
schema: {
|
||||
@@ -175,17 +157,17 @@ const registerUser = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// create a role for the user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: newUser.lastInsertRowid, role_id: defaultRole[0].id },
|
||||
});
|
||||
// create a role for the user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: newUser.lastInsertRowid, role_id: defaultRole[0].id }
|
||||
})
|
||||
|
||||
res.status(201).send({ message: successMessage.ROW_INSERTED });
|
||||
res.status(201).send({ message: successMessage.ROW_INSERTED })
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[201] = {
|
||||
description: 'Row inserted',
|
||||
schema: {
|
||||
@@ -193,16 +175,16 @@ const registerUser = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR });
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR })
|
||||
}
|
||||
}
|
||||
|
||||
const changePassword = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Change Password'
|
||||
#swagger.summary = 'Change Password'
|
||||
#swagger.description = 'Endpoint to change a password'
|
||||
|
||||
#swagger.parameters['body'] = {
|
||||
@@ -215,32 +197,25 @@ const changePassword = async (req, res) => {
|
||||
}
|
||||
*/
|
||||
|
||||
const userInfo = req.user;
|
||||
const { currentPassword, newPassword } = req.body.fields;
|
||||
const userInfo = req.user
|
||||
const { currentPassword, newPassword } = req.body.fields
|
||||
|
||||
try {
|
||||
// get the user from the Db
|
||||
const users = authService.getUsersById({ userId: userInfo.userId });
|
||||
try {
|
||||
// get the user from the Db
|
||||
const users = authService.getUsersById({ userId: userInfo.userId })
|
||||
|
||||
if (users.length <= 0) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.USER_NOT_FOUND_ERROR });
|
||||
}
|
||||
if (users.length <= 0) {
|
||||
return res.status(401).send({ message: errorMessage.USER_NOT_FOUND_ERROR })
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
const user = users[0]
|
||||
|
||||
// check if the users current password is valid
|
||||
const isMatch = await comparePasswords(
|
||||
currentPassword,
|
||||
user.hashed_password,
|
||||
);
|
||||
// check if the users current password is valid
|
||||
const isMatch = await comparePasswords(currentPassword, user.hashed_password)
|
||||
|
||||
if (!isMatch) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.INVALID_CURRENT_PASSWORD_ERROR });
|
||||
/*
|
||||
if (!isMatch) {
|
||||
return res.status(401).send({ message: errorMessage.INVALID_CURRENT_PASSWORD_ERROR })
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'User not found error',
|
||||
schema: {
|
||||
@@ -248,19 +223,19 @@ const changePassword = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// check if the new password is strong
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(newPassword),
|
||||
)
|
||||
) {
|
||||
return res.status(400).send({
|
||||
message: errorMessage.WEAK_PASSWORD_ERROR,
|
||||
});
|
||||
// check if the new password is strong
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(newPassword)
|
||||
)
|
||||
) {
|
||||
return res.status(400).send({
|
||||
message: errorMessage.WEAK_PASSWORD_ERROR
|
||||
})
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[400] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
@@ -268,31 +243,28 @@ const changePassword = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(
|
||||
newPassword,
|
||||
SALT_ROUNDS,
|
||||
);
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(newPassword, SALT_ROUNDS)
|
||||
|
||||
user.salt = salt;
|
||||
user.hashed_password = hashedPassword;
|
||||
user.salt = salt
|
||||
user.hashed_password = hashedPassword
|
||||
|
||||
// update the user
|
||||
rowService.update({
|
||||
tableName: USERS_TABLE,
|
||||
lookupField: tableFields.ID,
|
||||
fieldsString: `${tableFields.HASHED_PASSWORD} = '${hashedPassword}', ${tableFields.SALT} = '${salt}'`,
|
||||
pks: `${user.id}`,
|
||||
});
|
||||
// update the user
|
||||
rowService.update({
|
||||
tableName: USERS_TABLE,
|
||||
lookupField: tableFields.ID,
|
||||
fieldsString: `${tableFields.HASHED_PASSWORD} = '${hashedPassword}', ${tableFields.SALT} = '${salt}'`,
|
||||
pks: `${user.id}`
|
||||
})
|
||||
|
||||
res.status(200).send({
|
||||
message: successMessage.PASSWORD_UPDATE_SUCCESS,
|
||||
data: { id: user.id, username: user.username },
|
||||
});
|
||||
res.status(200).send({
|
||||
message: successMessage.PASSWORD_UPDATE_SUCCESS,
|
||||
data: { id: user.id, username: user.username }
|
||||
})
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[200] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
@@ -300,96 +272,92 @@ const changePassword = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR });
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR })
|
||||
}
|
||||
}
|
||||
|
||||
const createInitialUser = async () => {
|
||||
// extract some fields from the environment variables or from the CLI
|
||||
const { initialUserUsername: username, initialUserPassword: password } =
|
||||
config;
|
||||
// extract some fields from the environment variables or from the CLI
|
||||
const { initialUserUsername: username, initialUserPassword: password } = config
|
||||
|
||||
try {
|
||||
// check if there are users in the DB
|
||||
const users = authService.getAllUsers();
|
||||
try {
|
||||
// check if there are users in the DB
|
||||
const users = authService.getAllUsers()
|
||||
|
||||
if (users.length <= 0) {
|
||||
// check if initial users username is passed from the env or CLI
|
||||
if (!username) {
|
||||
console.error(errorMessage.INITIAL_USER_USERNAME_NOT_PASSED_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
if (users.length <= 0) {
|
||||
// check if initial users username is passed from the env or CLI
|
||||
if (!username) {
|
||||
console.error(errorMessage.INITIAL_USER_USERNAME_NOT_PASSED_ERROR)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// check if initial users password is passed from the env or CLI
|
||||
if (!password) {
|
||||
console.error(errorMessage.INITIAL_USER_PASSWORD_NOT_PASSED_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
// check if initial users password is passed from the env or CLI
|
||||
if (!password) {
|
||||
console.error(errorMessage.INITIAL_USER_PASSWORD_NOT_PASSED_ERROR)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// check if the usernmae is taken
|
||||
const users = authService.getUsersByUsername({ username });
|
||||
// check if the usernmae is taken
|
||||
const users = authService.getUsersByUsername({ username })
|
||||
|
||||
if (users.length > 0) {
|
||||
console.error(errorMessage.USERNAME_TAKEN_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
if (users.length > 0) {
|
||||
console.error(errorMessage.USERNAME_TAKEN_ERROR)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// check if the password is strong
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password),
|
||||
)
|
||||
) {
|
||||
console.error(errorMessage.WEAK_PASSWORD_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
// check if the password is strong
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password)
|
||||
)
|
||||
) {
|
||||
console.error(errorMessage.WEAK_PASSWORD_ERROR)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { hashedPassword, salt } = await hashPassword(
|
||||
password,
|
||||
SALT_ROUNDS,
|
||||
);
|
||||
// hash the password
|
||||
const { hashedPassword, salt } = await hashPassword(password, SALT_ROUNDS)
|
||||
|
||||
// create the initial user
|
||||
const { lastInsertRowid: userId } = rowService.save({
|
||||
tableName: USERS_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
hashed_password: hashedPassword,
|
||||
salt,
|
||||
is_superuser: 'false',
|
||||
},
|
||||
});
|
||||
// create the initial user
|
||||
const { lastInsertRowid: userId } = rowService.save({
|
||||
tableName: USERS_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
hashed_password: hashedPassword,
|
||||
salt,
|
||||
is_superuser: 'false'
|
||||
}
|
||||
})
|
||||
|
||||
// get the default role from the DB
|
||||
const roles = authService.getDefaultRole();
|
||||
// get the default role from the DB
|
||||
const roles = authService.getDefaultRole()
|
||||
|
||||
if (roles.length <= 0) {
|
||||
console.log(errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
if (roles.length <= 0) {
|
||||
console.log(errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const defaultRoleId = roles[0].id;
|
||||
const defaultRoleId = roles[0].id
|
||||
|
||||
// create a _users_role for the initial user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: userId, role_id: defaultRoleId },
|
||||
});
|
||||
// create a _users_role for the initial user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: userId, role_id: defaultRoleId }
|
||||
})
|
||||
|
||||
console.log(successMessage.INITIAL_USER_CREATED_SUCCESS);
|
||||
} else {
|
||||
console.log(infoMessage.INITIAL_USER_ALREADY_CREATED);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
console.log(successMessage.INITIAL_USER_CREATED_SUCCESS)
|
||||
} else {
|
||||
console.log(infoMessage.INITIAL_USER_ALREADY_CREATED)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateSuperuser,
|
||||
registerUser,
|
||||
changePassword,
|
||||
createInitialUser,
|
||||
};
|
||||
updateSuperuser,
|
||||
registerUser,
|
||||
changePassword,
|
||||
createInitialUser
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
const version = require('../../package.json').version;
|
||||
const version = require('../../package.json').version
|
||||
|
||||
// Root endpoint
|
||||
const root = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Root']
|
||||
#swagger.summary = 'Timestamp'
|
||||
#swagger.description = 'Endpoint to return server timestamp'
|
||||
*/
|
||||
|
||||
res.json({
|
||||
message: 'Soul is running...',
|
||||
data: {
|
||||
version,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
};
|
||||
res.json({
|
||||
message: 'Soul is running...',
|
||||
data: {
|
||||
version,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const health = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Root']
|
||||
#swagger.summary = 'Health Check'
|
||||
#swagger.description = 'Endpoint to return server health status'
|
||||
*/
|
||||
|
||||
res.send('OK');
|
||||
};
|
||||
res.send('OK')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
root,
|
||||
health,
|
||||
};
|
||||
root,
|
||||
health
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
const supertest = require('supertest');
|
||||
const supertest = require('supertest')
|
||||
|
||||
const app = require('../index');
|
||||
const requestWithSupertest = supertest(app);
|
||||
const app = require('../index')
|
||||
const requestWithSupertest = supertest(app)
|
||||
|
||||
describe('Root Endpoints', () => {
|
||||
it('GET / should return server version and timestamp', async () => {
|
||||
const res = await requestWithSupertest.get('/api');
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toHaveProperty('version');
|
||||
expect(res.body.data).toHaveProperty('timestamp');
|
||||
});
|
||||
});
|
||||
it('GET / should return server version and timestamp', async () => {
|
||||
const res = await requestWithSupertest.get('/api')
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('message')
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toHaveProperty('version')
|
||||
expect(res.body.data).toHaveProperty('timestamp')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Health Endpoints', () => {
|
||||
it('GET /health should return server version and timestamp', async () => {
|
||||
const res = await requestWithSupertest.get('/api/health');
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('text'));
|
||||
});
|
||||
});
|
||||
it('GET /health should return server version and timestamp', async () => {
|
||||
const res = await requestWithSupertest.get('/api/health')
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('text'))
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,332 +1,330 @@
|
||||
const { not } = require('joi');
|
||||
const supertest = require('supertest');
|
||||
const { not } = require('joi')
|
||||
const supertest = require('supertest')
|
||||
|
||||
const app = require('../index');
|
||||
const config = require('../config');
|
||||
const { generateToken } = require('../utils');
|
||||
const app = require('../index')
|
||||
const config = require('../config')
|
||||
const { generateToken } = require('../utils')
|
||||
|
||||
const requestWithSupertest = supertest(app);
|
||||
const requestWithSupertest = supertest(app)
|
||||
|
||||
function queryString(params) {
|
||||
const queryString = Object.keys(params)
|
||||
.map((key) => `${key}=${params[key]}`)
|
||||
.join('&');
|
||||
const queryString = Object.keys(params)
|
||||
.map((key) => `${key}=${params[key]}`)
|
||||
.join('&')
|
||||
|
||||
return queryString;
|
||||
return queryString
|
||||
}
|
||||
|
||||
describe('Rows Endpoints', () => {
|
||||
it('GET /tables/:name/rows should return a list of all rows', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows should return a list of all rows', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toEqual(expect.any(Array));
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('firstName');
|
||||
expect(res.body.data[0]).toHaveProperty('lastName');
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toEqual(expect.any(Array))
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('firstName')
|
||||
expect(res.body.data[0]).toHaveProperty('lastName')
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows?_limit=8&_schema=firstName,lastName&_ordering:-firstName&_page=2: should query the rows by the provided query params', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows?_limit=8&_schema=firstName,lastName&_ordering:-firstName&_page=2: should query the rows by the provided query params', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const params = {
|
||||
_search: 'a',
|
||||
_ordering: '-firstName',
|
||||
_schema: 'firstName,lastName',
|
||||
_limit: 8,
|
||||
_page: 2,
|
||||
};
|
||||
const query = queryString(params);
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?${query}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const params = {
|
||||
_search: 'a',
|
||||
_ordering: '-firstName',
|
||||
_schema: 'firstName,lastName',
|
||||
_limit: 8,
|
||||
_page: 2
|
||||
}
|
||||
const query = queryString(params)
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?${query}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toEqual(expect.any(Array));
|
||||
expect(res.body.data[0]).toHaveProperty('firstName');
|
||||
expect(res.body.data[0]).toHaveProperty('lastName');
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toEqual(expect.any(Array))
|
||||
expect(res.body.data[0]).toHaveProperty('firstName')
|
||||
expect(res.body.data[0]).toHaveProperty('lastName')
|
||||
|
||||
expect(res.body.next).toEqual(
|
||||
`/tables/users/rows?${queryString({
|
||||
...params,
|
||||
_page: params._page + 1,
|
||||
}).toString()}`,
|
||||
);
|
||||
expect(res.body.next).toEqual(
|
||||
`/tables/users/rows?${queryString({
|
||||
...params,
|
||||
_page: params._page + 1
|
||||
}).toString()}`
|
||||
)
|
||||
|
||||
expect(res.body.previous).toEqual(
|
||||
`/tables/users/rows?${queryString({
|
||||
...params,
|
||||
_page: params._page - 1,
|
||||
}).toString()}`,
|
||||
);
|
||||
});
|
||||
expect(res.body.previous).toEqual(
|
||||
`/tables/users/rows?${queryString({
|
||||
...params,
|
||||
_page: params._page - 1
|
||||
}).toString()}`
|
||||
)
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows: should return a null field', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows: should return a null field', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows?_filters=firstName__null,lastName__notnull')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows?_filters=firstName__null,lastName__notnull')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body.data[0].firstName).toBeNull();
|
||||
expect(res.body.data[0].lastName).not.toBeNull();
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.data[0].firstName).toBeNull()
|
||||
expect(res.body.data[0].lastName).not.toBeNull()
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created after 2010-01-01 00:00:00.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created after 2010-01-01 00:00:00.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const date = '2010-01-01 00:00:00';
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__gte:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const date = '2010-01-01 00:00:00'
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__gte:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt);
|
||||
const referenceDate = new Date(date);
|
||||
expect(createdAt.getTime()).toBeGreaterThan(referenceDate.getTime());
|
||||
});
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt)
|
||||
const referenceDate = new Date(date)
|
||||
expect(createdAt.getTime()).toBeGreaterThan(referenceDate.getTime())
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('firstName');
|
||||
expect(res.body.data[0]).toHaveProperty('lastName');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('firstName')
|
||||
expect(res.body.data[0]).toHaveProperty('lastName')
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt')
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created before 2008-01-20 00:00:00.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created before 2008-01-20 00:00:00.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const date = '2008-01-20 00:00:00';
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__lte:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const date = '2008-01-20 00:00:00'
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__lte:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt);
|
||||
const referenceDate = new Date(date);
|
||||
expect(createdAt.getTime()).toBeLessThan(referenceDate.getTime());
|
||||
});
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt)
|
||||
const referenceDate = new Date(date)
|
||||
expect(createdAt.getTime()).toBeLessThan(referenceDate.getTime())
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('firstName');
|
||||
expect(res.body.data[0]).toHaveProperty('lastName');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('firstName')
|
||||
expect(res.body.data[0]).toHaveProperty('lastName')
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt')
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created at 2013-01-08 00:00:00', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created at 2013-01-08 00:00:00', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const date = '2013-01-08 00:00:00';
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__eq:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const date = '2013-01-08 00:00:00'
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__eq:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt);
|
||||
const referenceDate = new Date(date);
|
||||
expect(createdAt.getTime()).toEqual(referenceDate.getTime());
|
||||
});
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt)
|
||||
const referenceDate = new Date(date)
|
||||
expect(createdAt.getTime()).toEqual(referenceDate.getTime())
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('firstName');
|
||||
expect(res.body.data[0]).toHaveProperty('lastName');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('firstName')
|
||||
expect(res.body.data[0]).toHaveProperty('lastName')
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt')
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created at 2007-01-08 00:00:00', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows: should successfully retrieve users created at 2007-01-08 00:00:00', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const date = '2007-01-08 00:00:00';
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__eq:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const date = '2007-01-08 00:00:00'
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__eq:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
//There are no users that are created at 2007-01-08 00:00:00 so the API should return empty data
|
||||
expect(res.body.data).toHaveLength(0);
|
||||
expect(res.status).toEqual(200);
|
||||
});
|
||||
//There are no users that are created at 2007-01-08 00:00:00 so the API should return empty data
|
||||
expect(res.body.data).toHaveLength(0)
|
||||
expect(res.status).toEqual(200)
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows: should successfully retrieve users that are not created at 2021-01-08 00:00:00', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows: should successfully retrieve users that are not created at 2021-01-08 00:00:00', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const date = '2021-01-08 00:00:00';
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__neq:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const date = '2021-01-08 00:00:00'
|
||||
const res = await requestWithSupertest
|
||||
.get(`/api/tables/users/rows?_filters=createdAt__neq:${date}`)
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt);
|
||||
const referenceDate = new Date(date);
|
||||
expect(createdAt.getTime()).not.toEqual(referenceDate.getTime());
|
||||
});
|
||||
res.body.data.map((user) => {
|
||||
const createdAt = new Date(user.createdAt)
|
||||
const referenceDate = new Date(date)
|
||||
expect(createdAt.getTime()).not.toEqual(referenceDate.getTime())
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('firstName');
|
||||
expect(res.body.data[0]).toHaveProperty('lastName');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('firstName')
|
||||
expect(res.body.data[0]).toHaveProperty('lastName')
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt')
|
||||
})
|
||||
|
||||
it('POST /tables/:name/rows should insert a new row and return the lastInsertRowid', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('POST /tables/:name/rows should insert a new row and return the lastInsertRowid', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({ fields: { firstName: 'Jane', lastName: 'Doe' } });
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/users/rows')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({ fields: { firstName: 'Jane', lastName: 'Doe' } })
|
||||
|
||||
expect(res.status).toEqual(201);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
});
|
||||
expect(res.status).toEqual(201)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows/:pks should return a row by its primary key', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows/:pks should return a row by its primary key', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data[0]).toHaveProperty('id');
|
||||
expect(res.body.data[0]).toHaveProperty('firstName');
|
||||
expect(res.body.data[0]).toHaveProperty('lastName');
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data[0]).toHaveProperty('id')
|
||||
expect(res.body.data[0]).toHaveProperty('firstName')
|
||||
expect(res.body.data[0]).toHaveProperty('lastName')
|
||||
})
|
||||
|
||||
it('PUT /tables/:name/rows/:pks should update a row by its primary key and return the number of changes', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/tables/users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({ fields: { firstName: 'Jane', lastName: 'Doe' } });
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
});
|
||||
it('PUT /tables/:name/rows/:pks should update a row by its primary key and return the number of changes', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/tables/users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({ fields: { firstName: 'Jane', lastName: 'Doe' } })
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
})
|
||||
|
||||
it('DELETE /tables/:name/rows/:pks should delete a row by its primary key and return the number of changes', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('DELETE /tables/:name/rows/:pks should delete a row by its primary key and return the number of changes', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.delete('/api/tables/users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.delete('/api/tables/users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
})
|
||||
|
||||
it('POST /tables/:name/rows should insert a new row if any of the value of the object being inserted is null', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/users/rows')
|
||||
.send({
|
||||
fields: {
|
||||
firstName: null,
|
||||
lastName: 'Doe',
|
||||
email: null,
|
||||
username: 'Jane',
|
||||
},
|
||||
})
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
expect(res.status).toEqual(201);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
});
|
||||
it('POST /tables/:name/rows should insert a new row if any of the value of the object being inserted is null', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables/users/rows')
|
||||
.send({
|
||||
fields: {
|
||||
firstName: null,
|
||||
lastName: 'Doe',
|
||||
email: null,
|
||||
username: 'Jane'
|
||||
}
|
||||
})
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
expect(res.status).toEqual(201)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows should return values if any of the IDs from the array match the user ID.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows should return values if any of the IDs from the array match the user ID.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows?_filters=id:[2,3]')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toEqual(expect.any(Array));
|
||||
expect(res.body.data.length).toEqual(2);
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows?_filters=id:[2,3]')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toEqual(expect.any(Array))
|
||||
expect(res.body.data.length).toEqual(2)
|
||||
})
|
||||
|
||||
it('GET /tables/:name/rows should return values if the provided ID matches the user ID.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name/rows should return values if the provided ID matches the user ID.', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get(
|
||||
'/api/tables/users/rows?_filters=id:2,firstName:Michael,lastName:Lee',
|
||||
)
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toEqual(expect.any(Array));
|
||||
expect(res.body.data.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users/rows?_filters=id:2,firstName:Michael,lastName:Lee')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toEqual(expect.any(Array))
|
||||
expect(res.body.data.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const db = require('../db/index');
|
||||
const db = require('../db/index')
|
||||
|
||||
const createTable = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Tables']
|
||||
#swagger.summary = 'Create Table'
|
||||
#swagger.description = 'Endpoint to create a table'
|
||||
@@ -12,104 +12,88 @@ const createTable = async (req, res) => {
|
||||
schema: { $ref: "#/definitions/CreateTableRequestBody" }
|
||||
}
|
||||
*/
|
||||
const {
|
||||
name: tableName,
|
||||
schema,
|
||||
autoAddCreatedAt = true,
|
||||
autoAddUpdatedAt = true,
|
||||
} = req.body;
|
||||
const { name: tableName, schema, autoAddCreatedAt = true, autoAddUpdatedAt = true } = req.body
|
||||
|
||||
let indices = [];
|
||||
let schemaString = schema
|
||||
// support name, type, default, not null, unique, primary key, foreign key, index
|
||||
// e.g. { name: 'id', type: 'INTEGER', primaryKey: true }
|
||||
let indices = []
|
||||
let schemaString = schema
|
||||
// support name, type, default, not null, unique, primary key, foreign key, index
|
||||
// e.g. { name: 'id', type: 'INTEGER', primaryKey: true }
|
||||
|
||||
.map(
|
||||
({
|
||||
name,
|
||||
type,
|
||||
default: defaultValue,
|
||||
notNull,
|
||||
unique,
|
||||
primaryKey,
|
||||
foreignKey,
|
||||
index,
|
||||
}) => {
|
||||
let column = `${name} ${type}`;
|
||||
if (defaultValue) {
|
||||
column += ` DEFAULT ${defaultValue}`;
|
||||
}
|
||||
if (notNull) {
|
||||
column += ' NOT NULL';
|
||||
}
|
||||
if (unique) {
|
||||
column += ' UNIQUE';
|
||||
}
|
||||
if (primaryKey) {
|
||||
column += ' PRIMARY KEY';
|
||||
}
|
||||
if (foreignKey) {
|
||||
column += ` REFERENCES ${foreignKey.table}(${foreignKey.column})`;
|
||||
}
|
||||
if (foreignKey && foreignKey.onDelete) {
|
||||
column += ` ON DELETE ${foreignKey.onDelete}`;
|
||||
}
|
||||
if (foreignKey && foreignKey.onUpdate) {
|
||||
column += ` ON UPDATE ${foreignKey.onUpdate}`;
|
||||
}
|
||||
if (index) {
|
||||
indices.push(name);
|
||||
}
|
||||
.map(({ name, type, default: defaultValue, notNull, unique, primaryKey, foreignKey, index }) => {
|
||||
let column = `${name} ${type}`
|
||||
if (defaultValue) {
|
||||
column += ` DEFAULT ${defaultValue}`
|
||||
}
|
||||
if (notNull) {
|
||||
column += ' NOT NULL'
|
||||
}
|
||||
if (unique) {
|
||||
column += ' UNIQUE'
|
||||
}
|
||||
if (primaryKey) {
|
||||
column += ' PRIMARY KEY'
|
||||
}
|
||||
if (foreignKey) {
|
||||
column += ` REFERENCES ${foreignKey.table}(${foreignKey.column})`
|
||||
}
|
||||
if (foreignKey && foreignKey.onDelete) {
|
||||
column += ` ON DELETE ${foreignKey.onDelete}`
|
||||
}
|
||||
if (foreignKey && foreignKey.onUpdate) {
|
||||
column += ` ON UPDATE ${foreignKey.onUpdate}`
|
||||
}
|
||||
if (index) {
|
||||
indices.push(name)
|
||||
}
|
||||
|
||||
return column;
|
||||
}
|
||||
)
|
||||
.join(', ');
|
||||
return column
|
||||
})
|
||||
.join(', ')
|
||||
|
||||
// add id if primary key is not defined
|
||||
if (!schema.find((field) => field.primaryKey)) {
|
||||
schemaString = `
|
||||
// add id if primary key is not defined
|
||||
if (!schema.find((field) => field.primaryKey)) {
|
||||
schemaString = `
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
${schemaString}
|
||||
`;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
// add created at and updated at
|
||||
if (autoAddCreatedAt) {
|
||||
schemaString = `
|
||||
// add created at and updated at
|
||||
if (autoAddCreatedAt) {
|
||||
schemaString = `
|
||||
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
${schemaString}
|
||||
`;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
if (autoAddUpdatedAt) {
|
||||
schemaString = `
|
||||
if (autoAddUpdatedAt) {
|
||||
schemaString = `
|
||||
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
${schemaString}
|
||||
`;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
let indicesString = indices
|
||||
.map((field) => {
|
||||
return `
|
||||
let indicesString = indices
|
||||
.map((field) => {
|
||||
return `
|
||||
CREATE INDEX ${tableName}_${field}_index
|
||||
ON ${tableName} (${field})
|
||||
`;
|
||||
})
|
||||
.join(';');
|
||||
`
|
||||
})
|
||||
.join(';')
|
||||
|
||||
const query = `CREATE TABLE ${tableName} (${schemaString})`;
|
||||
const query = `CREATE TABLE ${tableName} (${schemaString})`
|
||||
|
||||
try {
|
||||
db.prepare(query).run();
|
||||
try {
|
||||
db.prepare(query).run()
|
||||
|
||||
if (indicesString) {
|
||||
db.prepare(indicesString).run();
|
||||
}
|
||||
if (indicesString) {
|
||||
db.prepare(indicesString).run()
|
||||
}
|
||||
|
||||
const generatedSchema = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
||||
const generatedSchema = db.prepare(`PRAGMA table_info(${tableName})`).all()
|
||||
|
||||
/*
|
||||
/*
|
||||
#swagger.responses[201] = {
|
||||
description: 'Table created',
|
||||
schema: {
|
||||
@@ -117,15 +101,15 @@ const createTable = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
res.status(201).json({
|
||||
message: 'Table created',
|
||||
data: {
|
||||
name: tableName,
|
||||
schema: generatedSchema,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
/*
|
||||
res.status(201).json({
|
||||
message: 'Table created',
|
||||
data: {
|
||||
name: tableName,
|
||||
schema: generatedSchema
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
/*
|
||||
#swagger.responses[400] = {
|
||||
description: 'Bad request',
|
||||
schema: {
|
||||
@@ -133,16 +117,16 @@ const createTable = async (req, res) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Return all tables
|
||||
const listTables = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Tables']
|
||||
#swagger.summary = 'List Tables'
|
||||
#swagger.description = 'Endpoint to list all tables'
|
||||
@@ -160,44 +144,42 @@ const listTables = async (req, res) => {
|
||||
description: 'Ordering term'
|
||||
}
|
||||
*/
|
||||
const { _search, _ordering } = req.query;
|
||||
const { _search, _ordering } = req.query
|
||||
|
||||
let query = `SELECT name FROM sqlite_master WHERE type IN ('table', 'view')`;
|
||||
let query = `SELECT name FROM sqlite_master WHERE type IN ('table', 'view')`
|
||||
|
||||
// if search is provided, search the tables
|
||||
// e.g. ?_search=users
|
||||
if (_search) {
|
||||
query += ` AND name LIKE $searchQuery`;
|
||||
}
|
||||
// if search is provided, search the tables
|
||||
// e.g. ?_search=users
|
||||
if (_search) {
|
||||
query += ` AND name LIKE $searchQuery`
|
||||
}
|
||||
|
||||
// if ordering is provided, order the tables
|
||||
// e.g. ?_ordering=name (ascending) or ?_ordering=-name (descending)
|
||||
if (_ordering) {
|
||||
query += ` ORDER BY $ordering`;
|
||||
}
|
||||
// if ordering is provided, order the tables
|
||||
// e.g. ?_ordering=name (ascending) or ?_ordering=-name (descending)
|
||||
if (_ordering) {
|
||||
query += ` ORDER BY $ordering`
|
||||
}
|
||||
|
||||
try {
|
||||
const tables = db.prepare(query).all({
|
||||
searchQuery: `%${_search}%`,
|
||||
ordering: `${_ordering?.replace('-', '')} ${
|
||||
_ordering?.startsWith('-') ? 'DESC' : 'ASC'
|
||||
}`,
|
||||
});
|
||||
try {
|
||||
const tables = db.prepare(query).all({
|
||||
searchQuery: `%${_search}%`,
|
||||
ordering: `${_ordering?.replace('-', '')} ${_ordering?.startsWith('-') ? 'DESC' : 'ASC'}`
|
||||
})
|
||||
|
||||
res.json({
|
||||
data: tables,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
res.json({
|
||||
data: tables
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Return the schema of a table by name
|
||||
const getTableSchema = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Tables']
|
||||
#swagger.summary = 'Get Table Schema'
|
||||
#swagger.description = 'Endpoint to get the schema of a table'
|
||||
@@ -209,25 +191,25 @@ const getTableSchema = async (req, res) => {
|
||||
}
|
||||
|
||||
*/
|
||||
const { name: tableName } = req.params;
|
||||
const query = `PRAGMA table_info(${tableName})`;
|
||||
try {
|
||||
const schema = db.prepare(query).all();
|
||||
const { name: tableName } = req.params
|
||||
const query = `PRAGMA table_info(${tableName})`
|
||||
try {
|
||||
const schema = db.prepare(query).all()
|
||||
|
||||
res.json({
|
||||
data: schema,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
res.json({
|
||||
data: schema
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a table by name
|
||||
const deleteTable = async (req, res) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.tags = ['Tables']
|
||||
#swagger.summary = 'Delete Table'
|
||||
#swagger.description = 'Endpoint to delete a table'
|
||||
@@ -239,26 +221,26 @@ const deleteTable = async (req, res) => {
|
||||
}
|
||||
|
||||
*/
|
||||
const { name: tableName } = req.params;
|
||||
const query = `DROP TABLE ${tableName}`;
|
||||
try {
|
||||
const data = db.prepare(query).run();
|
||||
const { name: tableName } = req.params
|
||||
const query = `DROP TABLE ${tableName}`
|
||||
try {
|
||||
const data = db.prepare(query).run()
|
||||
|
||||
res.json({
|
||||
message: 'Table deleted',
|
||||
data,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
res.json({
|
||||
message: 'Table deleted',
|
||||
data
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listTables,
|
||||
createTable,
|
||||
getTableSchema,
|
||||
deleteTable,
|
||||
};
|
||||
listTables,
|
||||
createTable,
|
||||
getTableSchema,
|
||||
deleteTable
|
||||
}
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
const supertest = require('supertest');
|
||||
const supertest = require('supertest')
|
||||
|
||||
const app = require('../index');
|
||||
const { generateToken } = require('../utils');
|
||||
const config = require('../config');
|
||||
const app = require('../index')
|
||||
const { generateToken } = require('../utils')
|
||||
const config = require('../config')
|
||||
|
||||
const requestWithSupertest = supertest(app);
|
||||
const requestWithSupertest = supertest(app)
|
||||
|
||||
describe('Tables Endpoints', () => {
|
||||
it('GET /tables should return a list of all tables', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables should return a list of all tables', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toEqual(expect.any(Array));
|
||||
expect(res.body.data[0]).toHaveProperty('name');
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toEqual(expect.any(Array))
|
||||
expect(res.body.data[0]).toHaveProperty('name')
|
||||
})
|
||||
|
||||
it('POST /tables should create a new table and return generated schema', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('POST /tables should create a new table and return generated schema', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables')
|
||||
.send({
|
||||
name: 'pets',
|
||||
autoAddCreatedAt: true,
|
||||
autoAddUpdatedAt: false,
|
||||
schema: [
|
||||
{
|
||||
name: 'owner',
|
||||
type: 'INTEGER',
|
||||
foreignKey: {
|
||||
table: 'users',
|
||||
column: 'id',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'TEXT',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'petId',
|
||||
unique: true,
|
||||
type: 'INTEGER',
|
||||
},
|
||||
],
|
||||
})
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.post('/api/tables')
|
||||
.send({
|
||||
name: 'pets',
|
||||
autoAddCreatedAt: true,
|
||||
autoAddUpdatedAt: false,
|
||||
schema: [
|
||||
{
|
||||
name: 'owner',
|
||||
type: 'INTEGER',
|
||||
foreignKey: {
|
||||
table: 'users',
|
||||
column: 'id',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'petId',
|
||||
unique: true,
|
||||
type: 'INTEGER'
|
||||
}
|
||||
]
|
||||
})
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(201);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toHaveProperty('name');
|
||||
expect(res.body.data).toHaveProperty('schema');
|
||||
expect(res.body.data.schema).toEqual(expect.any(Array));
|
||||
expect(res.body.data.schema[0]).toHaveProperty('name');
|
||||
expect(res.body.data.schema[0]).toHaveProperty('cid');
|
||||
});
|
||||
expect(res.status).toEqual(201)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toHaveProperty('name')
|
||||
expect(res.body.data).toHaveProperty('schema')
|
||||
expect(res.body.data.schema).toEqual(expect.any(Array))
|
||||
expect(res.body.data.schema[0]).toHaveProperty('name')
|
||||
expect(res.body.data.schema[0]).toHaveProperty('cid')
|
||||
})
|
||||
|
||||
it('GET /tables/:name should return schema of the table', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
it('GET /tables/:name should return schema of the table', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H'
|
||||
)
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users')
|
||||
.set('Cookie', [`accessToken=${accessToken}`]);
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/tables/users')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body.data).toEqual(expect.any(Array));
|
||||
});
|
||||
});
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.type).toEqual(expect.stringContaining('json'))
|
||||
expect(res.body).toHaveProperty('data')
|
||||
expect(res.body.data).toEqual(expect.any(Array))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
const BS3Database = require('better-sqlite3');
|
||||
const BS3Database = require('better-sqlite3')
|
||||
|
||||
const config = require('../config/index');
|
||||
const config = require('../config/index')
|
||||
|
||||
class Database {
|
||||
constructor(filename, options) {
|
||||
this.db = new BS3Database(filename, {
|
||||
verbose: this.getVerbose(),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
constructor(filename, options) {
|
||||
this.db = new BS3Database(filename, {
|
||||
verbose: this.getVerbose(),
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
getVerbose() {
|
||||
if (config.verbose === 'console') {
|
||||
return console.log;
|
||||
} else if (config.verbose === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
getVerbose() {
|
||||
if (config.verbose === 'console') {
|
||||
return console.log
|
||||
} else if (config.verbose === null) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.db;
|
||||
}
|
||||
get() {
|
||||
return this.db
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Database(config.db.filename).get();
|
||||
module.exports = new Database(config.db.filename).get()
|
||||
|
||||
242
src/db/schema.js
242
src/db/schema.js
@@ -1,136 +1,136 @@
|
||||
const { dbConstants } = require('../constants');
|
||||
const { dbConstants } = require('../constants')
|
||||
|
||||
const { tableFields, ROLES_TABLE, USERS_TABLE } = dbConstants;
|
||||
const { tableFields, ROLES_TABLE, USERS_TABLE } = dbConstants
|
||||
|
||||
module.exports = {
|
||||
roleSchema: [
|
||||
{
|
||||
name: tableFields.ROLE_NAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: true,
|
||||
},
|
||||
],
|
||||
roleSchema: [
|
||||
{
|
||||
name: tableFields.ROLE_NAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: true
|
||||
}
|
||||
],
|
||||
|
||||
userSchema: [
|
||||
{
|
||||
name: tableFields.USERNAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
name: tableFields.HASHED_PASSWORD,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
{
|
||||
name: tableFields.SALT,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
userSchema: [
|
||||
{
|
||||
name: tableFields.USERNAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
name: tableFields.HASHED_PASSWORD,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
},
|
||||
{
|
||||
name: tableFields.SALT,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.IS_SUPERUSER,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
],
|
||||
{
|
||||
name: tableFields.IS_SUPERUSER,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
}
|
||||
],
|
||||
|
||||
rolePermissionSchema: [
|
||||
{
|
||||
name: tableFields.ROLE_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: ROLES_TABLE, column: tableFields.ID },
|
||||
},
|
||||
rolePermissionSchema: [
|
||||
{
|
||||
name: tableFields.ROLE_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: ROLES_TABLE, column: tableFields.ID }
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.TABLE_NAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
{
|
||||
name: tableFields.TABLE_NAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.CREATE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
{
|
||||
name: tableFields.CREATE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.READ,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
{
|
||||
name: tableFields.READ,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.UPDATE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
{
|
||||
name: tableFields.UPDATE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.DELETE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
],
|
||||
{
|
||||
name: tableFields.DELETE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
}
|
||||
],
|
||||
|
||||
usersRoleSchema: [
|
||||
{
|
||||
name: tableFields.USER_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: USERS_TABLE, column: tableFields.ID },
|
||||
},
|
||||
usersRoleSchema: [
|
||||
{
|
||||
name: tableFields.USER_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: USERS_TABLE, column: tableFields.ID }
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.ROLE_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: ROLES_TABLE, column: tableFields.ID },
|
||||
},
|
||||
],
|
||||
{
|
||||
name: tableFields.ROLE_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: ROLES_TABLE, column: tableFields.ID }
|
||||
}
|
||||
],
|
||||
|
||||
revokedRefreshTokensSchema: [
|
||||
{
|
||||
name: tableFields.REFRESH_TOKEN,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
revokedRefreshTokensSchema: [
|
||||
{
|
||||
name: tableFields.REFRESH_TOKEN,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
},
|
||||
|
||||
{
|
||||
name: tableFields.EXPIRES_AT,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
{
|
||||
name: tableFields.EXPIRES_AT,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
const fs = require('fs');
|
||||
const fs = require('fs')
|
||||
|
||||
const { extensions: extensionsConfig } = require('./config');
|
||||
const { extensions: extensionsConfig } = require('./config')
|
||||
|
||||
const { path: extensionsPath } = extensionsConfig;
|
||||
const { path: extensionsPath } = extensionsConfig
|
||||
|
||||
const setupExtensions = async (app, db) => {
|
||||
if (extensionsPath) {
|
||||
const extensions = fs.readdirSync(extensionsPath);
|
||||
extensions.forEach((extension) => {
|
||||
if (extension === 'api.js') {
|
||||
const apiExtensions = require(`${extensionsPath}/${extension}`);
|
||||
if (extensionsPath) {
|
||||
const extensions = fs.readdirSync(extensionsPath)
|
||||
extensions.forEach((extension) => {
|
||||
if (extension === 'api.js') {
|
||||
const apiExtensions = require(`${extensionsPath}/${extension}`)
|
||||
|
||||
console.log('API extensions loaded');
|
||||
console.log('API extensions loaded')
|
||||
|
||||
Object.keys(apiExtensions).forEach((key) => {
|
||||
const api = apiExtensions[key];
|
||||
switch (api.method) {
|
||||
case 'GET':
|
||||
app.get(api.path, (req, res) => api.handler(req, res, db));
|
||||
break;
|
||||
case 'POST':
|
||||
app.post(api.path, api.handler);
|
||||
break;
|
||||
case 'PUT':
|
||||
app.put(api.path, api.handler);
|
||||
break;
|
||||
case 'DELETE':
|
||||
app.delete(api.path, api.handler);
|
||||
break;
|
||||
Object.keys(apiExtensions).forEach((key) => {
|
||||
const api = apiExtensions[key]
|
||||
switch (api.method) {
|
||||
case 'GET':
|
||||
app.get(api.path, (req, res) => api.handler(req, res, db))
|
||||
break
|
||||
case 'POST':
|
||||
app.post(api.path, api.handler)
|
||||
break
|
||||
case 'PUT':
|
||||
app.put(api.path, api.handler)
|
||||
break
|
||||
case 'DELETE':
|
||||
app.delete(api.path, api.handler)
|
||||
break
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
console.log(' >', api.path);
|
||||
});
|
||||
console.log('\n');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('No extensions directory provided');
|
||||
}
|
||||
};
|
||||
default:
|
||||
break
|
||||
}
|
||||
console.log(' >', api.path)
|
||||
})
|
||||
console.log('\n')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('No extensions directory provided')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupExtensions,
|
||||
};
|
||||
setupExtensions
|
||||
}
|
||||
|
||||
143
src/index.js
143
src/index.js
@@ -1,117 +1,114 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const winston = require('winston');
|
||||
const expressWinston = require('express-winston');
|
||||
const cors = require('cors');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const swaggerUi = require('swagger-ui-express');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
const winston = require('winston')
|
||||
const expressWinston = require('express-winston')
|
||||
const cors = require('cors')
|
||||
const rateLimit = require('express-rate-limit')
|
||||
const swaggerUi = require('swagger-ui-express')
|
||||
const cookieParser = require('cookie-parser')
|
||||
|
||||
const config = require('./config/index');
|
||||
const db = require('./db/index');
|
||||
const config = require('./config/index')
|
||||
const db = require('./db/index')
|
||||
|
||||
const rootRoutes = require('./routes/index');
|
||||
const tablesRoutes = require('./routes/tables');
|
||||
const rowsRoutes = require('./routes/rows');
|
||||
const authRoutes = require('./routes/auth');
|
||||
const rootRoutes = require('./routes/index')
|
||||
const tablesRoutes = require('./routes/tables')
|
||||
const rowsRoutes = require('./routes/rows')
|
||||
const authRoutes = require('./routes/auth')
|
||||
|
||||
const swaggerFile = require('./swagger/swagger.json');
|
||||
const { setupExtensions } = require('./extensions');
|
||||
const swaggerFile = require('./swagger/swagger.json')
|
||||
const { setupExtensions } = require('./extensions')
|
||||
const {
|
||||
createDefaultTables,
|
||||
createInitialUser,
|
||||
removeRevokedRefreshTokens,
|
||||
checkAuthConfigs,
|
||||
} = require('./controllers/auth');
|
||||
createDefaultTables,
|
||||
createInitialUser,
|
||||
removeRevokedRefreshTokens,
|
||||
checkAuthConfigs
|
||||
} = require('./controllers/auth')
|
||||
|
||||
const { runCLICommands } = require('./commands');
|
||||
const { authConstants } = require('./constants');
|
||||
const { runCLICommands } = require('./commands')
|
||||
const { authConstants } = require('./constants')
|
||||
|
||||
const app = express();
|
||||
const app = express()
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.json())
|
||||
app.use(cookieParser())
|
||||
|
||||
// Activate wal mode
|
||||
db.exec('PRAGMA journal_mode = WAL');
|
||||
db.exec('PRAGMA journal_mode = WAL')
|
||||
|
||||
// Enable CORS
|
||||
let corsOrigin = config.cors.origin;
|
||||
let corsOrigin = config.cors.origin
|
||||
|
||||
if (corsOrigin.includes('*')) {
|
||||
corsOrigin = '*';
|
||||
corsOrigin = '*'
|
||||
}
|
||||
|
||||
const corsOptions = {
|
||||
origin: corsOrigin,
|
||||
credentials: true,
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
};
|
||||
origin: corsOrigin,
|
||||
credentials: true,
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
}
|
||||
|
||||
app.use(cors(corsOptions));
|
||||
app.use(cors(corsOptions))
|
||||
|
||||
// Log requests
|
||||
if (config.verbose !== null) {
|
||||
app.use(
|
||||
expressWinston.logger({
|
||||
transports: [new winston.transports.Console()],
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
app.use(
|
||||
expressWinston.logger({
|
||||
transports: [new winston.transports.Console()],
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
|
||||
winston.format.json(),
|
||||
),
|
||||
meta: false,
|
||||
msg: 'HTTP {{req.method}} {{req.url}}',
|
||||
expressFormat: true,
|
||||
winston.format.json()
|
||||
),
|
||||
meta: false,
|
||||
msg: 'HTTP {{req.method}} {{req.url}}',
|
||||
expressFormat: true,
|
||||
|
||||
colorize: false,
|
||||
}),
|
||||
);
|
||||
colorize: false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (config.rateLimit.enabled) {
|
||||
const limiter = rateLimit({
|
||||
windowMs: config.rateLimit.windowMs,
|
||||
max: config.rateLimit.max, // Limit each IP to {max} requests per `window`
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit*` headers
|
||||
legacyHeaders: false, // Disable the `XRateLimit*` headers
|
||||
});
|
||||
const limiter = rateLimit({
|
||||
windowMs: config.rateLimit.windowMs,
|
||||
max: config.rateLimit.max, // Limit each IP to {max} requests per `window`
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit*` headers
|
||||
legacyHeaders: false // Disable the `XRateLimit*` headers
|
||||
})
|
||||
|
||||
// Apply the rate limiting middleware to all requests
|
||||
app.use(limiter);
|
||||
// Apply the rate limiting middleware to all requests
|
||||
app.use(limiter)
|
||||
}
|
||||
|
||||
// If Auth mode is activated but if the tokenSecret value is undefined then throw an error
|
||||
checkAuthConfigs({ auth: config.auth, tokenSecret: config.tokenSecret });
|
||||
checkAuthConfigs({ auth: config.auth, tokenSecret: config.tokenSecret })
|
||||
|
||||
// If Auth mode is activated then create auth tables in the DB & create a super user if there are no users in the DB
|
||||
if (config.auth) {
|
||||
createDefaultTables();
|
||||
createInitialUser();
|
||||
createDefaultTables()
|
||||
createInitialUser()
|
||||
} else {
|
||||
console.warn(
|
||||
'Warning: Soul is running in open mode without authentication or authorization for API endpoints. Please be aware that your API endpoints will not be secure.',
|
||||
);
|
||||
console.warn(
|
||||
'Warning: Soul is running in open mode without authentication or authorization for API endpoints. Please be aware that your API endpoints will not be secure.'
|
||||
)
|
||||
}
|
||||
|
||||
// remove revoked refresh tokens every X days
|
||||
setInterval(
|
||||
removeRevokedRefreshTokens,
|
||||
authConstants.REVOKED_REFRESH_TOKENS_REMOVAL_TIME_RANGE,
|
||||
);
|
||||
setInterval(removeRevokedRefreshTokens, authConstants.REVOKED_REFRESH_TOKENS_REMOVAL_TIME_RANGE)
|
||||
|
||||
// If the user has passed custom CLI commands run the command and exit to avoid running the server
|
||||
runCLICommands();
|
||||
runCLICommands()
|
||||
|
||||
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
||||
app.use('/api', rootRoutes);
|
||||
app.use('/api/tables', tablesRoutes);
|
||||
app.use('/api/tables', rowsRoutes);
|
||||
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
||||
app.use('/api', rootRoutes)
|
||||
app.use('/api/tables', tablesRoutes)
|
||||
app.use('/api/tables', rowsRoutes)
|
||||
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/auth', authRoutes)
|
||||
|
||||
setupExtensions(app, db);
|
||||
setupExtensions(app, db)
|
||||
|
||||
module.exports = app;
|
||||
module.exports = app
|
||||
|
||||
@@ -1,101 +1,89 @@
|
||||
const config = require('../config');
|
||||
const { registerUser } = require('../controllers/auth');
|
||||
const {
|
||||
apiConstants,
|
||||
dbConstants,
|
||||
responseMessages,
|
||||
} = require('../constants/');
|
||||
const { removeFields } = require('../utils');
|
||||
const { customValidator } = require('../middlewares/validation');
|
||||
const schema = require('../schemas/auth');
|
||||
const config = require('../config')
|
||||
const { registerUser } = require('../controllers/auth')
|
||||
const { apiConstants, dbConstants, responseMessages } = require('../constants/')
|
||||
const { removeFields } = require('../utils')
|
||||
const { customValidator } = require('../middlewares/validation')
|
||||
const schema = require('../schemas/auth')
|
||||
|
||||
const { httpVerbs } = apiConstants;
|
||||
const {
|
||||
reservedTableNames,
|
||||
USERS_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
tableFields,
|
||||
} = dbConstants;
|
||||
const { errorMessage } = responseMessages;
|
||||
const { httpVerbs } = apiConstants
|
||||
const { reservedTableNames, USERS_TABLE, ROLES_PERMISSIONS_TABLE, tableFields } = dbConstants
|
||||
const { errorMessage } = responseMessages
|
||||
|
||||
const processRowRequest = async (req, res, next) => {
|
||||
const resource = req.params.name;
|
||||
const { method } = req;
|
||||
const resource = req.params.name
|
||||
const { method } = req
|
||||
|
||||
// If the user sends a request to the auth tables while AUTH is set to false, throw an error
|
||||
if (apiConstants.authEndpoints.includes(resource) && !config.auth) {
|
||||
return res.status(403).send({
|
||||
message: errorMessage.AUTH_SET_TO_FALSE_ERROR,
|
||||
});
|
||||
}
|
||||
// If the user sends a request to the auth tables while AUTH is set to false, throw an error
|
||||
if (apiConstants.authEndpoints.includes(resource) && !config.auth) {
|
||||
return res.status(403).send({
|
||||
message: errorMessage.AUTH_SET_TO_FALSE_ERROR
|
||||
})
|
||||
}
|
||||
|
||||
// Redirect this request to the registerUser controller => POST /api/tables/_users/rows
|
||||
if (resource === USERS_TABLE && method === httpVerbs.POST) {
|
||||
return registerUser(req, res);
|
||||
}
|
||||
// Redirect this request to the registerUser controller => POST /api/tables/_users/rows
|
||||
if (resource === USERS_TABLE && method === httpVerbs.POST) {
|
||||
return registerUser(req, res)
|
||||
}
|
||||
|
||||
// Remove some fields for this request and check the username field => PUT /api/tables/_users/rows
|
||||
if (resource === USERS_TABLE && method === httpVerbs.PUT) {
|
||||
/**
|
||||
* remove some user fields from the request like (is_superuser, hashed_password, salt).
|
||||
* NOTE: password can be updated via the /change-password API and superuser status can be only updated from the CLI
|
||||
*/
|
||||
removeFields(
|
||||
[req.body.fields],
|
||||
[tableFields.SALT, tableFields.IS_SUPERUSER, tableFields.HASHED_PASSWORD],
|
||||
);
|
||||
}
|
||||
// Remove some fields for this request and check the username field => PUT /api/tables/_users/rows
|
||||
if (resource === USERS_TABLE && method === httpVerbs.PUT) {
|
||||
/**
|
||||
* remove some user fields from the request like (is_superuser, hashed_password, salt).
|
||||
* NOTE: password can be updated via the /change-password API and superuser status can be only updated from the CLI
|
||||
*/
|
||||
removeFields(
|
||||
[req.body.fields],
|
||||
[tableFields.SALT, tableFields.IS_SUPERUSER, tableFields.HASHED_PASSWORD]
|
||||
)
|
||||
}
|
||||
|
||||
// Validate fields for the _roles_permission API on POST and PUT requests
|
||||
if (
|
||||
resource === ROLES_PERMISSIONS_TABLE &&
|
||||
(method === httpVerbs.POST || method === httpVerbs.PUT)
|
||||
) {
|
||||
const validation = customValidator(schema.updateRolePermissions)(req);
|
||||
// Validate fields for the _roles_permission API on POST and PUT requests
|
||||
if (resource === ROLES_PERMISSIONS_TABLE && (method === httpVerbs.POST || method === httpVerbs.PUT)) {
|
||||
const validation = customValidator(schema.updateRolePermissions)(req)
|
||||
|
||||
if (validation.errorStatus) {
|
||||
return res.status(400).json({
|
||||
message: validation.message,
|
||||
error: validation.details,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (validation.errorStatus) {
|
||||
return res.status(400).json({
|
||||
message: validation.message,
|
||||
error: validation.details
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
next()
|
||||
}
|
||||
|
||||
const processRowResponse = async (req, res, next) => {
|
||||
// Extract payload data
|
||||
const resource = req.params.name;
|
||||
const status = req.response.status;
|
||||
const payload = req.response.payload;
|
||||
// Extract payload data
|
||||
const resource = req.params.name
|
||||
const status = req.response.status
|
||||
const payload = req.response.payload
|
||||
|
||||
// Remove some fields from the response
|
||||
if (resource === USERS_TABLE) {
|
||||
removeFields(payload.data, [tableFields.SALT, tableFields.HASHED_PASSWORD]);
|
||||
}
|
||||
// Remove some fields from the response
|
||||
if (resource === USERS_TABLE) {
|
||||
removeFields(payload.data, [tableFields.SALT, tableFields.HASHED_PASSWORD])
|
||||
}
|
||||
|
||||
res.status(status).send(payload);
|
||||
next();
|
||||
};
|
||||
res.status(status).send(payload)
|
||||
next()
|
||||
}
|
||||
|
||||
const processTableRequest = async (req, res, next) => {
|
||||
const { method, body, baseUrl } = req;
|
||||
const { method, body, baseUrl } = req
|
||||
|
||||
// if the user tries to create a table with the reserved table names throw an error. Request => POST /api/tables
|
||||
if (baseUrl === apiConstants.baseTableUrl && method === httpVerbs.POST) {
|
||||
if (reservedTableNames.includes(body.name)) {
|
||||
return res.status(409).send({
|
||||
message: errorMessage.RESERVED_TABLE_NAME_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
// if the user tries to create a table with the reserved table names throw an error. Request => POST /api/tables
|
||||
if (baseUrl === apiConstants.baseTableUrl && method === httpVerbs.POST) {
|
||||
if (reservedTableNames.includes(body.name)) {
|
||||
return res.status(409).send({
|
||||
message: errorMessage.RESERVED_TABLE_NAME_ERROR
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
processRowRequest,
|
||||
processRowResponse,
|
||||
processTableRequest,
|
||||
};
|
||||
processRowRequest,
|
||||
processRowResponse,
|
||||
processTableRequest
|
||||
}
|
||||
|
||||
@@ -1,90 +1,76 @@
|
||||
const config = require('../config');
|
||||
const { decodeToken, toBoolean } = require('../utils/index');
|
||||
const { apiConstants, responseMessages } = require('../constants');
|
||||
const { authService } = require('../services');
|
||||
const config = require('../config')
|
||||
const { decodeToken, toBoolean } = require('../utils/index')
|
||||
const { apiConstants, responseMessages } = require('../constants')
|
||||
const { authService } = require('../services')
|
||||
|
||||
const { errorMessage } = responseMessages;
|
||||
const { errorMessage } = responseMessages
|
||||
|
||||
const hasAccess = async (req, res, next) => {
|
||||
let payload;
|
||||
const { name: tableName } = req.params;
|
||||
const verb = req.method;
|
||||
const originalURL = req.originalUrl;
|
||||
let payload
|
||||
const { name: tableName } = req.params
|
||||
const verb = req.method
|
||||
const originalURL = req.originalUrl
|
||||
|
||||
try {
|
||||
if (config.auth) {
|
||||
// extract the payload from the token and verify it
|
||||
try {
|
||||
payload = await decodeToken(
|
||||
req.cookies.accessToken,
|
||||
config.tokenSecret,
|
||||
);
|
||||
req.user = payload;
|
||||
} catch (error) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.INVALID_ACCESS_TOKEN_ERROR });
|
||||
}
|
||||
try {
|
||||
if (config.auth) {
|
||||
// extract the payload from the token and verify it
|
||||
try {
|
||||
payload = await decodeToken(req.cookies.accessToken, config.tokenSecret)
|
||||
req.user = payload
|
||||
} catch (error) {
|
||||
return res.status(401).send({ message: errorMessage.INVALID_ACCESS_TOKEN_ERROR })
|
||||
}
|
||||
|
||||
// if the user is a super_user, allow access on the resource
|
||||
if (toBoolean(payload.isSuperuser)) {
|
||||
return next();
|
||||
}
|
||||
// if the user is a super_user, allow access on the resource
|
||||
if (toBoolean(payload.isSuperuser)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// if the endpoint is set to be accessed by any user regardless of there roles, then allow access
|
||||
if (apiConstants.universalAccessEndpoints.includes(originalURL)) {
|
||||
return next();
|
||||
}
|
||||
// if the endpoint is set to be accessed by any user regardless of there roles, then allow access
|
||||
if (apiConstants.universalAccessEndpoints.includes(originalURL)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// if table_name is not passed from the router throw unauthorized error
|
||||
if (!tableName) {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ message: errorMessage.NOT_AUTHORIZED_ERROR });
|
||||
}
|
||||
// if table_name is not passed from the router throw unauthorized error
|
||||
if (!tableName) {
|
||||
return res.status(403).send({ message: errorMessage.NOT_AUTHORIZED_ERROR })
|
||||
}
|
||||
|
||||
// if the user is not a super user, fetch the permission of the user from the DB
|
||||
const rolePermissions = authService.getPermissionByRoleIds({
|
||||
roleIds: payload.roleIds,
|
||||
});
|
||||
// if the user is not a super user, fetch the permission of the user from the DB
|
||||
const rolePermissions = authService.getPermissionByRoleIds({
|
||||
roleIds: payload.roleIds
|
||||
})
|
||||
|
||||
const resourcePermission = rolePermissions.filter(
|
||||
(row) => row.table_name === tableName,
|
||||
);
|
||||
const resourcePermission = rolePermissions.filter((row) => row.table_name === tableName)
|
||||
|
||||
if (resourcePermission.length <= 0) {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ message: errorMessage.PERMISSION_NOT_DEFINED_ERROR });
|
||||
}
|
||||
if (resourcePermission.length <= 0) {
|
||||
return res.status(403).send({ message: errorMessage.PERMISSION_NOT_DEFINED_ERROR })
|
||||
}
|
||||
|
||||
// If the user has permission on the table in at least in one of the roles then allow access on the table
|
||||
let hasPermission = false;
|
||||
// If the user has permission on the table in at least in one of the roles then allow access on the table
|
||||
let hasPermission = false
|
||||
|
||||
resourcePermission.some((resource) => {
|
||||
const httpMethod =
|
||||
apiConstants.httpMethodDefinitions[verb].toLowerCase();
|
||||
resourcePermission.some((resource) => {
|
||||
const httpMethod = apiConstants.httpMethodDefinitions[verb].toLowerCase()
|
||||
|
||||
if (toBoolean(resource[httpMethod])) {
|
||||
hasPermission = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (toBoolean(resource[httpMethod])) {
|
||||
hasPermission = true
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if (hasPermission) {
|
||||
next();
|
||||
} else {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ message: errorMessage.NOT_AUTHORIZED_ERROR });
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(401).send({ message: error.message });
|
||||
}
|
||||
};
|
||||
if (hasPermission) {
|
||||
next()
|
||||
} else {
|
||||
return res.status(403).send({ message: errorMessage.NOT_AUTHORIZED_ERROR })
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
res.status(401).send({ message: error.message })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { hasAccess };
|
||||
module.exports = { hasAccess }
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
const { websocketSubscribers } = require('../websocket');
|
||||
const { websocketSubscribers } = require('../websocket')
|
||||
|
||||
const broadcast = (req) => {
|
||||
const data = req.broadcast;
|
||||
const { name: tableName } = req.params;
|
||||
const data = req.broadcast
|
||||
const { name: tableName } = req.params
|
||||
|
||||
const subscribers = websocketSubscribers?.get(tableName);
|
||||
const subscribers = websocketSubscribers?.get(tableName)
|
||||
|
||||
if (subscribers) {
|
||||
subscribers.forEach(({ ws }) => {
|
||||
ws.send(JSON.stringify(data));
|
||||
});
|
||||
}
|
||||
};
|
||||
if (subscribers) {
|
||||
subscribers.forEach(({ ws }) => {
|
||||
ws.send(JSON.stringify(data))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
broadcast,
|
||||
};
|
||||
broadcast
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// including the method, url, status code and response time
|
||||
const logger = (req, res, next) => {
|
||||
const start = Date.now();
|
||||
res.on('finish', () => {
|
||||
const delta = Date.now() - start;
|
||||
console.log(`${req.method} ${req.url} ${res.statusCode} ${delta}ms`);
|
||||
});
|
||||
next();
|
||||
};
|
||||
const start = Date.now()
|
||||
res.on('finish', () => {
|
||||
const delta = Date.now() - start
|
||||
console.log(`${req.method} ${req.url} ${res.statusCode} ${delta}ms`)
|
||||
})
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logger,
|
||||
};
|
||||
logger
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
const validator = (schema) => (req, res, next) => {
|
||||
const { body, params, query, cookies } = req;
|
||||
const data = { body, params, query, cookies };
|
||||
const { body, params, query, cookies } = req
|
||||
const data = { body, params, query, cookies }
|
||||
|
||||
const { value, error } = schema.validate(data);
|
||||
const { value, error } = schema.validate(data)
|
||||
|
||||
if (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error.details,
|
||||
});
|
||||
} else {
|
||||
req.body = value.body;
|
||||
req.params = value.params;
|
||||
req.query = value.query;
|
||||
req.cookies = value.cookies;
|
||||
if (error) {
|
||||
res.status(400).json({
|
||||
message: error.message,
|
||||
error: error.details
|
||||
})
|
||||
} else {
|
||||
req.body = value.body
|
||||
req.params = value.params
|
||||
req.query = value.query
|
||||
req.cookies = value.cookies
|
||||
|
||||
next();
|
||||
}
|
||||
};
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
const customValidator = (schema) => (req) => {
|
||||
const response = { errorStatus: false, message: '', error: '' };
|
||||
const response = { errorStatus: false, message: '', error: '' }
|
||||
|
||||
const { body, params, query, cookies } = req;
|
||||
const data = { body, params, query, cookies };
|
||||
const { body, params, query, cookies } = req
|
||||
const data = { body, params, query, cookies }
|
||||
|
||||
const { error } = schema.validate(data);
|
||||
const { error } = schema.validate(data)
|
||||
|
||||
if (error) {
|
||||
response.errorStatus = true;
|
||||
response.message = error.message;
|
||||
response.error = error.details;
|
||||
}
|
||||
if (error) {
|
||||
response.errorStatus = true
|
||||
response.message = error.message
|
||||
response.error = error.details
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
return response
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validator,
|
||||
customValidator,
|
||||
};
|
||||
validator,
|
||||
customValidator
|
||||
}
|
||||
|
||||
@@ -1,35 +1,18 @@
|
||||
const express = require('express');
|
||||
const express = require('express')
|
||||
|
||||
const controllers = require('../controllers/auth');
|
||||
const { validator } = require('../middlewares/validation');
|
||||
const schema = require('../schemas/auth');
|
||||
const { hasAccess } = require('../middlewares/auth');
|
||||
const controllers = require('../controllers/auth')
|
||||
const { validator } = require('../middlewares/validation')
|
||||
const schema = require('../schemas/auth')
|
||||
const { hasAccess } = require('../middlewares/auth')
|
||||
|
||||
const router = express.Router();
|
||||
const router = express.Router()
|
||||
|
||||
router.post(
|
||||
'/token/obtain',
|
||||
validator(schema.obtainAccessToken),
|
||||
controllers.obtainAccessToken,
|
||||
);
|
||||
router.post('/token/obtain', validator(schema.obtainAccessToken), controllers.obtainAccessToken)
|
||||
|
||||
router.get(
|
||||
'/token/refresh',
|
||||
validator(schema.refreshAccessToken),
|
||||
controllers.refreshAccessToken,
|
||||
);
|
||||
router.get('/token/refresh', validator(schema.refreshAccessToken), controllers.refreshAccessToken)
|
||||
|
||||
router.put(
|
||||
'/change-password',
|
||||
hasAccess,
|
||||
validator(schema.changePassword),
|
||||
controllers.changePassword,
|
||||
);
|
||||
router.put('/change-password', hasAccess, validator(schema.changePassword), controllers.changePassword)
|
||||
|
||||
router.get(
|
||||
'/logout',
|
||||
validator(schema.removeAccessTokens),
|
||||
controllers.removeTokens,
|
||||
);
|
||||
router.get('/logout', validator(schema.removeAccessTokens), controllers.removeTokens)
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const express = require('express');
|
||||
const express = require('express')
|
||||
|
||||
const controllers = require('../controllers/index');
|
||||
const controllers = require('../controllers/index')
|
||||
|
||||
const router = express.Router();
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/', controllers.root);
|
||||
router.get('/health', controllers.health);
|
||||
router.get('/', controllers.root)
|
||||
router.get('/health', controllers.health)
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
const express = require('express');
|
||||
const express = require('express')
|
||||
|
||||
const controllers = require('../controllers/rows');
|
||||
const { broadcast } = require('../middlewares/broadcast');
|
||||
const { validator } = require('../middlewares/validation');
|
||||
const { processRowRequest, processRowResponse } = require('../middlewares/api');
|
||||
const { hasAccess } = require('../middlewares/auth');
|
||||
const schema = require('../schemas/rows');
|
||||
const controllers = require('../controllers/rows')
|
||||
const { broadcast } = require('../middlewares/broadcast')
|
||||
const { validator } = require('../middlewares/validation')
|
||||
const { processRowRequest, processRowResponse } = require('../middlewares/api')
|
||||
const { hasAccess } = require('../middlewares/auth')
|
||||
const schema = require('../schemas/rows')
|
||||
|
||||
const router = express.Router();
|
||||
const router = express.Router()
|
||||
|
||||
router.get(
|
||||
'/:name/rows',
|
||||
hasAccess,
|
||||
validator(schema.listTableRows),
|
||||
processRowRequest,
|
||||
controllers.listTableRows,
|
||||
processRowResponse,
|
||||
);
|
||||
'/:name/rows',
|
||||
hasAccess,
|
||||
validator(schema.listTableRows),
|
||||
processRowRequest,
|
||||
controllers.listTableRows,
|
||||
processRowResponse
|
||||
)
|
||||
router.post(
|
||||
'/:name/rows',
|
||||
hasAccess,
|
||||
validator(schema.insertRowInTable),
|
||||
processRowRequest,
|
||||
controllers.insertRowInTable,
|
||||
broadcast,
|
||||
);
|
||||
'/:name/rows',
|
||||
hasAccess,
|
||||
validator(schema.insertRowInTable),
|
||||
processRowRequest,
|
||||
controllers.insertRowInTable,
|
||||
broadcast
|
||||
)
|
||||
router.get(
|
||||
'/:name/rows/:pks',
|
||||
hasAccess,
|
||||
validator(schema.getRowInTableByPK),
|
||||
controllers.getRowInTableByPK,
|
||||
processRowResponse,
|
||||
);
|
||||
'/:name/rows/:pks',
|
||||
hasAccess,
|
||||
validator(schema.getRowInTableByPK),
|
||||
controllers.getRowInTableByPK,
|
||||
processRowResponse
|
||||
)
|
||||
router.put(
|
||||
'/:name/rows/:pks',
|
||||
hasAccess,
|
||||
validator(schema.updateRowInTableByPK),
|
||||
processRowRequest,
|
||||
controllers.updateRowInTableByPK,
|
||||
broadcast,
|
||||
);
|
||||
'/:name/rows/:pks',
|
||||
hasAccess,
|
||||
validator(schema.updateRowInTableByPK),
|
||||
processRowRequest,
|
||||
controllers.updateRowInTableByPK,
|
||||
broadcast
|
||||
)
|
||||
router.delete(
|
||||
'/:name/rows/:pks',
|
||||
hasAccess,
|
||||
validator(schema.deleteRowInTableByPK),
|
||||
controllers.deleteRowInTableByPK,
|
||||
broadcast,
|
||||
);
|
||||
'/:name/rows/:pks',
|
||||
hasAccess,
|
||||
validator(schema.deleteRowInTableByPK),
|
||||
controllers.deleteRowInTableByPK,
|
||||
broadcast
|
||||
)
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router
|
||||
|
||||
@@ -1,40 +1,19 @@
|
||||
const express = require('express');
|
||||
const express = require('express')
|
||||
|
||||
const controllers = require('../controllers/tables');
|
||||
const { validator } = require('../middlewares/validation');
|
||||
const schema = require('../schemas/tables');
|
||||
const { hasAccess } = require('../middlewares/auth');
|
||||
const { processTableRequest } = require('../middlewares/api');
|
||||
const controllers = require('../controllers/tables')
|
||||
const { validator } = require('../middlewares/validation')
|
||||
const schema = require('../schemas/tables')
|
||||
const { hasAccess } = require('../middlewares/auth')
|
||||
const { processTableRequest } = require('../middlewares/api')
|
||||
|
||||
const router = express.Router();
|
||||
const router = express.Router()
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
hasAccess,
|
||||
validator(schema.listTables),
|
||||
controllers.listTables,
|
||||
);
|
||||
router.get('/', hasAccess, validator(schema.listTables), controllers.listTables)
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
processTableRequest,
|
||||
hasAccess,
|
||||
validator(schema.createTable),
|
||||
controllers.createTable,
|
||||
);
|
||||
router.post('/', processTableRequest, hasAccess, validator(schema.createTable), controllers.createTable)
|
||||
|
||||
router.get(
|
||||
'/:name',
|
||||
hasAccess,
|
||||
validator(schema.getTableSchema),
|
||||
controllers.getTableSchema,
|
||||
);
|
||||
router.get('/:name', hasAccess, validator(schema.getTableSchema), controllers.getTableSchema)
|
||||
|
||||
router.delete(
|
||||
'/:name',
|
||||
hasAccess,
|
||||
validator(schema.deleteTable),
|
||||
controllers.deleteTable,
|
||||
);
|
||||
router.delete('/:name', hasAccess, validator(schema.deleteTable), controllers.deleteTable)
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router
|
||||
|
||||
@@ -1,109 +1,109 @@
|
||||
const Joi = require('joi');
|
||||
const Joi = require('joi')
|
||||
|
||||
const obtainAccessToken = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required()
|
||||
}).required()
|
||||
}).required(),
|
||||
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}).unknown(true),
|
||||
});
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
}).unknown(true)
|
||||
})
|
||||
|
||||
const refreshAccessToken = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().required(),
|
||||
accessToken: Joi.string().optional(),
|
||||
})
|
||||
.unknown(true)
|
||||
.required(),
|
||||
});
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().required(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
.unknown(true)
|
||||
.required()
|
||||
})
|
||||
|
||||
const changePassword = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object().required(),
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object().required(),
|
||||
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
currentPassword: Joi.string().required(),
|
||||
newPassword: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional(),
|
||||
})
|
||||
.unknown(true)
|
||||
.required(),
|
||||
});
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
currentPassword: Joi.string().required(),
|
||||
newPassword: Joi.string().required()
|
||||
}).required()
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional()
|
||||
})
|
||||
.unknown(true)
|
||||
.required()
|
||||
})
|
||||
|
||||
const registerUser = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required()
|
||||
}).required()
|
||||
}).required(),
|
||||
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional(),
|
||||
})
|
||||
.unknown(true)
|
||||
.required(),
|
||||
});
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional()
|
||||
})
|
||||
.unknown(true)
|
||||
.required()
|
||||
})
|
||||
|
||||
const updateRolePermissions = Joi.object({
|
||||
query: Joi.object({}).required(),
|
||||
params: Joi.object({ name: Joi.string(), pks: Joi.string() }).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
role_id: Joi.number().required(),
|
||||
table_name: Joi.string().required(),
|
||||
create: Joi.number().valid(0, 1).required(),
|
||||
read: Joi.number().valid(0, 1).required(),
|
||||
update: Joi.number().valid(0, 1).required(),
|
||||
delete: Joi.number().valid(0, 1).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
query: Joi.object({}).required(),
|
||||
params: Joi.object({ name: Joi.string(), pks: Joi.string() }).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
role_id: Joi.number().required(),
|
||||
table_name: Joi.string().required(),
|
||||
create: Joi.number().valid(0, 1).required(),
|
||||
read: Joi.number().valid(0, 1).required(),
|
||||
update: Joi.number().valid(0, 1).required(),
|
||||
delete: Joi.number().valid(0, 1).required()
|
||||
}).required()
|
||||
}).required(),
|
||||
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional(),
|
||||
})
|
||||
.unknown(true)
|
||||
.required(),
|
||||
});
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional()
|
||||
})
|
||||
.unknown(true)
|
||||
.required()
|
||||
})
|
||||
|
||||
const removeAccessTokens = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().required(),
|
||||
accessToken: Joi.string().required(),
|
||||
})
|
||||
.unknown(true)
|
||||
.required(),
|
||||
});
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().required(),
|
||||
accessToken: Joi.string().required()
|
||||
})
|
||||
.unknown(true)
|
||||
.required()
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
obtainAccessToken,
|
||||
refreshAccessToken,
|
||||
changePassword,
|
||||
registerUser,
|
||||
updateRolePermissions,
|
||||
removeAccessTokens,
|
||||
};
|
||||
obtainAccessToken,
|
||||
refreshAccessToken,
|
||||
changePassword,
|
||||
registerUser,
|
||||
updateRolePermissions,
|
||||
removeAccessTokens
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
const Joi = require('joi');
|
||||
const Joi = require('joi')
|
||||
|
||||
const transaction = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object().required(),
|
||||
body: Joi.object({
|
||||
transaction: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
statement: Joi.string().required(),
|
||||
values: Joi.object().required(),
|
||||
}),
|
||||
Joi.object({
|
||||
query: Joi.string().required(),
|
||||
}),
|
||||
)
|
||||
.required(),
|
||||
}).required(),
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object().required(),
|
||||
body: Joi.object({
|
||||
transaction: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
statement: Joi.string().required(),
|
||||
values: Joi.object().required()
|
||||
}),
|
||||
Joi.object({
|
||||
query: Joi.string().required()
|
||||
})
|
||||
)
|
||||
.required()
|
||||
}).required(),
|
||||
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
transaction,
|
||||
};
|
||||
transaction
|
||||
}
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
const Joi = require('joi');
|
||||
const Joi = require('joi')
|
||||
|
||||
const listTableRows = Joi.object({
|
||||
query: Joi.object({
|
||||
_page: Joi.number().integer().min(1).default(1),
|
||||
_limit: Joi.number().integer().min(1).default(10),
|
||||
_search: Joi.string(),
|
||||
_ordering: Joi.string().regex(/^[\w-]+$/),
|
||||
_schema: Joi.string(),
|
||||
_extend: Joi.string(),
|
||||
_filters: Joi.string(),
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string(),
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object({
|
||||
_page: Joi.number().integer().min(1).default(1),
|
||||
_limit: Joi.number().integer().min(1).default(10),
|
||||
_search: Joi.string(),
|
||||
_ordering: Joi.string().regex(/^[\w-]+$/),
|
||||
_schema: Joi.string(),
|
||||
_extend: Joi.string(),
|
||||
_filters: Joi.string()
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
const insertRowInTable = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object().required(),
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required()
|
||||
}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object().required()
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
const getRowInTableByPK = Joi.object({
|
||||
query: Joi.object({
|
||||
_lookup_field: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30),
|
||||
_schema: Joi.string(),
|
||||
_extend: Joi.string(),
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
pks: Joi.string().required(),
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object({
|
||||
_lookup_field: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30),
|
||||
_schema: Joi.string(),
|
||||
_extend: Joi.string()
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
pks: Joi.string().required()
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
const updateRowInTableByPK = Joi.object({
|
||||
query: Joi.object({
|
||||
_lookup_field: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30),
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
pks: Joi.string().required(),
|
||||
}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object().required(),
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object({
|
||||
_lookup_field: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
pks: Joi.string().required()
|
||||
}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object().required()
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
const deleteRowInTableByPK = Joi.object({
|
||||
query: Joi.object({
|
||||
_lookup_field: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30),
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
pks: Joi.string().required(),
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object({
|
||||
_lookup_field: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
}).required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
pks: Joi.string().required()
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
listTableRows,
|
||||
insertRowInTable,
|
||||
getRowInTableByPK,
|
||||
updateRowInTableByPK,
|
||||
deleteRowInTableByPK,
|
||||
};
|
||||
listTableRows,
|
||||
insertRowInTable,
|
||||
getRowInTableByPK,
|
||||
updateRowInTableByPK,
|
||||
deleteRowInTableByPK
|
||||
}
|
||||
|
||||
@@ -1,121 +1,112 @@
|
||||
const Joi = require('joi');
|
||||
const Joi = require('joi')
|
||||
|
||||
const listTables = Joi.object({
|
||||
query: Joi.object({
|
||||
_search: Joi.string(),
|
||||
_ordering: Joi.string(),
|
||||
}).required(),
|
||||
params: Joi.object().required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object({
|
||||
_search: Joi.string(),
|
||||
_ordering: Joi.string()
|
||||
}).required(),
|
||||
params: Joi.object().required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
const createTable = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object().required(),
|
||||
body: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
autoAddCreatedAt: Joi.boolean().default(true),
|
||||
autoAddUpdatedAt: Joi.boolean().default(true),
|
||||
schema: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
// type one of sqlite3 types
|
||||
type: Joi.string()
|
||||
.valid(
|
||||
'TEXT',
|
||||
'NUMERIC',
|
||||
'INTEGER',
|
||||
'REAL',
|
||||
'BLOB',
|
||||
'BOOLEAN',
|
||||
'DATE',
|
||||
'DATETIME',
|
||||
)
|
||||
.insensitive()
|
||||
.required(),
|
||||
default: Joi.any(),
|
||||
notNull: Joi.boolean(),
|
||||
unique: Joi.boolean(),
|
||||
primaryKey: Joi.boolean(),
|
||||
foreignKey: Joi.object({
|
||||
table: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
column: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
onDelete: Joi.string()
|
||||
.valid('CASCADE', 'SET NULL', 'SET DEFAULT', 'RESTRICT')
|
||||
.insensitive()
|
||||
.default('CASCADE'),
|
||||
onUpdate: Joi.string()
|
||||
.valid('CASCADE', 'SET NULL', 'SET DEFAULT', 'RESTRICT')
|
||||
.insensitive()
|
||||
.default('RESTRICT'),
|
||||
}),
|
||||
index: Joi.boolean(),
|
||||
}),
|
||||
)
|
||||
.required(),
|
||||
}),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object().required(),
|
||||
body: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
autoAddCreatedAt: Joi.boolean().default(true),
|
||||
autoAddUpdatedAt: Joi.boolean().default(true),
|
||||
schema: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
// type one of sqlite3 types
|
||||
type: Joi.string()
|
||||
.valid('TEXT', 'NUMERIC', 'INTEGER', 'REAL', 'BLOB', 'BOOLEAN', 'DATE', 'DATETIME')
|
||||
.insensitive()
|
||||
.required(),
|
||||
default: Joi.any(),
|
||||
notNull: Joi.boolean(),
|
||||
unique: Joi.boolean(),
|
||||
primaryKey: Joi.boolean(),
|
||||
foreignKey: Joi.object({
|
||||
table: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
column: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(2)
|
||||
.max(30)
|
||||
.required(),
|
||||
onDelete: Joi.string()
|
||||
.valid('CASCADE', 'SET NULL', 'SET DEFAULT', 'RESTRICT')
|
||||
.insensitive()
|
||||
.default('CASCADE'),
|
||||
onUpdate: Joi.string()
|
||||
.valid('CASCADE', 'SET NULL', 'SET DEFAULT', 'RESTRICT')
|
||||
.insensitive()
|
||||
.default('RESTRICT')
|
||||
}),
|
||||
index: Joi.boolean()
|
||||
})
|
||||
)
|
||||
.required()
|
||||
}),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
const getTableSchema = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
}),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required()
|
||||
}),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
const deleteTable = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required(),
|
||||
}),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({
|
||||
name: Joi.string()
|
||||
.regex(/^[\w-]+$/)
|
||||
.min(3)
|
||||
.max(30)
|
||||
.required()
|
||||
}),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional()
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
listTables,
|
||||
createTable,
|
||||
getTableSchema,
|
||||
deleteTable,
|
||||
};
|
||||
listTables,
|
||||
createTable,
|
||||
getTableSchema,
|
||||
deleteTable
|
||||
}
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const http = require('http');
|
||||
const http = require('http')
|
||||
|
||||
const app = require('./index');
|
||||
const { wss } = require('./websocket');
|
||||
const config = require('./config/index');
|
||||
const app = require('./index')
|
||||
const { wss } = require('./websocket')
|
||||
const config = require('./config/index')
|
||||
|
||||
if (config.startWithStudio) {
|
||||
(async () => {
|
||||
const { handler: soulStudioHandler } = await import(
|
||||
'soul-studio/build/handler.js'
|
||||
);
|
||||
app.use('/studio', soulStudioHandler);
|
||||
})();
|
||||
;(async () => {
|
||||
const { handler: soulStudioHandler } = await import('soul-studio/build/handler.js')
|
||||
app.use('/studio', soulStudioHandler)
|
||||
})()
|
||||
}
|
||||
|
||||
const server = http.createServer(app);
|
||||
const server = http.createServer(app)
|
||||
|
||||
const port = config.port;
|
||||
const port = config.port
|
||||
|
||||
const baseURL = `http://localhost:${port}`;
|
||||
const coreURL = `${baseURL}/api/`;
|
||||
const studioURL = `${baseURL}/studio/`;
|
||||
const baseURL = `http://localhost:${port}`
|
||||
const coreURL = `${baseURL}/api/`
|
||||
const studioURL = `${baseURL}/studio/`
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`Soul is running...`);
|
||||
console.log(` > Core API at ${coreURL}`);
|
||||
console.log(`Soul is running...`)
|
||||
console.log(` > Core API at ${coreURL}`)
|
||||
|
||||
if (config.startWithStudio) {
|
||||
console.log(` > Studio at ${studioURL}`);
|
||||
require('child_process').exec(`open ${studioURL}`);
|
||||
}
|
||||
});
|
||||
if (config.startWithStudio) {
|
||||
console.log(` > Studio at ${studioURL}`)
|
||||
require('child_process').exec(`open ${studioURL}`)
|
||||
}
|
||||
})
|
||||
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
wss.handleUpgrade(request, socket, head, (socket) => {
|
||||
wss.emit('connection', socket, request);
|
||||
});
|
||||
});
|
||||
wss.handleUpgrade(request, socket, head, (socket) => {
|
||||
wss.emit('connection', socket, request)
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = {};
|
||||
module.exports = {}
|
||||
|
||||
@@ -1,110 +1,108 @@
|
||||
const db = require('../db');
|
||||
const rowService = require('./rowService')(db);
|
||||
const db = require('../db')
|
||||
const rowService = require('./rowService')(db)
|
||||
|
||||
const { constantRoles, dbConstants } = require('../constants');
|
||||
const { constantRoles, dbConstants } = require('../constants')
|
||||
|
||||
const {
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
tableFields,
|
||||
} = dbConstants;
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
REVOKED_REFRESH_TOKENS_TABLE,
|
||||
tableFields
|
||||
} = dbConstants
|
||||
|
||||
module.exports = (db) => {
|
||||
return {
|
||||
getUsersByUsername({ username }) {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: `WHERE ${tableFields.USERNAME} =?`,
|
||||
whereStringValues: [username],
|
||||
});
|
||||
return {
|
||||
getUsersByUsername({ username }) {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: `WHERE ${tableFields.USERNAME} =?`,
|
||||
whereStringValues: [username]
|
||||
})
|
||||
|
||||
return users;
|
||||
},
|
||||
return users
|
||||
},
|
||||
|
||||
getUsersById({ userId }) {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: `WHERE ${tableFields.ID}=?`,
|
||||
whereStringValues: [userId],
|
||||
});
|
||||
getUsersById({ userId }) {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: `WHERE ${tableFields.ID}=?`,
|
||||
whereStringValues: [userId]
|
||||
})
|
||||
|
||||
return users;
|
||||
},
|
||||
return users
|
||||
},
|
||||
|
||||
getAllUsers() {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: '',
|
||||
whereStringValues: [],
|
||||
});
|
||||
getAllUsers() {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: '',
|
||||
whereStringValues: []
|
||||
})
|
||||
|
||||
return users;
|
||||
},
|
||||
return users
|
||||
},
|
||||
|
||||
// TODO: bypass pagination by providing query param for number of rows
|
||||
getPermissionByRoleIds({ roleIds }) {
|
||||
const permissions = rowService.get({
|
||||
tableName: ROLES_PERMISSIONS_TABLE,
|
||||
whereString: `WHERE ${tableFields.ROLE_ID} IN (${roleIds.map(
|
||||
() => '?',
|
||||
)})`,
|
||||
whereStringValues: [...roleIds],
|
||||
limit: 10000,
|
||||
});
|
||||
// TODO: bypass pagination by providing query param for number of rows
|
||||
getPermissionByRoleIds({ roleIds }) {
|
||||
const permissions = rowService.get({
|
||||
tableName: ROLES_PERMISSIONS_TABLE,
|
||||
whereString: `WHERE ${tableFields.ROLE_ID} IN (${roleIds.map(() => '?')})`,
|
||||
whereStringValues: [...roleIds],
|
||||
limit: 10000
|
||||
})
|
||||
|
||||
return permissions;
|
||||
},
|
||||
return permissions
|
||||
},
|
||||
|
||||
getUserRoleByUserId({ userId }) {
|
||||
const userRoles = rowService.get({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
whereString: `WHERE ${tableFields.USER_ID} =?`,
|
||||
whereStringValues: [userId],
|
||||
});
|
||||
getUserRoleByUserId({ userId }) {
|
||||
const userRoles = rowService.get({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
whereString: `WHERE ${tableFields.USER_ID} =?`,
|
||||
whereStringValues: [userId]
|
||||
})
|
||||
|
||||
return userRoles;
|
||||
},
|
||||
return userRoles
|
||||
},
|
||||
|
||||
getDefaultRole() {
|
||||
const defaultRole = rowService.get({
|
||||
tableName: ROLES_TABLE,
|
||||
whereString: `WHERE ${tableFields.ROLE_NAME}=?`,
|
||||
whereStringValues: [constantRoles.DEFAULT_ROLE],
|
||||
});
|
||||
getDefaultRole() {
|
||||
const defaultRole = rowService.get({
|
||||
tableName: ROLES_TABLE,
|
||||
whereString: `WHERE ${tableFields.ROLE_NAME}=?`,
|
||||
whereStringValues: [constantRoles.DEFAULT_ROLE]
|
||||
})
|
||||
|
||||
return defaultRole;
|
||||
},
|
||||
return defaultRole
|
||||
},
|
||||
|
||||
saveRevokedRefreshToken({ refreshToken, expiresAt }) {
|
||||
const { lastInsertRowid } = rowService.save({
|
||||
tableName: REVOKED_REFRESH_TOKENS_TABLE,
|
||||
fields: {
|
||||
refresh_token: refreshToken,
|
||||
expires_at: expiresAt,
|
||||
},
|
||||
});
|
||||
saveRevokedRefreshToken({ refreshToken, expiresAt }) {
|
||||
const { lastInsertRowid } = rowService.save({
|
||||
tableName: REVOKED_REFRESH_TOKENS_TABLE,
|
||||
fields: {
|
||||
refresh_token: refreshToken,
|
||||
expires_at: expiresAt
|
||||
}
|
||||
})
|
||||
|
||||
return { id: lastInsertRowid };
|
||||
},
|
||||
return { id: lastInsertRowid }
|
||||
},
|
||||
|
||||
getRevokedRefreshToken({ refreshToken }) {
|
||||
const token = rowService.get({
|
||||
tableName: REVOKED_REFRESH_TOKENS_TABLE,
|
||||
whereString: `WHERE ${tableFields.REFRESH_TOKEN}=?`,
|
||||
whereStringValues: [refreshToken],
|
||||
});
|
||||
getRevokedRefreshToken({ refreshToken }) {
|
||||
const token = rowService.get({
|
||||
tableName: REVOKED_REFRESH_TOKENS_TABLE,
|
||||
whereString: `WHERE ${tableFields.REFRESH_TOKEN}=?`,
|
||||
whereStringValues: [refreshToken]
|
||||
})
|
||||
|
||||
return token;
|
||||
},
|
||||
return token
|
||||
},
|
||||
|
||||
deleteRevokedRefreshTokens({ lookupField }) {
|
||||
const query = `DELETE FROM ${REVOKED_REFRESH_TOKENS_TABLE} ${lookupField}`;
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.run();
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
deleteRevokedRefreshTokens({ lookupField }) {
|
||||
const query = `DELETE FROM ${REVOKED_REFRESH_TOKENS_TABLE} ${lookupField}`
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.run()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const db = require('../db');
|
||||
const db = require('../db')
|
||||
|
||||
const rowService = require('./rowService')(db);
|
||||
const tableService = require('./tableService')(db);
|
||||
const authService = require('./authService')(db);
|
||||
const rowService = require('./rowService')(db)
|
||||
const tableService = require('./tableService')(db)
|
||||
const authService = require('./authService')(db)
|
||||
|
||||
module.exports = { rowService, tableService, authService };
|
||||
module.exports = { rowService, tableService, authService }
|
||||
|
||||
@@ -1,98 +1,94 @@
|
||||
const { apiConstants } = require('../constants');
|
||||
const { apiConstants } = require('../constants')
|
||||
|
||||
module.exports = (db) => {
|
||||
return {
|
||||
get(data) {
|
||||
const query = `SELECT ${data.schemaString || '*'} FROM ${
|
||||
data.tableName
|
||||
} ${data.extendString || ''} ${data.whereString || ''} ${
|
||||
data.orderString || ''
|
||||
} LIMIT ? OFFSET ?`;
|
||||
return {
|
||||
get(data) {
|
||||
const query = `SELECT ${data.schemaString || '*'} FROM ${data.tableName} ${
|
||||
data.extendString || ''
|
||||
} ${data.whereString || ''} ${data.orderString || ''} LIMIT ? OFFSET ?`
|
||||
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.all(
|
||||
...data.whereStringValues,
|
||||
data.limit || apiConstants.DEFAULT_PAGE_LIMIT,
|
||||
data.page || apiConstants.DEFAULT_PAGE_INDEX,
|
||||
);
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.all(
|
||||
...data.whereStringValues,
|
||||
data.limit || apiConstants.DEFAULT_PAGE_LIMIT,
|
||||
data.page || apiConstants.DEFAULT_PAGE_INDEX
|
||||
)
|
||||
|
||||
return result;
|
||||
},
|
||||
return result
|
||||
},
|
||||
|
||||
getById(data) {
|
||||
const pks = data.pks.split(',');
|
||||
const placeholders = pks.map(() => '?').join(',');
|
||||
const query = `SELECT ${data.schemaString} FROM ${data.tableName} ${data.extendString} WHERE ${data.tableName}.${data.lookupField} in (${placeholders})`;
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.all(...pks);
|
||||
return result;
|
||||
},
|
||||
getById(data) {
|
||||
const pks = data.pks.split(',')
|
||||
const placeholders = pks.map(() => '?').join(',')
|
||||
const query = `SELECT ${data.schemaString} FROM ${data.tableName} ${data.extendString} WHERE ${data.tableName}.${data.lookupField} in (${placeholders})`
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.all(...pks)
|
||||
return result
|
||||
},
|
||||
|
||||
getCount(data) {
|
||||
const query = `SELECT COUNT(*) as total FROM ${data.tableName} ${data.whereString}`;
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.get(...data.whereStringValues).total;
|
||||
return result;
|
||||
},
|
||||
getCount(data) {
|
||||
const query = `SELECT COUNT(*) as total FROM ${data.tableName} ${data.whereString}`
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.get(...data.whereStringValues).total
|
||||
return result
|
||||
},
|
||||
|
||||
save(data) {
|
||||
// wrap text values in quotes
|
||||
const fieldsString = Object.keys(data.fields)
|
||||
.map((field) => `'${field}'`)
|
||||
.join(', ');
|
||||
save(data) {
|
||||
// wrap text values in quotes
|
||||
const fieldsString = Object.keys(data.fields)
|
||||
.map((field) => `'${field}'`)
|
||||
.join(', ')
|
||||
|
||||
// wrap text values in quotes
|
||||
const valuesString = Object.values(data.fields).map((value) => value);
|
||||
const placeholders = Object.values(data.fields)
|
||||
.map(() => '?')
|
||||
.join(',');
|
||||
// wrap text values in quotes
|
||||
const valuesString = Object.values(data.fields).map((value) => value)
|
||||
const placeholders = Object.values(data.fields)
|
||||
.map(() => '?')
|
||||
.join(',')
|
||||
|
||||
let values = `(${fieldsString}) VALUES (${placeholders})`;
|
||||
if (valuesString === '') {
|
||||
values = 'DEFAULT VALUES';
|
||||
}
|
||||
let values = `(${fieldsString}) VALUES (${placeholders})`
|
||||
if (valuesString === '') {
|
||||
values = 'DEFAULT VALUES'
|
||||
}
|
||||
|
||||
const query = `INSERT INTO ${data.tableName} ${values}`;
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.run(...valuesString);
|
||||
return result;
|
||||
},
|
||||
const query = `INSERT INTO ${data.tableName} ${values}`
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.run(...valuesString)
|
||||
return result
|
||||
},
|
||||
|
||||
bulkWrite(data) {
|
||||
const { tableName, fields } = data;
|
||||
const fieldNames = Object.keys(fields[0]);
|
||||
const valueSets = fields.map((row) => Object.values(row));
|
||||
bulkWrite(data) {
|
||||
const { tableName, fields } = data
|
||||
const fieldNames = Object.keys(fields[0])
|
||||
const valueSets = fields.map((row) => Object.values(row))
|
||||
|
||||
const placeholders = fieldNames.map(() => '?');
|
||||
const valuesString = valueSets
|
||||
.map(() => `(${placeholders.join(',')})`)
|
||||
.join(',');
|
||||
const placeholders = fieldNames.map(() => '?')
|
||||
const valuesString = valueSets.map(() => `(${placeholders.join(',')})`).join(',')
|
||||
|
||||
const query = `INSERT INTO ${tableName} (${fieldNames
|
||||
.map((field) => `'${field}'`)
|
||||
.join(', ')}) VALUES ${valuesString}`;
|
||||
const query = `INSERT INTO ${tableName} (${fieldNames
|
||||
.map((field) => `'${field}'`)
|
||||
.join(', ')}) VALUES ${valuesString}`
|
||||
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.run(...valueSets.flat());
|
||||
return result;
|
||||
},
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.run(...valueSets.flat())
|
||||
return result
|
||||
},
|
||||
|
||||
update(data) {
|
||||
const pks = data.pks.split(',');
|
||||
const placeholders = pks.map(() => '?').join(',');
|
||||
const query = `UPDATE ${data.tableName} SET ${data.fieldsString} WHERE ${data.lookupField} in (${placeholders})`;
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.run(...pks);
|
||||
return result;
|
||||
},
|
||||
update(data) {
|
||||
const pks = data.pks.split(',')
|
||||
const placeholders = pks.map(() => '?').join(',')
|
||||
const query = `UPDATE ${data.tableName} SET ${data.fieldsString} WHERE ${data.lookupField} in (${placeholders})`
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.run(...pks)
|
||||
return result
|
||||
},
|
||||
|
||||
delete(data) {
|
||||
const pks = data.pks.split(',');
|
||||
const placeholders = pks.map(() => '?').join(',');
|
||||
const query = `DELETE FROM ${data.tableName} WHERE ${data.lookupField} in (${placeholders})`;
|
||||
const statement = db.prepare(query);
|
||||
const result = statement.run(...pks);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
delete(data) {
|
||||
const pks = data.pks.split(',')
|
||||
const placeholders = pks.map(() => '?').join(',')
|
||||
const query = `DELETE FROM ${data.tableName} WHERE ${data.lookupField} in (${placeholders})`
|
||||
const statement = db.prepare(query)
|
||||
const result = statement.run(...pks)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +1,123 @@
|
||||
module.exports = (db) => {
|
||||
return {
|
||||
createTable(tableName, schema, options = {}) {
|
||||
const {
|
||||
autoAddCreatedAt = true,
|
||||
autoAddUpdatedAt = true,
|
||||
multipleUniqueConstraints,
|
||||
} = options;
|
||||
return {
|
||||
createTable(tableName, schema, options = {}) {
|
||||
const { autoAddCreatedAt = true, autoAddUpdatedAt = true, multipleUniqueConstraints } = options
|
||||
|
||||
let indices = [];
|
||||
let indices = []
|
||||
|
||||
let schemaString = schema
|
||||
.map(({ name, type, notNull, unique, primaryKey, foreignKey }) => {
|
||||
let column = `'${name}' '${type}'`;
|
||||
let schemaString = schema
|
||||
.map(({ name, type, notNull, unique, primaryKey, foreignKey }) => {
|
||||
let column = `'${name}' '${type}'`
|
||||
|
||||
if (notNull) {
|
||||
column += ' NOT NULL';
|
||||
}
|
||||
if (unique) {
|
||||
column += ' UNIQUE';
|
||||
}
|
||||
if (primaryKey) {
|
||||
column += ' PRIMARY KEY';
|
||||
}
|
||||
if (foreignKey) {
|
||||
column += ` REFERENCES ${foreignKey.table}(${foreignKey.column})`;
|
||||
}
|
||||
if (foreignKey && foreignKey.onDelete) {
|
||||
column += ` ON DELETE ${foreignKey.onDelete}`;
|
||||
}
|
||||
if (foreignKey && foreignKey.onUpdate) {
|
||||
column += ` ON UPDATE ${foreignKey.onUpdate}`;
|
||||
}
|
||||
if (notNull) {
|
||||
column += ' NOT NULL'
|
||||
}
|
||||
if (unique) {
|
||||
column += ' UNIQUE'
|
||||
}
|
||||
if (primaryKey) {
|
||||
column += ' PRIMARY KEY'
|
||||
}
|
||||
if (foreignKey) {
|
||||
column += ` REFERENCES ${foreignKey.table}(${foreignKey.column})`
|
||||
}
|
||||
if (foreignKey && foreignKey.onDelete) {
|
||||
column += ` ON DELETE ${foreignKey.onDelete}`
|
||||
}
|
||||
if (foreignKey && foreignKey.onUpdate) {
|
||||
column += ` ON UPDATE ${foreignKey.onUpdate}`
|
||||
}
|
||||
|
||||
return column;
|
||||
})
|
||||
.join(', ');
|
||||
return column
|
||||
})
|
||||
.join(', ')
|
||||
|
||||
// add id if primary key is not defined
|
||||
if (!schema.find((field) => field.primaryKey)) {
|
||||
schemaString = `
|
||||
// add id if primary key is not defined
|
||||
if (!schema.find((field) => field.primaryKey)) {
|
||||
schemaString = `
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
${schemaString}
|
||||
`;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
// add created at and updated at
|
||||
if (autoAddCreatedAt) {
|
||||
schemaString = `${schemaString}, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP`;
|
||||
}
|
||||
// add created at and updated at
|
||||
if (autoAddCreatedAt) {
|
||||
schemaString = `${schemaString}, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP`
|
||||
}
|
||||
|
||||
if (autoAddUpdatedAt) {
|
||||
schemaString = `${schemaString}, updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP`;
|
||||
}
|
||||
if (autoAddUpdatedAt) {
|
||||
schemaString = `${schemaString}, updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP`
|
||||
}
|
||||
|
||||
if (multipleUniqueConstraints) {
|
||||
schemaString = `${schemaString}, CONSTRAINT ${
|
||||
multipleUniqueConstraints.name
|
||||
} UNIQUE (${multipleUniqueConstraints.fields
|
||||
.map((field) => field)
|
||||
.join(' ,')})`;
|
||||
}
|
||||
if (multipleUniqueConstraints) {
|
||||
schemaString = `${schemaString}, CONSTRAINT ${
|
||||
multipleUniqueConstraints.name
|
||||
} UNIQUE (${multipleUniqueConstraints.fields.map((field) => field).join(' ,')})`
|
||||
}
|
||||
|
||||
let indicesString = indices
|
||||
.map((field) => {
|
||||
return `
|
||||
let indicesString = indices
|
||||
.map((field) => {
|
||||
return `
|
||||
CREATE INDEX ${tableName}_${field}_index
|
||||
ON ${tableName} (${field})
|
||||
`;
|
||||
})
|
||||
.join(';');
|
||||
`
|
||||
})
|
||||
.join(';')
|
||||
|
||||
const query = `CREATE TABLE ${tableName} (${schemaString})`;
|
||||
const query = `CREATE TABLE ${tableName} (${schemaString})`
|
||||
|
||||
try {
|
||||
db.prepare(query).run();
|
||||
try {
|
||||
db.prepare(query).run()
|
||||
|
||||
if (indicesString) {
|
||||
db.prepare(indicesString).run();
|
||||
}
|
||||
if (indicesString) {
|
||||
db.prepare(indicesString).run()
|
||||
}
|
||||
|
||||
db.prepare(`PRAGMA table_info(${tableName})`).all();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
db.prepare(`PRAGMA table_info(${tableName})`).all()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
listTables(options = {}) {
|
||||
const { search, ordering, exclude } = options;
|
||||
listTables(options = {}) {
|
||||
const { search, ordering, exclude } = options
|
||||
|
||||
let query = `SELECT name FROM sqlite_master WHERE type IN ('table', 'view')`;
|
||||
let query = `SELECT name FROM sqlite_master WHERE type IN ('table', 'view')`
|
||||
|
||||
// if search is provided, search the tables
|
||||
// e.g. search=users
|
||||
if (search) {
|
||||
query += ` AND name LIKE $searchQuery`;
|
||||
}
|
||||
// if search is provided, search the tables
|
||||
// e.g. search=users
|
||||
if (search) {
|
||||
query += ` AND name LIKE $searchQuery`
|
||||
}
|
||||
|
||||
// if exclude is passed don't return the some tables
|
||||
// e.g. exclude=['_users', '_roles']
|
||||
if (exclude) {
|
||||
const excludeTables = exclude.map((field) => `'${field}'`).join(' ,');
|
||||
query += `AND name NOT IN (${excludeTables});`;
|
||||
}
|
||||
// if exclude is passed don't return the some tables
|
||||
// e.g. exclude=['_users', '_roles']
|
||||
if (exclude) {
|
||||
const excludeTables = exclude.map((field) => `'${field}'`).join(' ,')
|
||||
query += `AND name NOT IN (${excludeTables});`
|
||||
}
|
||||
|
||||
// if ordering is provided, order the tables
|
||||
// e.g. ordering=name (ascending) or ?_ordering=-name (descending)
|
||||
if (ordering) {
|
||||
query += ` ORDER BY $ordering`;
|
||||
}
|
||||
// if ordering is provided, order the tables
|
||||
// e.g. ordering=name (ascending) or ?_ordering=-name (descending)
|
||||
if (ordering) {
|
||||
query += ` ORDER BY $ordering`
|
||||
}
|
||||
|
||||
try {
|
||||
const tables = db.prepare(query).all({
|
||||
searchQuery: `%${search}%`,
|
||||
ordering: `${ordering?.replace('-', '')} ${
|
||||
ordering?.startsWith('-') ? 'DESC' : 'ASC'
|
||||
}`,
|
||||
});
|
||||
return tables;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
try {
|
||||
const tables = db.prepare(query).all({
|
||||
searchQuery: `%${search}%`,
|
||||
ordering: `${ordering?.replace('-', '')} ${ordering?.startsWith('-') ? 'DESC' : 'ASC'}`
|
||||
})
|
||||
return tables
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
checkTableExists(tableName) {
|
||||
const query = `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`;
|
||||
const result = db.prepare(query).get();
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
checkTableExists(tableName) {
|
||||
const query = `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`
|
||||
const result = db.prepare(query).get()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,200 +1,195 @@
|
||||
const swaggerAutogen = require('swagger-autogen')();
|
||||
const swaggerAutogen = require('swagger-autogen')()
|
||||
|
||||
const config = require('../config/index');
|
||||
const version = require('../../package.json').version;
|
||||
const config = require('../config/index')
|
||||
const version = require('../../package.json').version
|
||||
|
||||
const outputFile = './swagger.json';
|
||||
const endpointsFiles = ['../index.js'];
|
||||
const outputFile = './swagger.json'
|
||||
const endpointsFiles = ['../index.js']
|
||||
|
||||
const doc = {
|
||||
info: {
|
||||
version: version,
|
||||
title: 'Soul API',
|
||||
description:
|
||||
'API Documentation for <b>Soul</b>, a SQLite REST and realtime server. ',
|
||||
},
|
||||
host: `localhost:${config.port}`,
|
||||
basePath: '/',
|
||||
schemes: ['http', 'https'],
|
||||
consumes: ['application/json'],
|
||||
produces: ['application/json'],
|
||||
tags: [
|
||||
{
|
||||
name: 'Root',
|
||||
description: 'Root endpoints',
|
||||
},
|
||||
{
|
||||
name: 'Tables',
|
||||
description: 'Tables endpoints',
|
||||
},
|
||||
{
|
||||
name: 'Rows',
|
||||
description: 'Rows endpoints',
|
||||
},
|
||||
{
|
||||
name: 'Auth',
|
||||
description: 'Auth endpoints',
|
||||
},
|
||||
],
|
||||
securityDefinitions: {},
|
||||
definitions: {
|
||||
Table: {
|
||||
name: 'users',
|
||||
},
|
||||
Row: {},
|
||||
Query: {
|
||||
query: 'SELECT * FROM users',
|
||||
},
|
||||
Statement: {
|
||||
statement:
|
||||
'INSERT INTO users (id, firstName, lastName) VALUES (:id, :firstName, :lastName)',
|
||||
values: { id: 1, firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
Transaction: {
|
||||
transaction: [
|
||||
{ $ref: '#/definitions/Query' },
|
||||
{ $ref: '#/definitions/Statement' },
|
||||
],
|
||||
},
|
||||
ForeignKey: {
|
||||
table: 'users',
|
||||
column: 'id',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
Field: {
|
||||
name: 'user_id',
|
||||
type: 'INTEGER',
|
||||
default: 1,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
primaryKey: false,
|
||||
foreignKey: { $ref: '#/definitions/ForeignKey' },
|
||||
index: false,
|
||||
},
|
||||
Schema: [{ $ref: '#/definitions/Field' }],
|
||||
info: {
|
||||
version: version,
|
||||
title: 'Soul API',
|
||||
description: 'API Documentation for <b>Soul</b>, a SQLite REST and realtime server. '
|
||||
},
|
||||
host: `localhost:${config.port}`,
|
||||
basePath: '/',
|
||||
schemes: ['http', 'https'],
|
||||
consumes: ['application/json'],
|
||||
produces: ['application/json'],
|
||||
tags: [
|
||||
{
|
||||
name: 'Root',
|
||||
description: 'Root endpoints'
|
||||
},
|
||||
{
|
||||
name: 'Tables',
|
||||
description: 'Tables endpoints'
|
||||
},
|
||||
{
|
||||
name: 'Rows',
|
||||
description: 'Rows endpoints'
|
||||
},
|
||||
{
|
||||
name: 'Auth',
|
||||
description: 'Auth endpoints'
|
||||
}
|
||||
],
|
||||
securityDefinitions: {},
|
||||
definitions: {
|
||||
Table: {
|
||||
name: 'users'
|
||||
},
|
||||
Row: {},
|
||||
Query: {
|
||||
query: 'SELECT * FROM users'
|
||||
},
|
||||
Statement: {
|
||||
statement: 'INSERT INTO users (id, firstName, lastName) VALUES (:id, :firstName, :lastName)',
|
||||
values: { id: 1, firstName: 'John', lastName: 'Doe' }
|
||||
},
|
||||
Transaction: {
|
||||
transaction: [{ $ref: '#/definitions/Query' }, { $ref: '#/definitions/Statement' }]
|
||||
},
|
||||
ForeignKey: {
|
||||
table: 'users',
|
||||
column: 'id',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
Field: {
|
||||
name: 'user_id',
|
||||
type: 'INTEGER',
|
||||
default: 1,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
primaryKey: false,
|
||||
foreignKey: { $ref: '#/definitions/ForeignKey' },
|
||||
index: false
|
||||
},
|
||||
Schema: [{ $ref: '#/definitions/Field' }],
|
||||
|
||||
CreateTableRequestBody: {
|
||||
name: 'users',
|
||||
schema: { $ref: '#/definitions/Schema' },
|
||||
autoAddCreatedAt: true,
|
||||
autoAddUpdatedAt: true,
|
||||
},
|
||||
CreateTableSuccessResponse: {
|
||||
message: 'Table created',
|
||||
data: {
|
||||
name: 'users',
|
||||
fields: [{ $ref: '#/definitions/Field' }],
|
||||
},
|
||||
},
|
||||
CreateTableErrorResponse: {
|
||||
message: 'Table not created',
|
||||
error: 'already_exists',
|
||||
data: {},
|
||||
},
|
||||
CreateTableRequestBody: {
|
||||
name: 'users',
|
||||
schema: { $ref: '#/definitions/Schema' },
|
||||
autoAddCreatedAt: true,
|
||||
autoAddUpdatedAt: true
|
||||
},
|
||||
CreateTableSuccessResponse: {
|
||||
message: 'Table created',
|
||||
data: {
|
||||
name: 'users',
|
||||
fields: [{ $ref: '#/definitions/Field' }]
|
||||
}
|
||||
},
|
||||
CreateTableErrorResponse: {
|
||||
message: 'Table not created',
|
||||
error: 'already_exists',
|
||||
data: {}
|
||||
},
|
||||
|
||||
InsertRowRequestBody: {
|
||||
$ref: '#/definitions/Row',
|
||||
},
|
||||
InsertRowSuccessResponse: {
|
||||
message: 'Row inserted',
|
||||
data: {
|
||||
id: 1,
|
||||
createdAt: '2022-10-10 10:55:29',
|
||||
updatedAt: '2022-10-10 10:55:29',
|
||||
firstName: 'John',
|
||||
},
|
||||
},
|
||||
InsertRowErrorResponse: {
|
||||
message: 'Row not inserted',
|
||||
error: 'not_found',
|
||||
},
|
||||
InsertRowRequestBody: {
|
||||
$ref: '#/definitions/Row'
|
||||
},
|
||||
InsertRowSuccessResponse: {
|
||||
message: 'Row inserted',
|
||||
data: {
|
||||
id: 1,
|
||||
createdAt: '2022-10-10 10:55:29',
|
||||
updatedAt: '2022-10-10 10:55:29',
|
||||
firstName: 'John'
|
||||
}
|
||||
},
|
||||
InsertRowErrorResponse: {
|
||||
message: 'Row not inserted',
|
||||
error: 'not_found'
|
||||
},
|
||||
|
||||
UpdateRowRequestBody: {
|
||||
fields: [{ $ref: '#/definitions/Field' }],
|
||||
},
|
||||
UpdateRowRequestBody: {
|
||||
fields: [{ $ref: '#/definitions/Field' }]
|
||||
},
|
||||
|
||||
BulkUpdateRowsRequestBody: {
|
||||
pks: [1, 2, 3],
|
||||
fields: [{ $ref: '#/definitions/Field' }],
|
||||
},
|
||||
BulkUpdateRowsRequestBody: {
|
||||
pks: [1, 2, 3],
|
||||
fields: [{ $ref: '#/definitions/Field' }]
|
||||
},
|
||||
|
||||
BulkDeleteRowsRequestBody: {
|
||||
pks: [1, 2, 3],
|
||||
},
|
||||
BulkDeleteRowsRequestBody: {
|
||||
pks: [1, 2, 3]
|
||||
},
|
||||
|
||||
TransactionRequestBody: {
|
||||
$ref: '#/definitions/Transaction',
|
||||
},
|
||||
ObtainAccessTokenRequestBody: {
|
||||
fields: {
|
||||
username: '@john',
|
||||
password: 'Ak22#cPM33@v*#',
|
||||
},
|
||||
},
|
||||
TransactionRequestBody: {
|
||||
$ref: '#/definitions/Transaction'
|
||||
},
|
||||
ObtainAccessTokenRequestBody: {
|
||||
fields: {
|
||||
username: '@john',
|
||||
password: 'Ak22#cPM33@v*#'
|
||||
}
|
||||
},
|
||||
|
||||
ObtainAccessTokenSuccessResponse: {
|
||||
message: 'Success',
|
||||
data: {
|
||||
userId: 1,
|
||||
},
|
||||
},
|
||||
ObtainAccessTokenSuccessResponse: {
|
||||
message: 'Success',
|
||||
data: {
|
||||
userId: 1
|
||||
}
|
||||
},
|
||||
|
||||
InvalidCredentialErrorResponse: {
|
||||
message: 'Invalid username or password',
|
||||
},
|
||||
InvalidCredentialErrorResponse: {
|
||||
message: 'Invalid username or password'
|
||||
},
|
||||
|
||||
UserRegisterationRequestBody: {
|
||||
fields: {
|
||||
username: '@john',
|
||||
password: 'Ak22#cPM33@v*#',
|
||||
},
|
||||
},
|
||||
UserRegisterationRequestBody: {
|
||||
fields: {
|
||||
username: '@john',
|
||||
password: 'Ak22#cPM33@v*#'
|
||||
}
|
||||
},
|
||||
|
||||
WeakPasswordErrorResponse: {
|
||||
message:
|
||||
'This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters',
|
||||
},
|
||||
WeakPasswordErrorResponse: {
|
||||
message:
|
||||
'This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters'
|
||||
},
|
||||
|
||||
UsernameTakenErrorResponse: {
|
||||
message: 'This username is taken',
|
||||
},
|
||||
UsernameTakenErrorResponse: {
|
||||
message: 'This username is taken'
|
||||
},
|
||||
|
||||
DefaultRoleNotCreatedErrorResponse: {
|
||||
message: 'Please restart soul so a default role can be created',
|
||||
},
|
||||
DefaultRoleNotCreatedErrorResponse: {
|
||||
message: 'Please restart soul so a default role can be created'
|
||||
},
|
||||
|
||||
UserNotFoundErrorResponse: {
|
||||
message: 'User not found',
|
||||
},
|
||||
UserNotFoundErrorResponse: {
|
||||
message: 'User not found'
|
||||
},
|
||||
|
||||
InvalidRefreshTokenErrorResponse: {
|
||||
message: 'Invalid refresh token',
|
||||
},
|
||||
InvalidRefreshTokenErrorResponse: {
|
||||
message: 'Invalid refresh token'
|
||||
},
|
||||
|
||||
ChangePasswordRequestBody: {
|
||||
fields: {
|
||||
currentPassword: 'Ak22#cPM33@v*#',
|
||||
newPassword: 'hKB33o@3245CD$',
|
||||
},
|
||||
},
|
||||
ChangePasswordRequestBody: {
|
||||
fields: {
|
||||
currentPassword: 'Ak22#cPM33@v*#',
|
||||
newPassword: 'hKB33o@3245CD$'
|
||||
}
|
||||
},
|
||||
|
||||
ChangePasswordSuccessResponse: {
|
||||
message: 'Password updated successfully',
|
||||
data: { id: 1, username: '@john' },
|
||||
},
|
||||
ChangePasswordSuccessResponse: {
|
||||
message: 'Password updated successfully',
|
||||
data: { id: 1, username: '@john' }
|
||||
},
|
||||
|
||||
RefreshAccessTokenSuccessResponse: {
|
||||
message: 'Success',
|
||||
data: { userId: 1 },
|
||||
},
|
||||
RefreshAccessTokenSuccessResponse: {
|
||||
message: 'Success',
|
||||
data: { userId: 1 }
|
||||
},
|
||||
|
||||
InvalidPasswordErrorResponse: { message: 'Invalid password' },
|
||||
InvalidPasswordErrorResponse: { message: 'Invalid password' },
|
||||
|
||||
RemoveTokensResponse: {
|
||||
message: 'Logout successful',
|
||||
},
|
||||
},
|
||||
};
|
||||
RemoveTokensResponse: {
|
||||
message: 'Logout successful'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
swaggerAutogen(outputFile, endpointsFiles, doc);
|
||||
swaggerAutogen(outputFile, endpointsFiles, doc)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
const { createTestTable, insertIntoTestTable } = require('.');
|
||||
const { createTestTable, insertIntoTestTable } = require('.')
|
||||
|
||||
const setup = () => {
|
||||
console.log('Test suite started');
|
||||
console.log('Creating test table...');
|
||||
createTestTable();
|
||||
console.log('Inserting a row into test table...');
|
||||
insertIntoTestTable();
|
||||
};
|
||||
console.log('Test suite started')
|
||||
console.log('Creating test table...')
|
||||
createTestTable()
|
||||
console.log('Inserting a row into test table...')
|
||||
insertIntoTestTable()
|
||||
}
|
||||
|
||||
module.exports = setup;
|
||||
module.exports = setup
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const { dropTestDatabase } = require('.');
|
||||
const { dropTestDatabase } = require('.')
|
||||
|
||||
const globalTearDown = () => {
|
||||
console.log('Test suite finished');
|
||||
console.log('Dropping test database...');
|
||||
dropTestDatabase();
|
||||
};
|
||||
console.log('Test suite finished')
|
||||
console.log('Dropping test database...')
|
||||
dropTestDatabase()
|
||||
}
|
||||
|
||||
module.exports = globalTearDown;
|
||||
module.exports = globalTearDown
|
||||
|
||||
@@ -1,48 +1,46 @@
|
||||
const fs = require('fs');
|
||||
const { unlink } = require('fs/promises');
|
||||
const db = require('../db/index');
|
||||
const { testNames } = require('./testData');
|
||||
const fs = require('fs')
|
||||
const { unlink } = require('fs/promises')
|
||||
const db = require('../db/index')
|
||||
const { testNames } = require('./testData')
|
||||
|
||||
const dropTestTable = (table = 'users') => {
|
||||
db.prepare(`DROP TABLE IF EXISTS ${table}`).run();
|
||||
};
|
||||
db.prepare(`DROP TABLE IF EXISTS ${table}`).run()
|
||||
}
|
||||
|
||||
const dropTestDatabase = async (path = 'test.db') => {
|
||||
// delete test database file e.g. test.db
|
||||
try {
|
||||
await unlink(path), console.log(`successfully deleted ${path}`);
|
||||
} catch (error) {
|
||||
console.error('there was an error:', error);
|
||||
}
|
||||
// delete test database file e.g. test.db
|
||||
try {
|
||||
await unlink(path), console.log(`successfully deleted ${path}`)
|
||||
} catch (error) {
|
||||
console.error('there was an error:', error)
|
||||
}
|
||||
|
||||
if (fs.existsSync(path + '-wal')) {
|
||||
try {
|
||||
await Promise.allSettled([unlink(path + '-wal'), unlink(path + '-shm')]);
|
||||
} catch (error) {
|
||||
console.error('there was an error:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (fs.existsSync(path + '-wal')) {
|
||||
try {
|
||||
await Promise.allSettled([unlink(path + '-wal'), unlink(path + '-shm')])
|
||||
} catch (error) {
|
||||
console.error('there was an error:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createTestTable = (table = 'users') => {
|
||||
db.prepare(
|
||||
`CREATE TABLE ${table} (id INTEGER PRIMARY KEY, firstName TEXT, lastName TEXT, email TEXT, username TEXT, createdAt TEXT)`,
|
||||
).run();
|
||||
};
|
||||
db.prepare(
|
||||
`CREATE TABLE ${table} (id INTEGER PRIMARY KEY, firstName TEXT, lastName TEXT, email TEXT, username TEXT, createdAt TEXT)`
|
||||
).run()
|
||||
}
|
||||
|
||||
const insertIntoTestTable = (table = 'users') => {
|
||||
const statement = db.prepare(
|
||||
`INSERT INTO ${table} (firstName, lastName, createdAt) VALUES (?, ?, ?)`,
|
||||
);
|
||||
const statement = db.prepare(`INSERT INTO ${table} (firstName, lastName, createdAt) VALUES (?, ?, ?)`)
|
||||
|
||||
for (const user of testNames) {
|
||||
statement.run(user.firstName, user.lastName, user.createdAt);
|
||||
}
|
||||
};
|
||||
for (const user of testNames) {
|
||||
statement.run(user.firstName, user.lastName, user.createdAt)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dropTestTable,
|
||||
dropTestDatabase,
|
||||
createTestTable,
|
||||
insertIntoTestTable,
|
||||
};
|
||||
dropTestTable,
|
||||
dropTestDatabase,
|
||||
createTestTable,
|
||||
insertIntoTestTable
|
||||
}
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
const testNames = [
|
||||
{ firstName: 'Emily', lastName: 'William', createdAt: '2008-01-08 00:00:00' },
|
||||
{ firstName: 'Michael', lastName: 'Lee', createdAt: '2009-01-08 00:00:00' },
|
||||
{ firstName: 'Sarah', lastName: 'Johnson', createdAt: '2010-01-08 00:00:00' },
|
||||
{ firstName: 'David', lastName: 'Chen', createdAt: '2011-01-08 00:00:00' },
|
||||
{
|
||||
firstName: 'Olivia',
|
||||
lastName: 'William',
|
||||
createdAt: '2012-01-08 00:00:00',
|
||||
},
|
||||
{ firstName: 'William', lastName: 'Kim', createdAt: '2013-01-08 00:00:00' },
|
||||
{ firstName: 'Sophia', lastName: 'Singh', createdAt: '2013-02-08 00:00:00' },
|
||||
{
|
||||
firstName: 'James',
|
||||
lastName: 'Rodriguez',
|
||||
createdAt: '2013-03-08 00:00:00',
|
||||
},
|
||||
{ firstName: 'Ava', lastName: 'Patel', createdAt: '2013-01-04 00:00:00' },
|
||||
{
|
||||
firstName: 'Benjamin',
|
||||
lastName: 'Garcia',
|
||||
createdAt: '2015-01-08 00:00:00',
|
||||
},
|
||||
{
|
||||
firstName: 'Isabella',
|
||||
lastName: 'Nguyen',
|
||||
createdAt: '2014-01-08 00:00:00',
|
||||
},
|
||||
{ firstName: 'Ethan', lastName: 'Lee', createdAt: '2016-01-08 00:00:00' },
|
||||
{ firstName: 'Mia', lastName: 'Wilson', createdAt: '2017-01-08 00:00:00' },
|
||||
{
|
||||
firstName: 'Alexander',
|
||||
lastName: 'William',
|
||||
createdAt: '2018-01-08 00:00:00',
|
||||
},
|
||||
{
|
||||
firstName: 'Charlotte',
|
||||
lastName: 'Hernandez',
|
||||
createdAt: '2019-01-08 00:00:00',
|
||||
},
|
||||
{ firstName: 'Liam', lastName: 'Gonzalez', createdAt: '2020-01-08 00:00:00' },
|
||||
{ firstName: 'Emma', lastName: 'Gomez', createdAt: '2021-01-08 00:00:00' },
|
||||
{ firstName: 'Noah', lastName: 'Perez', createdAt: '2021-01-08 00:00:00' },
|
||||
{ firstName: 'Avery', lastName: 'Ramirez', createdAt: '2023-02-08 00:00:00' },
|
||||
{ firstName: 'Jacob', lastName: 'Turner', createdAt: '2023-03-08 00:00:00' },
|
||||
{
|
||||
firstName: 'Abigail',
|
||||
lastName: 'Williams',
|
||||
createdAt: '2023-02-10 00:00:00',
|
||||
},
|
||||
{ firstName: 'Elijah', lastName: 'Hall', createdAt: '2023-04-02 00:00:00' },
|
||||
{ firstName: 'Mila', lastName: 'Flores', createdAt: '2023-05-13 00:00:00' },
|
||||
{
|
||||
firstName: 'Evelyn',
|
||||
lastName: 'Morales',
|
||||
createdAt: '2023-06-05 00:00:00',
|
||||
},
|
||||
{ firstName: 'Logan', lastName: 'Collins', createdAt: '2023-06-07 00:00:00' },
|
||||
{ firstName: null, lastName: 'Flores', createdAt: '2023-06-09 00:00:00' },
|
||||
];
|
||||
{ firstName: 'Emily', lastName: 'William', createdAt: '2008-01-08 00:00:00' },
|
||||
{ firstName: 'Michael', lastName: 'Lee', createdAt: '2009-01-08 00:00:00' },
|
||||
{ firstName: 'Sarah', lastName: 'Johnson', createdAt: '2010-01-08 00:00:00' },
|
||||
{ firstName: 'David', lastName: 'Chen', createdAt: '2011-01-08 00:00:00' },
|
||||
{
|
||||
firstName: 'Olivia',
|
||||
lastName: 'William',
|
||||
createdAt: '2012-01-08 00:00:00'
|
||||
},
|
||||
{ firstName: 'William', lastName: 'Kim', createdAt: '2013-01-08 00:00:00' },
|
||||
{ firstName: 'Sophia', lastName: 'Singh', createdAt: '2013-02-08 00:00:00' },
|
||||
{
|
||||
firstName: 'James',
|
||||
lastName: 'Rodriguez',
|
||||
createdAt: '2013-03-08 00:00:00'
|
||||
},
|
||||
{ firstName: 'Ava', lastName: 'Patel', createdAt: '2013-01-04 00:00:00' },
|
||||
{
|
||||
firstName: 'Benjamin',
|
||||
lastName: 'Garcia',
|
||||
createdAt: '2015-01-08 00:00:00'
|
||||
},
|
||||
{
|
||||
firstName: 'Isabella',
|
||||
lastName: 'Nguyen',
|
||||
createdAt: '2014-01-08 00:00:00'
|
||||
},
|
||||
{ firstName: 'Ethan', lastName: 'Lee', createdAt: '2016-01-08 00:00:00' },
|
||||
{ firstName: 'Mia', lastName: 'Wilson', createdAt: '2017-01-08 00:00:00' },
|
||||
{
|
||||
firstName: 'Alexander',
|
||||
lastName: 'William',
|
||||
createdAt: '2018-01-08 00:00:00'
|
||||
},
|
||||
{
|
||||
firstName: 'Charlotte',
|
||||
lastName: 'Hernandez',
|
||||
createdAt: '2019-01-08 00:00:00'
|
||||
},
|
||||
{ firstName: 'Liam', lastName: 'Gonzalez', createdAt: '2020-01-08 00:00:00' },
|
||||
{ firstName: 'Emma', lastName: 'Gomez', createdAt: '2021-01-08 00:00:00' },
|
||||
{ firstName: 'Noah', lastName: 'Perez', createdAt: '2021-01-08 00:00:00' },
|
||||
{ firstName: 'Avery', lastName: 'Ramirez', createdAt: '2023-02-08 00:00:00' },
|
||||
{ firstName: 'Jacob', lastName: 'Turner', createdAt: '2023-03-08 00:00:00' },
|
||||
{
|
||||
firstName: 'Abigail',
|
||||
lastName: 'Williams',
|
||||
createdAt: '2023-02-10 00:00:00'
|
||||
},
|
||||
{ firstName: 'Elijah', lastName: 'Hall', createdAt: '2023-04-02 00:00:00' },
|
||||
{ firstName: 'Mila', lastName: 'Flores', createdAt: '2023-05-13 00:00:00' },
|
||||
{
|
||||
firstName: 'Evelyn',
|
||||
lastName: 'Morales',
|
||||
createdAt: '2023-06-05 00:00:00'
|
||||
},
|
||||
{ firstName: 'Logan', lastName: 'Collins', createdAt: '2023-06-07 00:00:00' },
|
||||
{ firstName: null, lastName: 'Flores', createdAt: '2023-06-09 00:00:00' }
|
||||
]
|
||||
|
||||
const testData = {
|
||||
strongPassword: 'HeK34#C44DMJ',
|
||||
strongPassword2: 'Mk22#c9@Cv!K',
|
||||
weakPassword: '12345678',
|
||||
invalidUsername: 'invalid_username',
|
||||
invalidPassword: 'invalid_password',
|
||||
users: {
|
||||
user1: { username: 'Jane' },
|
||||
user2: { username: 'Mike' },
|
||||
user3: { username: 'John' },
|
||||
},
|
||||
};
|
||||
strongPassword: 'HeK34#C44DMJ',
|
||||
strongPassword2: 'Mk22#c9@Cv!K',
|
||||
weakPassword: '12345678',
|
||||
invalidUsername: 'invalid_username',
|
||||
invalidPassword: 'invalid_password',
|
||||
users: {
|
||||
user1: { username: 'Jane' },
|
||||
user2: { username: 'Mike' },
|
||||
user3: { username: 'John' }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { testNames, testData };
|
||||
module.exports = { testNames, testData }
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcrypt')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const { passwordStrength } = require('check-password-strength');
|
||||
const { passwordStrength } = require('check-password-strength')
|
||||
|
||||
const hashPassword = async (password, saltRounds) => {
|
||||
const salt = await bcrypt.genSalt(saltRounds);
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
return { hashedPassword, salt };
|
||||
};
|
||||
const salt = await bcrypt.genSalt(saltRounds)
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds)
|
||||
return { hashedPassword, salt }
|
||||
}
|
||||
|
||||
const comparePasswords = async (plainPassword, hashedPassword) => {
|
||||
const isMatch = await bcrypt.compare(plainPassword, hashedPassword);
|
||||
return isMatch;
|
||||
};
|
||||
const isMatch = await bcrypt.compare(plainPassword, hashedPassword)
|
||||
return isMatch
|
||||
}
|
||||
|
||||
const checkPasswordStrength = (password) => {
|
||||
const value = passwordStrength(password).value;
|
||||
return value;
|
||||
};
|
||||
const value = passwordStrength(password).value
|
||||
return value
|
||||
}
|
||||
|
||||
const generateToken = async (payload, secret, expiresIn) => {
|
||||
return jwt.sign(payload, secret, { expiresIn });
|
||||
};
|
||||
return jwt.sign(payload, secret, { expiresIn })
|
||||
}
|
||||
|
||||
const decodeToken = async (token, secret) => {
|
||||
try {
|
||||
const decoded = jwt.verify(token, secret);
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
throw new Error('Invalid token');
|
||||
}
|
||||
};
|
||||
try {
|
||||
const decoded = jwt.verify(token, secret)
|
||||
return decoded
|
||||
} catch (error) {
|
||||
throw new Error('Invalid token')
|
||||
}
|
||||
}
|
||||
|
||||
const toBoolean = (value) => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
const lowerCaseValue = value.toLowerCase();
|
||||
if (lowerCaseValue === 'true') {
|
||||
return true;
|
||||
} else if (lowerCaseValue === 'false') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const lowerCaseValue = value.toLowerCase()
|
||||
if (lowerCaseValue === 'true') {
|
||||
return true
|
||||
} else if (lowerCaseValue === 'false') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
if (value === 1) {
|
||||
return true;
|
||||
} else if (value === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
if (value === 1) {
|
||||
return true
|
||||
} else if (value === 0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Invalid value. Cannot convert to boolean.');
|
||||
};
|
||||
throw new Error('Invalid value. Cannot convert to boolean.')
|
||||
}
|
||||
|
||||
const removeFields = async (rows, fields) => {
|
||||
const newPayload = rows.map((row) => {
|
||||
fields.map((field) => {
|
||||
delete row[field];
|
||||
});
|
||||
});
|
||||
const newPayload = rows.map((row) => {
|
||||
fields.map((field) => {
|
||||
delete row[field]
|
||||
})
|
||||
})
|
||||
|
||||
return newPayload;
|
||||
};
|
||||
return newPayload
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hashPassword,
|
||||
comparePasswords,
|
||||
checkPasswordStrength,
|
||||
generateToken,
|
||||
decodeToken,
|
||||
toBoolean,
|
||||
removeFields,
|
||||
};
|
||||
hashPassword,
|
||||
comparePasswords,
|
||||
checkPasswordStrength,
|
||||
generateToken,
|
||||
decodeToken,
|
||||
toBoolean,
|
||||
removeFields
|
||||
}
|
||||
|
||||
104
src/websocket.js
104
src/websocket.js
@@ -1,70 +1,70 @@
|
||||
const { WebSocketServer } = require('ws');
|
||||
const { WebSocketServer } = require('ws')
|
||||
|
||||
const db = require('./db/index');
|
||||
const db = require('./db/index')
|
||||
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
const wss = new WebSocketServer({ noServer: true })
|
||||
|
||||
const websocketSubscribers = new Map();
|
||||
const websocketSubscribers = new Map()
|
||||
|
||||
wss.on('connection', function (ws, request) {
|
||||
const [_path, params] = request?.url?.split('?');
|
||||
const [_path, params] = request?.url?.split('?')
|
||||
|
||||
if (!_path.startsWith('/ws')) {
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
if (!_path.startsWith('/ws')) {
|
||||
ws.close()
|
||||
return
|
||||
}
|
||||
|
||||
const tableName = _path.replace('/ws/tables/', '').replace('/', '');
|
||||
const tableName = _path.replace('/ws/tables/', '').replace('/', '')
|
||||
|
||||
// if table does not exists close the connection
|
||||
const query = `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`;
|
||||
try {
|
||||
const table = db.prepare(query).get();
|
||||
// if table does not exists close the connection
|
||||
const query = `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`
|
||||
try {
|
||||
const table = db.prepare(query).get()
|
||||
|
||||
if (!table) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
message: `Table ${tableName} does not exist`,
|
||||
})
|
||||
);
|
||||
if (!table) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
message: `Table ${tableName} does not exist`
|
||||
})
|
||||
)
|
||||
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
message: error.message,
|
||||
})
|
||||
);
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
ws.close()
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
message: error.message
|
||||
})
|
||||
)
|
||||
ws.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (!websocketSubscribers.has(tableName)) {
|
||||
websocketSubscribers.set(tableName, new Set());
|
||||
}
|
||||
if (!websocketSubscribers.has(tableName)) {
|
||||
websocketSubscribers.set(tableName, new Set())
|
||||
}
|
||||
|
||||
const subscriber = {
|
||||
ws,
|
||||
params: new URLSearchParams(params),
|
||||
};
|
||||
const subscriber = {
|
||||
ws,
|
||||
params: new URLSearchParams(params)
|
||||
}
|
||||
|
||||
console.log(`New subscriber for table ${tableName}`);
|
||||
websocketSubscribers.get(tableName).add(subscriber);
|
||||
console.log(`New subscriber for table ${tableName}`)
|
||||
websocketSubscribers.get(tableName).add(subscriber)
|
||||
|
||||
ws.send(JSON.stringify({ message: `Subscribed to table "${tableName}"` }));
|
||||
ws.send(JSON.stringify({ message: `Subscribed to table "${tableName}"` }))
|
||||
|
||||
ws.on('message', function (message) {
|
||||
console.log('received: %s', message);
|
||||
});
|
||||
ws.on('message', function (message) {
|
||||
console.log('received: %s', message)
|
||||
})
|
||||
|
||||
ws.on('close', function () {
|
||||
websocketSubscribers.get(tableName).delete(subscriber);
|
||||
});
|
||||
});
|
||||
ws.on('close', function () {
|
||||
websocketSubscribers.get(tableName).delete(subscriber)
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
wss,
|
||||
websocketSubscribers,
|
||||
};
|
||||
wss,
|
||||
websocketSubscribers
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user