Skip to content

Commit

Permalink
[TECH] Remove Bookshelf from membership repository
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Aug 20, 2024
2 parents bbbc912 + 524e3cb commit 184bcf8
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 116 deletions.
234 changes: 130 additions & 104 deletions api/lib/infrastructure/repositories/membership-repository.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,72 @@
import { knex } from '../../../db/knex-database-connection.js';
import { User } from '../../../src/identity-access-management/domain/models/User.js';
import { Organization } from '../../../src/organizational-entities/domain/models/Organization.js';
import { MembershipCreationError, MembershipUpdateError, NotFoundError } from '../../../src/shared/domain/errors.js';
import { Membership } from '../../../src/shared/domain/models/Membership.js';
import { BookshelfMembership } from '../../../src/shared/infrastructure/orm-models/Membership.js';
import * as bookshelfToDomainConverter from '../../../src/shared/infrastructure/utils/bookshelf-to-domain-converter.js';
import * as knexUtils from '../../../src/shared/infrastructure/utils/knex-utils.js';
import { fetchPage } from '../../../src/shared/infrastructure/utils/knex-utils.js';
import { DomainTransaction } from '../DomainTransaction.js';

const DEFAULT_PAGE_SIZE = 10;
const DEFAULT_PAGE_NUMBER = 1;

function _toDomain(bookshelfMembership) {
const membership = new Membership(bookshelfMembership.toJSON());
const ORGANIZATION_TAGS_TABLE = 'organization-tags';
const ORGANIZATIONS_TABLE = 'organizations';
const MEMBERSHIPS_TABLE = 'memberships';
const USERS_TABLE = 'users';

if (bookshelfMembership.relations.user) {
membership.user = new User(bookshelfMembership.relations.user.toJSON());
}

if (bookshelfMembership.relations.organization) {
membership.organization = new Organization(bookshelfMembership.relations.organization.toJSON());
}

return membership;
}
export const create = async (userId, organizationId, organizationRole) => {
const knexConnection = DomainTransaction.getConnection();
try {
const [{ id: membershipId }] = await knexConnection(MEMBERSHIPS_TABLE)
.insert({ userId, organizationId, organizationRole })
.returning('id');

function _setSearchFiltersForQueryBuilder(filter, qb) {
const { firstName, lastName, email, organizationRole } = filter;
if (firstName) {
qb.whereILike('users.firstName', `%${firstName}%`);
}
if (lastName) {
qb.whereILike('users.lastName', `%${lastName}%`);
}
if (email) {
qb.whereILike('users.email', `%${email}%`);
}
if (organizationRole) {
qb.where('memberships.organizationRole', organizationRole);
}
}

const create = function (userId, organizationId, organizationRole) {
return new BookshelfMembership({ userId, organizationId, organizationRole })
.save()
.then((bookshelfMembership) => bookshelfMembership.load(['user']))
.then(_toDomain)
.catch((err) => {
if (knexUtils.isUniqConstraintViolated(err)) {
throw new MembershipCreationError(err.message);
}
throw err;
});
};
const membership = await knexConnection(MEMBERSHIPS_TABLE).where({ id: membershipId }).first();
const user = await knexConnection(USERS_TABLE).where({ id: membership.userId }).first();

const get = async function (membershipId) {
let bookshelfMembership;
try {
bookshelfMembership = await BookshelfMembership.where('id', membershipId).fetch({
withRelated: ['user', 'organization'],
});
return toDomain(membership, user);
} catch (error) {
if (error instanceof BookshelfMembership.NotFoundError) {
throw new NotFoundError(`Membership ${membershipId} not found`);
if (knexUtils.isUniqConstraintViolated(error)) {
throw new MembershipCreationError(error.message);
}
throw error;
}
};

return _toDomain(bookshelfMembership);
export const get = async function (membershipId) {
const knexConnection = DomainTransaction.getConnection();

const membership = await knexConnection(MEMBERSHIPS_TABLE).where({ id: membershipId }).first();
if (!membership) {
throw new NotFoundError(`Membership ${membershipId} not found`);
}

const user = await knexConnection(USERS_TABLE).where({ id: membership.userId }).first();
const organisation = await knexConnection(ORGANIZATIONS_TABLE).where({ id: membership.organizationId }).first();

return toDomain(membership, user, organisation);
};

const findByOrganizationId = async function ({ organizationId }) {
const memberships = await BookshelfMembership.where({ organizationId, disabledAt: null })
.orderBy('id', 'ASC')
.fetchAll({ withRelated: ['user'] });
return bookshelfToDomainConverter.buildDomainObjects(BookshelfMembership, memberships);
export const findByOrganizationId = async function ({ organizationId }) {
const knexConnection = DomainTransaction.getConnection();

const memberships = await knexConnection(MEMBERSHIPS_TABLE)
.where({ organizationId, disabledAt: null })
.orderBy('id', 'ASC');

const membershipUserIds = memberships.map(({ userId }) => userId);
const users = await knexConnection(USERS_TABLE).whereIn('id', membershipUserIds);

return memberships.map((membership) => {
const user = users.find(({ id }) => id === membership.userId);
return toDomain(membership, user);
});
};

const findAdminsByOrganizationId = async function ({ organizationId }) {
const membershipsDTO = await knex('memberships')
export const findAdminsByOrganizationId = async function ({ organizationId }) {
const knexConnection = DomainTransaction.getConnection();

const memberships = await knexConnection(MEMBERSHIPS_TABLE)
.select([
'memberships.id as membershipId',
'memberships.organizationRole',
Expand All @@ -94,70 +82,108 @@ const findAdminsByOrganizationId = async function ({ organizationId }) {
})
.orderBy('memberships.id', 'ASC');

return membershipsDTO.map((membershipDTO) => {
const user = new User({ ...membershipDTO, id: membershipDTO.userId });
return new Membership({ ...membershipDTO, user, id: membershipDTO.membershipId });
return memberships.map((membership) => {
const userData = { ...membership, id: membership.userId };
const membershipData = { ...membership, user: userData, id: membership.membershipId };
return toDomain(membershipData, userData);
});
};

const findPaginatedFiltered = async function ({ organizationId, filter, page }) {
export const findPaginatedFiltered = async function ({ organizationId, filter, page }) {
const knexConnection = DomainTransaction.getConnection();

const pageSize = page.size ? page.size : DEFAULT_PAGE_SIZE;
const pageNumber = page.number ? page.number : DEFAULT_PAGE_NUMBER;
const { models, pagination } = await BookshelfMembership.query((qb) => {
qb.where({ 'memberships.organizationId': organizationId, 'memberships.disabledAt': null });
_setSearchFiltersForQueryBuilder(filter, qb);
qb.innerJoin('users', 'memberships.userId', 'users.id');
qb.orderByRaw('"organizationRole" ASC, LOWER(users."lastName") ASC, LOWER(users."firstName") ASC');
}).fetchPage({
withRelated: ['user'],
page: pageNumber,
pageSize,

const queryBuilder = knexConnection(MEMBERSHIPS_TABLE).select('memberships.id as membershipId', '*');
queryBuilder.where({
'memberships.organizationId': organizationId,
'memberships.disabledAt': null,
});
const memberships = bookshelfToDomainConverter.buildDomainObjects(BookshelfMembership, models);
return { models: memberships, pagination };
setSearchFiltersForQueryBuilder(filter, queryBuilder);
queryBuilder.innerJoin('users', 'memberships.userId', 'users.id');
queryBuilder.orderByRaw('"organizationRole" ASC, LOWER(users."lastName") ASC, LOWER(users."firstName") ASC');

const result = await fetchPage(queryBuilder, { number: pageNumber, size: pageSize });
const memberships = result.results.map(
(membership) =>
new Membership({
id: membership.membershipId,
organizationRole: membership.organizationRole,
updatedByUserId: membership.updatedByUserId,
user: new User(membership),
}),
);

return { models: memberships, pagination: result.pagination };
};

const findByUserIdAndOrganizationId = function ({ userId, organizationId, includeOrganization = false }) {
return BookshelfMembership.where({ userId, organizationId, disabledAt: null })
.fetchAll({ withRelated: includeOrganization ? ['organization', 'organization.tags'] : [] })
.then((memberships) => bookshelfToDomainConverter.buildDomainObjects(BookshelfMembership, memberships));
export const findByUserIdAndOrganizationId = async ({ userId, organizationId, includeOrganization = false }) => {
const knexConnection = DomainTransaction.getConnection();
const memberships = await knexConnection(MEMBERSHIPS_TABLE).where({ userId, organizationId, disabledAt: null });

if (!includeOrganization) {
return memberships.map(toDomain);
}
const membershipOrganizationIds = memberships.map(({ organizationId }) => organizationId);
const organizations = await knexConnection(ORGANIZATIONS_TABLE).whereIn('id', membershipOrganizationIds);
const organizationIds = organizations.map(({ id }) => id);
const organizationsTags = await knexConnection(ORGANIZATION_TAGS_TABLE).whereIn('organizationId', organizationIds);

return memberships.map((membership) => {
const organization = organizations.find(({ id }) => id === membership.organizationId);
const organizationTags = organizationsTags.filter(({ organizationId }) => organizationId === organization.id);
return toDomain(membership, null, organization, organizationTags);
});
};

const updateById = async function ({ id, membership }) {
let updatedMembership;
export const updateById = async ({ id, membership }) => {
const knexConnection = DomainTransaction.getConnection();

if (!membership) {
throw new MembershipUpdateError("Le membership n'est pas renseigné");
}
if (!(await knexConnection(MEMBERSHIPS_TABLE).select('id').where({ id }).first())) {
throw new MembershipUpdateError();
}

try {
updatedMembership = await new BookshelfMembership({ id }).save(membership, {
patch: true,
method: 'update',
require: true,
await knexConnection(MEMBERSHIPS_TABLE)
.where({ id })
.update({
...membership,
updatedAt: new Date(),
});
} catch (err) {
throw new MembershipUpdateError(err.message);
}

const updatedMembershipWithUserAndOrganization = await updatedMembership.refresh({
withRelated: ['user', 'organization'],
});
return bookshelfToDomainConverter.buildDomainObject(BookshelfMembership, updatedMembershipWithUserAndOrganization);
return get(id);
};

const disableMembershipsByUserId = async function ({ userId, updatedByUserId }) {
const knexConn = DomainTransaction.getConnection();
await knexConn('memberships').where({ userId }).update({ disabledAt: new Date(), updatedByUserId });
export const disableMembershipsByUserId = async function ({ userId, updatedByUserId }) {
const knexConnection = DomainTransaction.getConnection();
await knexConnection(MEMBERSHIPS_TABLE)
.where({ userId })
.update({ disabledAt: new Date(), updatedAt: new Date(), updatedByUserId });
};

export {
create,
disableMembershipsByUserId,
findAdminsByOrganizationId,
findByOrganizationId,
findByUserIdAndOrganizationId,
findPaginatedFiltered,
get,
updateById,
const toDomain = (membershipData, userData = null, organizationData = null, organizationTags = null) => {
const membership = new Membership(membershipData);
if (userData) membership.user = new User(userData);
if (organizationData) membership.organization = new Organization(organizationData);
if (organizationTags) membership.organization.tags = organizationTags;
return membership;
};

const setSearchFiltersForQueryBuilder = (filter, queryBuilder) => {
const { firstName, lastName, email, organizationRole } = filter;
if (firstName) {
queryBuilder.whereILike('users.firstName', `%${firstName}%`);
}
if (lastName) {
queryBuilder.whereILike('users.lastName', `%${lastName}%`);
}
if (email) {
queryBuilder.whereILike('users.email', `%${email}%`);
}
if (organizationRole) {
queryBuilder.where('memberships.organizationRole', organizationRole);
}
};
52 changes: 41 additions & 11 deletions api/src/shared/infrastructure/repositories/membership-repository.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
import { BookshelfMembership } from '../../../../src/shared/infrastructure/orm-models/Membership.js';
import * as bookshelfToDomainConverter from '../utils/bookshelf-to-domain-converter.js';
import { DomainTransaction } from '../../domain/DomainTransaction.js';
import { Membership, Organization, User } from '../../domain/models/index.js';

const findByUserId = function ({ userId }) {
return BookshelfMembership.where({ userId, disabledAt: null })
.fetchAll({ withRelated: ['organization'] })
.then((memberships) => bookshelfToDomainConverter.buildDomainObjects(BookshelfMembership, memberships));
const ORGANIZATION_TAGS_TABLE = 'organization-tags';
const ORGANIZATIONS_TABLE = 'organizations';
const MEMBERSHIPS_TABLE = 'memberships';

export const findByUserId = async function ({ userId }) {
const knexConnection = DomainTransaction.getConnection();

const memberships = await knexConnection(MEMBERSHIPS_TABLE).where({ userId, disabledAt: null });
const membershipOrganizationIds = memberships.map(({ organizationId }) => organizationId);
const relatedOrganizations = await knexConnection(ORGANIZATIONS_TABLE).whereIn('id', membershipOrganizationIds);

return memberships.map((membership) => {
const organization = relatedOrganizations.find(({ id }) => id === membership.organizationId);
return toDomain(membership, null, organization);
});
};

const findByUserIdAndOrganizationId = function ({ userId, organizationId, includeOrganization = false }) {
return BookshelfMembership.where({ userId, organizationId, disabledAt: null })
.fetchAll({ withRelated: includeOrganization ? ['organization', 'organization.tags'] : [] })
.then((memberships) => bookshelfToDomainConverter.buildDomainObjects(BookshelfMembership, memberships));
export const findByUserIdAndOrganizationId = async ({ userId, organizationId, includeOrganization = false }) => {
const knexConnection = DomainTransaction.getConnection();
const memberships = await knexConnection(MEMBERSHIPS_TABLE).where({ userId, organizationId, disabledAt: null });

if (!includeOrganization) {
return memberships.map(toDomain);
}
const membershipOrganizationIds = memberships.map(({ organizationId }) => organizationId);
const organizations = await knexConnection(ORGANIZATIONS_TABLE).whereIn('id', membershipOrganizationIds);
const organizationIds = organizations.map(({ id }) => id);
const organizationsTags = await knexConnection(ORGANIZATION_TAGS_TABLE).whereIn('organizationId', organizationIds);

return memberships.map((membership) => {
const organization = organizations.find(({ id }) => id === membership.organizationId);
const organizationTags = organizationsTags.filter(({ organizationId }) => organizationId === organization.id);
return toDomain(membership, null, organization, organizationTags);
});
};

export { findByUserId, findByUserIdAndOrganizationId };
const toDomain = (membershipData, userData = null, organizationData = null, organizationTags = null) => {
const membership = new Membership(membershipData);
if (userData) membership.user = new User(userData);
if (organizationData) membership.organization = new Organization(organizationData);
if (organizationTags) membership.organization.tags = organizationTags;
return membership;
};
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ describe('Integration | Infrastructure | Repository | membership-repository', fu
it('should throw MembershipUpdateError', async function () {
// given
const organizationRole = Membership.roles.ADMIN;
const messageNotRowUpdated = 'No Rows Updated';
const messageNotRowUpdated = 'Erreur lors de la mise à jour du membership à une organisation.';
const notExistingMembershipId = 9898977;
const membership = { organizationRole, updatedByUserId };

Expand Down

0 comments on commit 184bcf8

Please sign in to comment.