api extensions added.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
*.db-*
|
||||
*Chinook*
|
||||
*Chinook*
|
||||
_extensions/
|
||||
@@ -43,6 +43,11 @@ There's also a list of all endpoints examples at [docs/api-examples.md](docs/api
|
||||
|
||||
For websocket examples, check [docs/ws-examples.md](docs/ws-examples.md)
|
||||
|
||||
## Extending Soul
|
||||
|
||||
Soul is able to be extended (e.g. Adding custom APIs) via extensions, you can find a list of extensions at [docs/extensions-examples.md](docs/extensions-examples.md)
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soul-cli",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "A SQLite REST and Realtime server",
|
||||
"main": "src/server.js",
|
||||
"bin": {
|
||||
|
||||
@@ -34,6 +34,12 @@ if (process.env.NO_CLI !== 'true') {
|
||||
demandOption: false,
|
||||
choices: ['console', null],
|
||||
})
|
||||
.options('e', {
|
||||
alias: 'extensions',
|
||||
describe: 'Extensions directory path to load',
|
||||
type: 'string',
|
||||
demandOption: false,
|
||||
})
|
||||
.help(true).argv;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ const envVarsSchema = Joi.object()
|
||||
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),
|
||||
})
|
||||
.unknown();
|
||||
|
||||
@@ -76,4 +78,8 @@ module.exports = {
|
||||
windowMs: envVars.RATE_LIMIT_WINDOW,
|
||||
max: envVars.RATE_LIMIT_MAX,
|
||||
},
|
||||
|
||||
extensions: {
|
||||
path: argv.extensions || envVars.EXTENSIONS,
|
||||
},
|
||||
};
|
||||
|
||||
47
core/src/extensions.js
Normal file
47
core/src/extensions.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const { extensions: extensionsConfig } = require('./config');
|
||||
|
||||
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}`);
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
console.log(' >', api.path);
|
||||
});
|
||||
console.log('\n');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('No extensions directory provided');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setupExtensions,
|
||||
};
|
||||
@@ -14,9 +14,14 @@ const rootRoutes = require('./routes/index');
|
||||
const tablesRoutes = require('./routes/tables');
|
||||
const rowsRoutes = require('./routes/rows');
|
||||
const swaggerFile = require('./swagger/swagger.json');
|
||||
const { setupExtensions } = require('./extensions');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.send('OK');
|
||||
});
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Activate wal mode
|
||||
@@ -63,4 +68,6 @@ app.use('/api', rootRoutes);
|
||||
app.use('/api/tables', tablesRoutes);
|
||||
app.use('/api/tables', rowsRoutes);
|
||||
|
||||
setupExtensions(app, db);
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"title": "Soul API",
|
||||
"description": "API Documentation for <b>Soul</b>, a simple REST and realtime server. "
|
||||
"description": "API Documentation for <b>Soul</b>, a SQLite REST and realtime server. "
|
||||
},
|
||||
"host": "localhost:8000",
|
||||
"basePath": "/",
|
||||
@@ -33,6 +33,17 @@
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
168
docs/extensions-examples.md
Normal file
168
docs/extensions-examples.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Extensions Examples
|
||||
|
||||
Soul extensions are a way to extend the functionality of Soul. Extensions are written in JavaScript and can be used to add new endpoints, modify existing endpoints, or add new functionality to Soul.
|
||||
|
||||
## Types of Extensions
|
||||
|
||||
- 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.
|
||||
|
||||
### Download Sample Database
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite # Download sample sqlite database
|
||||
```
|
||||
|
||||
### Using Soul CLI
|
||||
```bash
|
||||
npm install -g soul-cli
|
||||
soul -d ./Chinook_Sqlite.sqlite -p 8000 -e "/path/to/_extensions/"
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Or Using Local Development</summary>
|
||||
|
||||
```bash
|
||||
git clone https://github.com/thevahidal/soul # Clone project
|
||||
cd core/
|
||||
npm install # Install dependencies
|
||||
npm link # might need `sudo`
|
||||
soul -d ./Chinook_Sqlite.sqlite -p 8000 -e "/path/to/_extensions/"
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
## Creating an API extension
|
||||
|
||||
To create an extension, create a new folder named `_extensions`. Then create a file named `api.js` inside it. This file will contain the extension code.
|
||||
|
||||
```js
|
||||
const hello = {
|
||||
method: 'GET',
|
||||
path: '/api/hello-soul',
|
||||
handler: (req, res, db) => {
|
||||
res.status(200).json({
|
||||
message: 'Hello Soul!'
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const timestamp = {
|
||||
method: 'GET',
|
||||
path: '/api/timestamp',
|
||||
handler: (req, res, db) => {
|
||||
res.status(200).json({
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const greetings = {
|
||||
method: 'POST',
|
||||
path: '/api/greetings/:name',
|
||||
handler: (req, res, db) => {
|
||||
const { name } = req.params;
|
||||
const { greeting } = req.body;
|
||||
res.status(200).json({
|
||||
message: `${greeting} ${name}!`,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
const searchTables = {
|
||||
method: 'GET',
|
||||
path: '/api/search-tables',
|
||||
handler: (req, res, db) => {
|
||||
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}%`,
|
||||
});
|
||||
res.status(200).json({
|
||||
tables,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
hello,
|
||||
timestamp,
|
||||
greetings,
|
||||
searchTables,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
Alright, now we can test if the extension is working:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/api/hello-soul
|
||||
```
|
||||
|
||||
It should return:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello Soul!"
|
||||
}
|
||||
```
|
||||
|
||||
And the same for the `timestamp` endpoint:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/api/timestamp
|
||||
```
|
||||
|
||||
It should return:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": 1620000000000
|
||||
}
|
||||
```
|
||||
|
||||
And `greetings` endpoint:
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"greeting": "Hello"}' http://localhost:8000/api/greetings/John
|
||||
```
|
||||
|
||||
It should return:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello John!"
|
||||
}
|
||||
```
|
||||
|
||||
And `list-tables` endpoint:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/api/search-tables?q=al
|
||||
```
|
||||
|
||||
It should return:
|
||||
|
||||
```json
|
||||
{
|
||||
"tables": [
|
||||
{
|
||||
"name": "Album"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
0
docs/extensions/api-examples.js
Normal file
0
docs/extensions/api-examples.js
Normal file
Reference in New Issue
Block a user