Skip to content

Commit

Permalink
Merge branch 'main' into feature/multiword-search
Browse files Browse the repository at this point in the history
  • Loading branch information
williambelle committed Jul 13, 2023
2 parents c988b44 + d0529a5 commit f322ef6
Show file tree
Hide file tree
Showing 25 changed files with 738 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ coverage

# Ansible
ansible/ansible-deps-cache

# Ignore bad JSON for jest test
tests/resources/cadidb/searchUnits-badJSON.json
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@
- [/api/ldap?q=278890][ldap-1]
- [/api/ldap?q=[email protected]&hl=en][ldap-2]

## Unit

### Endpoint

`GET /api/unit`

### Parameters

| Name | Type | Comments |
| ------ | -------- | -------------------------------------------------------------- |
| `q` | `String` | Query |
| `acro` | `String` | Specifies an exact unit acronym. If specified, `q` is ignored. |
| `hl` | `String` | Sets the user interface language. |

### Examples

- [/api/unit?q=fsd][unit-1]
- [/api/unit?q=vpo&hl=en][unit-2]
- [/api/unit?acro=vpo][unit-3]

## EPFL Graph

### Endpoint
Expand Down Expand Up @@ -83,5 +103,8 @@ See [Contributing](CONTRIBUTING.md).
[cse-3]: http://127.0.0.1:5555/api/cse?q=math&hl=en&sort=date&searchType=image
[ldap-1]: http://127.0.0.1:5555/api/ldap?q=278890
[ldap-2]: http://127.0.0.1:5555/api/[email protected]&hl=en
[unit-1]: http://127.0.0.1:5555/api/unit?q=fsd
[unit-2]: http://127.0.0.1:5555/api/unit?q=vpo&hl=en
[unit-3]: http://127.0.0.1:5555/api/unit?acro=vpo
[graphsearch-1]: http://127.0.0.1:5555/api/graphsearch?q=math
[graphsearch-2]: http://127.0.0.1:5555/api/graphsearch?q=vetterli
4 changes: 4 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const express = require('express');

const cseRouter = require('./routes/cse.route');
const peopleRouter = require('./routes/people.route');
const unitRouter = require('./routes/unit.route');
const semanticRouter = require('./routes/semantic.route');

const app = express();
Expand All @@ -32,6 +33,9 @@ app.use('/api/cse', cseRouter);
// People
app.use('/api/ldap', peopleRouter);

// Unit
app.use('/api/unit', unitRouter);

// EPFL Graph
app.use('/api/graphsearch', semanticRouter);

Expand Down
18 changes: 18 additions & 0 deletions src/controllers/unit.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const unitService = require('../services/unit.service');

async function get (req, res) {
try {
const results = await unitService.get(req.query);
return res.json(results);
} catch (err) {
console.error('[error] ', err);
return res.status(400).json({
success: false,
error: 'Oops, something went wrong'
});
}
}

module.exports = {
get
};
8 changes: 8 additions & 0 deletions src/routes/unit.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const express = require('express');
const router = express.Router();

const unitController = require('../controllers/unit.controller');

router.get('/', unitController.get);

module.exports = router;
19 changes: 9 additions & 10 deletions src/services/cadidb.service.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
const mysql = require('mysql2');
const mysql = require('mysql2/promise');
const cadidbConfig = require('../configs/cadidb.config');

// Create the pool
const pool = mysql.createPool(cadidbConfig.db);
// Get a Promise wrapped instance of the pool
const promisePool = pool.promise();

async function sendQuery (query, values) {
async function sendQuery (query, values, referrer) {
let connection;
try {
// Perform the database query
const [rows] = await promisePool.query(query, values);
connection = await pool.getConnection();
const [rows] = await connection.query(query, values, referrer);
return rows;
} catch (err) {
console.error('Error executing query', err);
return err;
} finally {
if (connection) {
connection.release();
}
}
}

Expand Down
137 changes: 137 additions & 0 deletions src/services/unit.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const cadidbService = require('./cadidb.service');

async function get (params) {
const lang = params.hl || 'fr';
const q = params.q;
const acro = params.acro;
if (acro) {
return await getUnit(acro, lang);
}

const unitList = await searchUnits(q, lang);

if (unitList.length === 0) {
return unitList;
} else if (unitList.length === 1) {
return await getUnit(unitList[0].acronym, lang);
} else {
return unitList;
}
}

async function searchUnits (q, lang) {
const query = 'SELECT sigle, libelle, libelle_en, hierarchie ' +
'FROM Unites_v2 WHERE cmpl_type <> ? AND ' +
'(sigle like ? OR libelle like ? OR libelle_en like ?)';
const values = ['Z', '%' + q + '%', '%' + q + '%', '%' + q + '%'];

const results = await cadidbService.sendQuery(query, values, 'searchUnits');
const formattedResults = results.map((dict) => {
const modifiedDict = {
acronym: dict.sigle,
path: dict.hierarchie.split(' '),
name: lang === 'en' ? dict.libelle_en : dict.libelle
};
return modifiedDict;
});
return formattedResults;
}

async function getUnit (acro, lang) {
const query = 'SELECT sigle, id_unite, libelle, libelle_en, hierarchie, ' +
'resp_sciper, resp_nom, resp_nom_usuel, resp_prenom, ' +
'resp_prenom_usuel, url, faxes, adresse, cmpl_type, ghost, ' +
'has_accreds ' +
'FROM Unites_v2 ' +
'WHERE sigle = ? AND cmpl_type <> ?';
const values = [acro, 'Z'];

const results = await cadidbService.sendQuery(query, values, 'getUnit');
if (results.length !== 1) {
return;
}
const dict = results[0];
const unitPath = await getUnitPath(dict.hierarchie, lang);
const unitFullDetails = {
code: dict.id_unite,
acronym: dict.sigle,
name: lang === 'en' ? dict.libelle_en : dict.libelle,
unitPath: dict.hierarchie,
path: unitPath,
terminal: dict.has_accreds,
ghost: dict.ghost,
address: dict.adresse.split('$').map((value) => value.trim())
.filter((value) => value !== ''),
head: {
sciper: dict.resp_sciper,
name: dict.resp_nom_usuel || dict.resp_nom,
firstname: dict.resp_prenom_usuel || dict.resp_prenom,
email: '<EMAIL>', // TODO: Get email from ldap
profile: '<EMAIL_PREFIX>' // TODO: Build from email over
}
};
if (dict.url) {
unitFullDetails.url = dict.url;
}
if (dict.has_accreds) {
// TODO: Get people from ldap

} else {
unitFullDetails.subunits = await getSubunits(dict.id_unite, lang);
}
if (dict.faxes) {
unitFullDetails.faxes = dict.faxes.split(',').map((fax) => {
const trimmedFax = fax.trim();
if (trimmedFax.startsWith('0')) {
return trimmedFax.replace(/^0(\d{2})(.*)/, '+41 $1 $2');
} else {
return '+41 21 69' + trimmedFax;
}
});
}

// TODO: if req.get('X-EPFL-Internal') === 'TRUE' → add `adminData`

return unitFullDetails;
}

// Get unit path (acronym + name)
async function getUnitPath (hierarchy, lang) {
const inHierarchyClause = `IN ('${hierarchy.split(' ').join("', '")}')`;
const query = 'SELECT sigle, libelle, libelle_en ' +
'FROM Unites_v2 ' +
`WHERE sigle ${inHierarchyClause}`;
const values = [];

const results = await cadidbService.sendQuery(query, values, 'getUnitPath');
const formattedResults = results.map((dict) => {
const modifiedDict = {
acronym: dict.sigle,
name: lang === 'en' ? dict.libelle_en : dict.libelle
};
return modifiedDict;
});
return formattedResults;
}

// Get Subunit(s) (acronym + name)
async function getSubunits (unitId, lang) {
const query = 'SELECT sigle, libelle, libelle_en ' +
'FROM Unites_v2 ' +
'WHERE id_parent = ? AND cmpl_type <> ?';
const values = [unitId, 'Z'];

const results = await cadidbService.sendQuery(query, values, 'getSubunits');
const formattedResults = results.map((dict) => {
const modifiedDict = {
acronym: dict.sigle,
name: lang === 'en' ? dict.libelle_en : dict.libelle
};
return modifiedDict;
});
return formattedResults;
}

module.exports = {
get
};
16 changes: 16 additions & 0 deletions src/utils/helper.util.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/**
* Utilities.
*
* @module utils/helper
*/

/**
* Validate environment variables.
*
* @example
* const helper = require('../utils/helper.util');
* helper.validateEnv('SEARCH_API_LDAP_URL'); // => 'ldaps://ldap.epfl.ch'
*
* @param {string} key A key of an environment variable.
* @returns {string} Return the value or exit(1) if the key doesn't exists.
*/
function validateEnv (key) {
if (!process.env[key]) {
console.error(
Expand Down
33 changes: 33 additions & 0 deletions tests/cadidb.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const cadidbService = require('../src/services/cadidb.service');
const mysql = require('mysql2/promise');

jest.mock('mysql2/promise', () => {
const mockPool = {
getConnection: jest.fn()
};

return {
createPool: jest.fn(() => mockPool),
mockPool // Exporting the mock pool for reference
};
});

describe('Test Cadi DB Service', () => {
test('It should execute the query and return the result', async () => {
const mockConnection = {
query: jest.fn().mockResolvedValue([[{ id: 1, name: 'Drebin' }]]),
release: jest.fn()
};

mysql.createPool().getConnection.mockResolvedValue(mockConnection);

const query = 'SELECT * FROM users';
const values = [];
const result = await cadidbService.sendQuery(query, values, 'test');

expect(result).toEqual([{ id: 1, name: 'Drebin' }]);
expect(mysql.createPool).toHaveBeenCalled();
expect(mockConnection.query).toHaveBeenCalledWith(query, values, 'test');
expect(mockConnection.release).toHaveBeenCalled();
});
});
12 changes: 12 additions & 0 deletions tests/resources/cadidb/getSubunits-ot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"sigle": "EP-5",
"libelle": "Episode V - L'Empire contre-attaque",
"libelle_en": "Episode V - The Empire Strikes Back"
},
{
"sigle": "EP-6",
"libelle": "Episode VI - Le Retour du Jedi",
"libelle_en": "Episode VI - Return of the Jedi"
}
]
20 changes: 20 additions & 0 deletions tests/resources/cadidb/getUnit-mandalore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"sigle": "MANDALORE",
"id_unite": 10111,
"libelle": "Mandalore est une planète située dans les territoires de la bordure extérieure de la galaxie.",
"libelle_en": "Mandalore is a planet located in the Outer Rim territories of the galaxy.",
"hierarchie": "EPFL SO TV-3 MANDALORE",
"resp_sciper": "670005",
"resp_nom": "Grogu",
"resp_nom_usuel": null,
"resp_prenom": "Din",
"resp_prenom_usuel": null,
"url": "",
"faxes": "",
"adresse": "EPFL SO TV-3 MANDALORE $ Outer Rim $ Station 12 $ CH-1015 Lausanne $ $ ",
"cmpl_type": "F",
"ghost": null,
"has_accreds": "1"
}
]
20 changes: 20 additions & 0 deletions tests/resources/cadidb/getUnit-ot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"sigle": "OT",
"id_unite": 10001,
"libelle": "Trilogie originale",
"libelle_en": "Original trilogy",
"hierarchie": "EPFL OT",
"resp_sciper": "202020",
"resp_nom": "Lucas",
"resp_nom_usuel": null,
"resp_prenom": "Gerge Walton",
"resp_prenom_usuel": "George",
"url": "https://en.wikipedia.org/wiki/Star_Wars_original_trilogy",
"faxes": "10121,0216910122",
"adresse": "EPFL OT $ Original trilogy Square $ Station 15 $ CH-1015 Lausanne $ $ ",
"cmpl_type": "F",
"ghost": null,
"has_accreds": null
}
]
22 changes: 22 additions & 0 deletions tests/resources/cadidb/getUnitPath-mandalore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"sigle": "EPFL",
"libelle": "Ecole polytechnique fédérale de Lausanne",
"libelle_en": "Ecole polytechnique fédérale de Lausanne"
},
{
"sigle": "SO",
"libelle": "Dérivé",
"libelle_en": "Spin-off"
},
{
"sigle": "TV-3",
"libelle": "Série TV 3",
"libelle_en": "TV Series 3"
},
{
"sigle": "MANDALORE",
"libelle": "Mandalore est une planète située dans les territoires de la bordure extérieure de la galaxie.",
"libelle_en": "Mandalore is a planet located in the Outer Rim territories of the galaxy."
}
]
Loading

0 comments on commit f322ef6

Please sign in to comment.