Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

294 refactor references service #331

Merged
merged 18 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ rules:
no-restricted-properties: error
no-restricted-syntax: error
no-return-assign: 'off'
no-return-await: error
no-return-await: 'off'
no-script-url: error
no-self-assign:
- error
Expand Down
8 changes: 5 additions & 3 deletions app/controllers/references-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const referencesService = require('../services/references-service');
const logger = require('../lib/logger');
const { DuplicateIdError } = require('../exceptions');

exports.retrieveAll = async function(req, res) {
const options = {
Expand Down Expand Up @@ -44,7 +45,7 @@ exports.create = async function(req, res) {
return res.status(201).send(reference);
}
catch(err) {
if (err.message === referencesService.errors.duplicateId) {
if (err instanceof DuplicateIdError) {
logger.warn("Duplicate source_name");
return res.status(409).send('Unable to create reference. Duplicate source_name.');
}
Expand All @@ -60,11 +61,12 @@ exports.update = async function(req, res) {
const referenceData = req.body;

// Create the reference
try {
try {
const reference = await referencesService.update(referenceData);
if (!reference) {
return res.status(404).send('Reference not found.');
} else {
}
else {
logger.debug('Success: Updated reference with source_name ' + reference.source_name);
return res.status(200).send(reference);
}
Expand Down
1 change: 0 additions & 1 deletion app/repository/_base.repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ class BaseRepository extends AbstractRepository {

// eslint-disable-next-line class-methods-use-this
async save(data) {

try {
const document = new this.model(data);
return await document.save();
Expand Down
110 changes: 110 additions & 0 deletions app/repository/references-repository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use strict';

const Reference = require('../models/reference-model');
const { BadlyFormattedParameterError, DuplicateIdError, DatabaseError } = require('../exceptions');

class ReferencesRepository {
constructor(model) {
this.model = model;
}

async retrieveAll(options) {
// Build the text search
let textSearch;
if (typeof options.search !== 'undefined') {
textSearch = { $text: { $search: options.search }};
}

// Build the query
const query = {};
if (typeof options.sourceName !== 'undefined') {
query['source_name'] = options.sourceName;
}

// Build the aggregation
const aggregation = [];
if (textSearch) {
aggregation.push({ $match: textSearch });
}

aggregation.push({ $sort: { 'source_name': 1 }});
aggregation.push({ $match: query });

const facet = {
$facet: {
totalCount: [ { $count: 'totalCount' }],
documents: [ ]
}
};
if (options.offset) {
facet.$facet.documents.push({ $skip: options.offset });
}

Check warning on line 41 in app/repository/references-repository.js

View check run for this annotation

Codecov / codecov/patch

app/repository/references-repository.js#L40-L41

Added lines #L40 - L41 were not covered by tests
else {
facet.$facet.documents.push({ $skip: 0 });
}
if (options.limit) {
facet.$facet.documents.push({ $limit: options.limit });
}

Check warning on line 47 in app/repository/references-repository.js

View check run for this annotation

Codecov / codecov/patch

app/repository/references-repository.js#L46-L47

Added lines #L46 - L47 were not covered by tests
aggregation.push(facet);

// Retrieve the documents
return await this.model.aggregate(aggregation);
}

async save(data) {
// Create the document
const reference = new this.model(data);

// Save the document in the database
try {
const savedReference = await reference.save();
return savedReference;
}
catch(err) {
if (err.name === 'MongoServerError' && err.code === 11000) {
// 11000 = Duplicate index
throw new DuplicateIdError({
details: `Reference with source_name '${ data.source_name }' already exists.`
});
}
else {
throw new DatabaseError(err);
}

Check warning on line 72 in app/repository/references-repository.js

View check run for this annotation

Codecov / codecov/patch

app/repository/references-repository.js#L71-L72

Added lines #L71 - L72 were not covered by tests
}
}

async updateAndSave(data) {
try {
const document = await this.model.findOne({ 'source_name': data.source_name });
if (!document) {
// document not found
return null;
}
else {
// Copy data to found document and save
Object.assign(document, data);
const savedDocument = await document.save();
return savedDocument;
}
}
catch(err) {
if (err.name === 'CastError') {
throw new BadlyFormattedParameterError({ parameterName: 'source_name' });
}
else {
throw new DatabaseError(err);
}
}

Check warning on line 97 in app/repository/references-repository.js

View check run for this annotation

Codecov / codecov/patch

app/repository/references-repository.js#L91-L97

Added lines #L91 - L97 were not covered by tests
}

async findOneAndRemove(sourceName) {
try {
return await this.model.findOneAndRemove({ 'source_name': sourceName });
}
catch(err) {
throw new DatabaseError(err);
}

Check warning on line 106 in app/repository/references-repository.js

View check run for this annotation

Codecov / codecov/patch

app/repository/references-repository.js#L105-L106

Added lines #L105 - L106 were not covered by tests
}
}

module.exports = new ReferencesRepository(Reference);
2 changes: 1 addition & 1 deletion app/services/_base.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
try {
results = await this.repository.retrieveAll(options);
} catch (err) {
const databaseError = new DatabaseError(err); // Let the DatabaseError buddle up
const databaseError = new DatabaseError(err); // Let the DatabaseError bubble up

Check warning on line 56 in app/services/_base.service.js

View check run for this annotation

Codecov / codecov/patch

app/services/_base.service.js#L56

Added line #L56 was not covered by tests
if (callback) {
return callback(databaseError);
}
Expand Down
151 changes: 25 additions & 126 deletions app/services/references-service.js
Original file line number Diff line number Diff line change
@@ -1,142 +1,41 @@
'use strict';

const Reference = require('../models/reference-model');
const baseService = require('./_base.service');
const ReferencesRepository = require('../repository/references-repository');
const { MissingParameterError } = require('../exceptions');

const errors = {
missingParameter: 'Missing required parameter',
badlyFormattedParameter: 'Badly formatted parameter',
duplicateId: 'Duplicate id',
notFound: 'Document not found',
invalidQueryStringParameter: 'Invalid query string parameter'
};
exports.errors = errors;

exports.retrieveAll = async function(options) {
// Build the text search
let textSearch;
if (typeof options.search !== 'undefined') {
textSearch = { $text: { $search: options.search }};
}

// Build the query
const query = {};
if (typeof options.sourceName !== 'undefined') {
query['source_name'] = options.sourceName;
}

// Build the aggregation
const aggregation = [];
if (textSearch) {
aggregation.push({ $match: textSearch });
}

aggregation.push({ $sort: { 'source_name': 1 }});
aggregation.push({ $match: query });

const facet = {
$facet: {
totalCount: [ { $count: 'totalCount' }],
documents: [ ]
}
};
if (options.offset) {
facet.$facet.documents.push({ $skip: options.offset });
}
else {
facet.$facet.documents.push({ $skip: 0 });
}
if (options.limit) {
facet.$facet.documents.push({ $limit: options.limit });
class ReferencesService {
constructor() {
this.repository = ReferencesRepository;
}
aggregation.push(facet);

// Retrieve the documents
const results = await Reference.aggregate(aggregation);
async retrieveAll(options) {
const results = await this.repository.retrieveAll(options);
const paginatedResults = baseService.paginate(options, results);

if (options.includePagination) {
let derivedTotalCount = 0;
if (results[0].totalCount.length > 0) {
derivedTotalCount = results[0].totalCount[0].totalCount;
}
const returnValue = {
pagination: {
total: derivedTotalCount,
offset: options.offset,
limit: options.limit
},
data: results[0].documents
};
return returnValue;
}
else {
return results[0].documents;
return paginatedResults;
}
};

exports.create = async function(data) {
// Create the document
const reference = new Reference(data);

// Save the document in the database
try {
const savedReference = await reference.save();
return savedReference;

async create(data) {
return await this.repository.save(data);
}
catch(err) {
if (err.name === 'MongoServerError' && err.code === 11000) {
// 11000 = Duplicate index
throw new Error(errors.duplicateId);
} else {
console.log(`name: ${err.name} code: ${err.code}`);
throw err;

async update(data) {
// Note: source_name is used as the key and cannot be updated
if (!data.source_name) {
throw new MissingParameterError({ parameterName: 'source_name' });

Check warning on line 26 in app/services/references-service.js

View check run for this annotation

Codecov / codecov/patch

app/services/references-service.js#L26

Added line #L26 was not covered by tests
}
}
};

exports.update = async function(data, callback) {
// Note: source_name is used as the key and cannot be updated
if (!data.source_name) {
const error = new Error(errors.missingParameter);
error.parameterName = 'source_name';
throw error;
return await this.repository.updateAndSave(data);
}

try {
const document = await Reference.findOne({ 'source_name': data.source_name });
if (!document) {
// document not found
return null;
}
else {
// Copy data to found document and save
Object.assign(document, data);
const savedDocument = await document.save();
return savedDocument;
async deleteBySourceName(sourceName) {
if (!sourceName) {
throw new MissingParameterError({ parameterName: 'source_name' });

Check warning on line 34 in app/services/references-service.js

View check run for this annotation

Codecov / codecov/patch

app/services/references-service.js#L34

Added line #L34 was not covered by tests
}
}
catch(err) {
if (err.name === 'CastError') {
const error = new Error(errors.badlyFormattedParameter);
error.parameterName = 'source_name';
return callback(error);
}
else if (err.name === 'MongoServerError' && err.code === 11000) {
// 11000 = Duplicate index
throw new Error(errors.duplicateId);
} else {
throw err;
}
}
};

exports.deleteBySourceName = async function (sourceName) {
if (!sourceName) {
const error = new Error(errors.missingParameter);
error.parameterName = 'sourceName';
throw error;
return await this.repository.findOneAndRemove(sourceName);
}
}

const deletedReference = await Reference.findOneAndRemove({ 'source_name': sourceName });
return deletedReference;
};

module.exports = new ReferencesService(ReferencesRepository);
Loading