Skip to content
Commits on Source (239)
.env
config/**.development.json
config/settings.development.json
\ No newline at end of file
config/settings.development.json
.DS_Store
......@@ -33,7 +33,7 @@ stages:
# Common setup for all meteor based jobs
.meteor:
extends: .not-on-stable
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.3
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.8.1
before_script:
- cd app
cache:
......@@ -49,7 +49,6 @@ stages:
variables:
METEOR_ALLOW_SUPERUSER: "true"
###############################################################################
# `initial-checks` stage: `commitlint`
###############################################################################
......@@ -58,7 +57,6 @@ commitlint:
stage: initial-checks
extends: .git:commitlint
###############################################################################
# `deps` stage: to download and cache dependencies
###############################################################################
......@@ -89,7 +87,6 @@ meteor-tests:
script:
- meteor npm test
###############################################################################
# `build` stage: `*-docker-build`
###############################################################################
......@@ -114,7 +111,6 @@ build-docker:
- !reference [.rules-map, not-on-semantic-release-commit]
- !reference [.rules-map, on-branch]
###############################################################################
# `release` stage: `semantic-release`, `testing-prerelease`, `merge-to-dev`, `tag *`
###############################################################################
......@@ -125,7 +121,8 @@ testing prerelease:
extends: .semantic-release:testing
# Avoid regression and update `version` of app/package*.json in `$DEV_BRANCH`
merge-to-dev: {extends: '.git:merge-to', variables: {GIT_MERGE_TARGET: $DEV_BRANCH}}
merge-to-dev:
{ extends: ".git:merge-to", variables: { GIT_MERGE_TARGET: $DEV_BRANCH } }
## tag contribution branches with a more stable name than `git-${CI_COMMIT_SHORT_SHA}`
tag contrib branch:
......
# The tag here should match the Meteor version of your app, per .meteor/release
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.3
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.8.0
# Copy app package.json and package-lock.json into container
COPY ./app/package*.json $APP_SOURCE_FOLDER/
......@@ -15,7 +15,7 @@ RUN bash $SCRIPTS_FOLDER/build-meteor-bundle.sh
RUN cd $APP_SOURCE_FOLDER && meteor npm install
# Rather than Node 8 latest (Alpine), you can also use the specific version of Node expected by your Meteor release, per https://docs.meteor.com/changelog.html
FROM hub.eole.education/proxyhub/library/node:14.19.3-alpine
FROM hub.eole.education/proxyhub/library/node:14.21.1-alpine
ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker
......@@ -38,7 +38,7 @@ RUN bash $SCRIPTS_FOLDER/build-meteor-npm-dependencies.sh --build-from-source
# Start another Docker stage, so that the final image doesn’t contain the layer with the build dependencies
# See previous FROM line; this must match
FROM hub.eole.education/proxyhub/library/node:14.19.3-alpine
FROM hub.eole.education/proxyhub/library/node:14.21.1-alpine
ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker
......
......@@ -9,6 +9,35 @@ Développé dans le cadre du projet [APPS-EDUCATION](https://apps.education.fr/)
Plus d'information sur le [ wiki](https://gitlab.mim-libre.fr/alphabet/laboite/-/wikis/home).
Utilise :
- [METEOR](https://www.meteor.com)
- [REACT](https://fr.reactjs.org/)
- [METEOR](https://www.meteor.com)
- [REACT](https://fr.reactjs.org/)
## Initialisation
- Le projet nécessite l'installation de Meteor.
https://www.meteor.com/developers/install
## Installation en local
- Se placer dans le dossier app du projet.
- Facultatif: Si présent, supprimer le dossier node_modules.
- Utiliser la commande suivante, toujours dans le dossier app:
`meteor npm ci`
- Une fois l'installation des packages effectuées, se rendre dans le dossier config du projet.
- Copier le fichier settings.development.json.sample et le coller au même endroit, en le renommant settings.development.json.
- Remplir le fichier settings.development.json avec les informations utiles.
## fichier settings.development.json
ATTENTION: Pour les développeurs, ne jamais publier le fichier settings.development.json et ne jamais remplir et publier le fichier settings.development.json.sample.
La description des champs et les valeurs requisent sont toutes définies dans le fichier README.md présent dans le dossier config.
## Lancement du projet
- Lorsque tout est convenablement installé et configuré, revenir dans le dossier app, puis exécuter la commande suivant:
`meteor npm start`
accounts-base@2.2.4
accounts-base@2.2.5
accounts-oauth@1.4.1
accounts-password@2.3.1
alanning:roles@3.4.0
......@@ -15,29 +15,29 @@ boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.4.0
check@1.3.1
check@1.3.2
dburles:collection-helpers@1.1.0
dburles:factory@1.1.0
ddp@1.4.0
ddp-client@2.5.0
ddp@1.4.1
ddp-client@2.6.1
ddp-common@1.4.0
ddp-rate-limiter@1.1.0
ddp-server@2.5.0
diff-sequence@1.1.1
ddp-rate-limiter@1.1.1
ddp-server@2.6.0
diff-sequence@1.1.2
dynamic-import@0.7.2
ecmascript@0.16.2
ecmascript@0.16.3
ecmascript-runtime@0.8.0
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.2
email@2.2.1
ejson@1.1.3
email@2.2.2
eoleteam:accounts-keycloak@2.1.0
eoleteam:keycloak-oauth@2.2.0
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
fetch@0.1.2
geojson-utils@1.0.11
hot-code-push@1.0.4
hot-module-replacement@0.5.1
hot-module-replacement@0.5.2
html-tools@1.1.3
htmljs@1.1.1
http@2.0.0
......@@ -50,7 +50,7 @@ localstorage@1.2.0
logging@1.3.1
matb33:collection-hooks@1.1.4
mdg:validated-method@1.2.0
meteor@1.10.0
meteor@1.10.2
meteor-base@1.5.1
meteortesting:browser-tests@1.3.5
meteortesting:mocha@2.0.3
......@@ -58,37 +58,37 @@ meteortesting:mocha-core@8.1.2
mexar:mdt@0.2.2
minifier-css@1.6.1
minifier-js@2.7.5
minimongo@1.8.0
minimongo@1.9.0
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.8
modules@0.18.0
modules-runtime@0.13.0
modules-runtime-hot@0.14.0
mongo@1.15.0
modern-browsers@0.1.9
modules@0.19.0
modules-runtime@0.13.1
modules-runtime-hot@0.14.1
mongo@1.16.1
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.3.1
npm-mongo@4.11.0
oauth@2.1.2
oauth2@1.3.1
ordered-dict@1.1.0
percolate:find-from-publication@0.2.1
percolate:migrations@1.0.3
promise@0.12.0
percolate:migrations@1.1.0
promise@0.12.1
raix:eventemitter@1.0.0
random@1.2.0
random@1.2.1
rate-limit@1.0.9
react-fast-refresh@0.2.3
react-meteor-data@2.4.0
reactive-var@1.0.11
reactive-var@1.0.12
reload@1.3.1
retry@1.1.0
reywood:publish-composite@1.7.3
routepolicy@1.1.1
seba:method-hooks@3.0.2
server-render@0.4.0
service-configuration@1.3.0
server-render@0.4.1
service-configuration@1.3.1
sha@1.0.9
shell-server@0.5.0
socket-stream-client@0.5.0
......@@ -99,10 +99,10 @@ static-html@1.3.2
templating-tools@1.2.2
tmeasday:check-npm-versions@1.0.2
tmeasday:publish-counts@0.8.0
tracker@1.2.0
tracker@1.2.1
typescript@4.5.4
underscore@1.0.10
underscore@1.0.11
universe:i18n@1.32.6
url@1.3.2
webapp@1.13.1
webapp-hashing@1.1.0
webapp@1.13.2
webapp-hashing@1.1.1
......@@ -6,12 +6,13 @@ import crypto from 'crypto';
import { parseStringPromise } from 'xml2js';
import logServer from '../logging';
import Groups from '../groups/groups';
import { testMeteorSettingsUrl } from '../../ui/utils/utilsFuncs';
const bbbEnabled = Meteor.settings.public.enableBBB;
class BigBlueButtonClient {
constructor() {
this.bbbURL = Meteor.settings.public.BBBUrl;
this.bbbURL = testMeteorSettingsUrl(Meteor.settings.public.BBBUrl);
this.bbbSecret = Meteor.settings.private.BBBSecret;
}
......
......@@ -6,12 +6,13 @@ import logServer from '../logging';
import Groups from '../groups/groups';
import AppRoles from '../users/users';
import { isActive } from '../utils';
import { testMeteorSettingsUrl } from '../../ui/utils/utilsFuncs';
const keycloakSettings = Meteor.settings.keycloak;
class KeyCloakClient {
constructor() {
this.kcURL = Meteor.settings.public.keycloakUrl;
this.kcURL = testMeteorSettingsUrl(Meteor.settings.public.keycloakUrl);
this.kcRealm = Meteor.settings.public.keycloakRealm;
this.clientId = null;
this.adminsGroupId = null;
......@@ -366,8 +367,7 @@ class KeyCloakClient {
this._removeGroup(name, callerId);
}
setAdmin(userId, callerId) {
const groupName = `admins`;
setAdmin(userId, callerId, groupName = `admins`) {
const user = Meteor.users.findOne(userId);
const keycloakId = user.services && user.services.keycloak ? user.services.keycloak.id : null;
if (keycloakId) {
......@@ -382,12 +382,20 @@ class KeyCloakClient {
},
})
.then(() => {
logServer(i18n.__('api.keycloak.adminAdded', { userId }));
if (groupName === 'adminStructure') {
logServer(i18n.__('api.keycloak.adminStructureAdded', { userId }));
} else {
logServer(i18n.__('api.keycloak.adminAdded', { userId }));
}
});
}),
)
.catch((error) => {
logServer(i18n.__('api.keycloak.addAdminError', { userId }), 'error', callerId);
if (groupName === 'adminStructure') {
logServer(i18n.__('api.keycloak.addAdminStructureError', { userId }), 'error', callerId);
} else {
logServer(i18n.__('api.keycloak.addAdminError', { userId }), 'error', callerId);
}
logServer(error.response && error.response.data ? error.response.data : error, 'error');
});
} else {
......@@ -395,8 +403,7 @@ class KeyCloakClient {
}
}
unsetAdmin(userId, callerId) {
const groupName = `admins`;
unsetAdmin(userId, callerId, groupName = `admins`) {
const user = Meteor.users.findOne(userId);
const keycloakId = user.services && user.services.keycloak ? user.services.keycloak.id : null;
if (keycloakId) {
......@@ -411,7 +418,11 @@ class KeyCloakClient {
},
})
.then(() => {
logServer(`Keycloak: user ${userId} removed from admins`);
if (groupName === 'adminStructure') {
logServer(i18n.__('api.keycloak.adminStructureRemoved', { userId }));
} else {
logServer(i18n.__('api.keycloak.adminRemoved', { userId }));
}
}),
),
)
......@@ -537,6 +548,18 @@ if (Meteor.isServer && Meteor.settings.public.enableKeycloak) {
}
});
Meteor.afterMethod('users.setAdminStructure', function kcSetAdmin({ userId }) {
if (!this.error) {
kcClient.setAdmin(userId, this.userId, 'adminStructure');
}
});
Meteor.afterMethod('users.unsetAdminStructure', function kcUnsetAdmin({ userId }) {
if (!this.error) {
kcClient.unsetAdmin(userId, this.userId, 'adminStructure');
}
});
Meteor.afterMethod('users.setAnimatorOf', function kcSetAnimator({ userId, groupId }) {
if (!this.error) {
// there is no difference between member and animator roles in keycloak
......
......@@ -4,13 +4,13 @@ import i18n from 'meteor/universe:i18n';
import { Roles } from 'meteor/alanning:roles';
import logServer from '../logging';
import Groups from '../groups/groups';
import { isActive } from '../utils';
import { isActive, testUrl } from '../utils';
const nextcloudPlugin = Meteor.settings.public.groupPlugins.nextcloud;
const { nextcloud } = Meteor.settings;
function ocsUrl(ncURL) {
const origin = ncURL.startsWith('http') ? ncURL : `https://${ncURL}`;
const origin = testUrl(ncURL);
return new URL('/ocs/v1.php/cloud', origin).href;
}
......@@ -29,7 +29,7 @@ class NextcloudClient {
const ncUser = (nextcloud && nextcloud.nextcloudUser) || '';
const ncPassword = (nextcloud && nextcloud.nextcloudPassword) || '';
this.nextURL = ocsUrl(ncURL);
this.appsURL = `${ncURL}/apps`;
this.appsURL = testUrl(`${ncURL}/apps`);
this.basicAuth = Buffer.from(`${ncUser}:${ncPassword}`, 'binary').toString('base64');
}
......
......@@ -95,6 +95,10 @@ AppSettings.schema = new SimpleSchema(
type: SettingsType('personal_data'),
label: getLabel('api.appsettings.labels.personal_data'),
},
personalSpace: {
type: SettingsType('personal_space'),
label: getLabel('api.appsettings.labels.personalSpace'),
},
maintenance: {
type: Boolean,
defaultValue: false,
......@@ -123,6 +127,7 @@ AppSettings.publicFields = {
maintenance: 1,
textMaintenance: 1,
userStructureValidationMandatory: 1,
personalSpace: 1,
};
AppSettings.introduction = {
......@@ -142,6 +147,8 @@ AppSettings.links = {
'gcu.external': 1,
'personalData.link': 1,
'personalData.external': 1,
'personalSpace.link': 1,
'personalSpace.external': 1,
};
AppSettings.legal = {
......@@ -164,6 +171,11 @@ AppSettings.personalData = {
'personalData.external': 1,
'personalData.content': 1,
};
AppSettings.personalSpace = {
'personalSpace.link': 1,
'personalSpace.external': 1,
'personalSpace.content': 1,
};
AppSettings.attachSchema(AppSettings.schema);
......
......@@ -62,6 +62,11 @@ describe('appsettings', function () {
content: 'pdata_content',
},
userStructureValidationMandatory: false,
personalSpace: {
external: false,
link: 'pSpaceData_link',
content: 'pSpaceData_content',
},
};
AppSettings.insert(appsettinit);
});
......@@ -205,6 +210,11 @@ describe('appsettings', function () {
content: 'pdata_content',
},
userStructureValidationMandatory: false,
personalSpace: {
external: false,
link: 'pSpaceData_link',
content: 'pSpaceData_content',
},
};
AppSettings.insert(appsettinit);
......
......@@ -45,4 +45,9 @@ Factory.define('appsettings', AppSettings, {
content: faker.lorem.sentences(),
},
userStructureValidationMandatory: false,
personalSpace: {
external: Boolean(Math.random() * 2),
link: faker.lorem.slug(),
content: faker.lorem.sentences(),
},
});
......@@ -79,6 +79,9 @@ Articles.schema = new SimpleSchema(
'groups.$.name': {
type: { type: String },
},
'groups.$.type': {
type: { type: SimpleSchema.Integer },
},
createdAt: {
type: Date,
label: getLabel('api.articles.labels.createdAt'),
......
......@@ -4,12 +4,16 @@
import { PublicationCollector } from 'meteor/johanbrook:publication-collector';
import { assert } from 'chai';
import { Meteor } from 'meteor/meteor';
import { Roles } from 'meteor/alanning:roles';
import { _ } from 'meteor/underscore';
import '../../../startup/i18n/en.i18n.json';
import faker from 'faker';
import { Random } from 'meteor/random';
import { Factory } from 'meteor/dburles:factory';
import { Accounts } from 'meteor/accounts-base';
import Groups from '../../groups/groups';
import '../../groups/server/factories';
import { setMemberOf } from '../../users/server/methods';
import Articles from '../articles';
import {
......@@ -95,6 +99,120 @@ describe('articles', function () {
});
});
});
describe('group publications', function eventPublications() {
let userId;
let adminId;
let memberId;
let group;
let group2;
let groupId;
let group2Id;
beforeEach(function beforeTesting() {
Groups.remove({});
Articles.remove({});
Meteor.roleAssignment.remove({});
Meteor.roles.remove({});
Meteor.users.remove({});
Roles.createRole('admin');
Roles.createRole('member');
userId = Accounts.createUser({
username: 'randomguy',
password: 'toto',
email: faker.internet.email(),
structure: faker.company.companyName(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
groupCount: 0,
groupQuota: 10,
});
adminId = Accounts.createUser({
email: faker.internet.email(),
username: 'admin',
password: 'toto',
structure: faker.company.companyName(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
groupCount: 0,
groupQuota: 10,
});
memberId = Accounts.createUser({
email: faker.internet.email(),
username: 'member',
password: 'toto',
structure: faker.company.companyName(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
groupCount: 0,
groupQuota: 10,
});
Meteor.users.update({}, { $set: { isActive: true } }, { multi: true });
Roles.addUsersToRoles(adminId, 'admin');
group = Factory.create('group', { owner: adminId, type: 0 });
groupId = group._id;
group2 = Factory.create('group', { owner: adminId, type: 5 });
group2Id = group2._id;
setMemberOf._execute({ userId: memberId }, { userId: memberId, groupId });
setMemberOf._execute({ userId: adminId }, { userId: memberId, groupId: group2Id });
_.times(3, () => {
Factory.create('article', {
_id: Random.id(),
userId: adminId,
groups: [
{ _id: groupId, name: 'group', type: 0 },
{ _id: group2Id, name: 'group2', type: 5 },
],
});
});
});
describe('groups.articles', function eventsForUserPub() {
it('does send articles from a public group when not member', function groupArticlesPub(done) {
const collector = new PublicationCollector({ userId });
collector.collect(
'groups.articles',
{ page: 1, search: '', slug: group.slug, itemPerPage: 10 },
(collections) => {
assert.equal(collections.articles.length, 3);
done();
},
);
});
it('does not send articles from a protected group when not member', function groupArticlesProtected(done) {
const collector = new PublicationCollector({ userId });
collector.collect(
'groups.articles',
{ page: 1, search: '', slug: group2.slug, itemPerPage: 10 },
(collections) => {
assert.equal(collections.articles, undefined);
done();
},
);
});
it('does send articles from a protected group when admin', function groupArticlesProtectedAdmin(done) {
const collector = new PublicationCollector({ userId: adminId });
collector.collect(
'groups.articles',
{ page: 1, search: '', slug: group2.slug, itemPerPage: 10 },
(collections) => {
assert.equal(collections.articles.length, 3);
done();
},
);
});
it('does send articles from a protected group when member', function groupArticlesProtectedMember(done) {
const collector = new PublicationCollector({ userId: memberId });
collector.collect(
'groups.articles',
{ page: 1, search: '', slug: group2.slug, itemPerPage: 10 },
(collections) => {
assert.equal(collections.articles.length, 3);
done();
},
);
});
});
});
describe('methods', function () {
let userId;
let userStructure;
......
import { Meteor } from 'meteor/meteor';
import { Roles } from 'meteor/alanning:roles';
import { FindFromPublication } from 'meteor/percolate:find-from-publication';
import { publishComposite } from 'meteor/reywood:publish-composite';
import SimpleSchema from 'simpl-schema';
import logServer from '../../logging';
import Tags from '../../tags/tags';
import { checkPaginationParams, getLabel } from '../../utils';
import { checkPaginationParams, getLabel, isActive } from '../../utils';
import Articles from '../articles';
import Groups from '../../groups/groups';
// build query for all articles
const queryAllArticles = ({ nodrafts, search, userId }) => {
......@@ -131,3 +133,78 @@ publishComposite('articles.one', ({ slug }) => {
],
};
});
// build query for all articles from group
const queryGroupArticles = ({ search, group }) => {
const regex = new RegExp(search, 'i');
const fieldsToSearch = ['title', 'description'];
const searchQuery = fieldsToSearch.map((field) => ({
[field]: { $regex: regex },
groups: { $elemMatch: { _id: group._id } },
}));
return {
$or: searchQuery,
};
};
Meteor.methods({
'get_groups.articles_count': function getArticlesGroupCount({ search, slug }) {
try {
const group = Groups.findOne(
{ slug },
{
fields: Groups.allPublicFields,
limit: 1,
sort: { name: -1 },
},
);
const query = queryGroupArticles({ search, group });
return Articles.find(query).count();
} catch (error) {
return 0;
}
},
});
// publish all existing articles for specific group
FindFromPublication.publish('groups.articles', function groupsArticles({ page, search, slug, itemPerPage, ...rest }) {
if (!isActive(this.userId)) {
return this.ready();
}
try {
checkPaginationParams.validate({ page, itemPerPage, search });
} catch (err) {
logServer(`publish groups.articles : ${err}`);
this.error(err);
}
const group = Groups.findOne(
{ slug },
{
fields: Groups.allPublicFields,
limit: 1,
sort: { name: -1 },
},
);
// for protected/private groups, publish articles only for allowed users
if (
group === undefined ||
(group.type !== 0 && !Roles.userIsInRole(this.userId, ['admin', 'animator', 'member'], group._id))
) {
return this.ready();
}
try {
const query = queryGroupArticles({ search, group });
const res = Articles.find(query, {
fields: Articles.publicFields,
skip: itemPerPage * (page - 1),
limit: itemPerPage,
sort: { updatedAt: -1 },
...rest,
});
return res;
} catch (error) {
return this.ready();
}
});
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { getLabel } from '../utils';
const AsamExtensions = new Mongo.Collection('asamextensions');
AsamExtensions.deny({
insert() {
return true;
},
update() {
return true;
},
remove() {
return true;
},
});
/**
* About:
* `entiteNomCourt`, `entiteNomLong', `familleNomCourt`, `familleNomLong`
*
* These are used for helping admin user to think about the right structure to match
*
* These are not to be taken as truth or correct name for x or y structure
*/
AsamExtensions.schema = new SimpleSchema({
extension: {
type: String,
label: getLabel('api.asamextensions.labels.extension'),
min: 1,
},
entiteNomCourt: {
type: String,
label: getLabel('api.asamextensions.labels.entiteNomCourt'),
},
entiteNomLong: { type: String, label: getLabel('api.asamextensions.labels.entiteNomLong') },
familleNomCourt: { type: String, label: getLabel('api.asamextensions.labels.familleNomCourt') },
familleNomLong: { type: String, label: getLabel('api.asamextensions.labels.familleNomLong') },
structureId: {
type: SimpleSchema.RegEx.Id,
label: getLabel('api.asamextensions.labels.structureId'),
defaultValue: null,
},
});
AsamExtensions.publicFields = {
extension: 1,
entiteNomCourt: 1,
entiteNomLong: 1,
familleNomCourt: 1,
familleNomLong: 1,
structureId: 1,
};
export default AsamExtensions;
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import SimpleSchema from 'simpl-schema';
import i18n from 'meteor/universe:i18n';
import { Roles } from 'meteor/alanning:roles';
import { _ } from 'meteor/underscore';
import AsamExtensions from './asamextensions';
export const assignStructureToAsam = new ValidatedMethod({
name: 'asam.assignStructureToAsam',
validate: new SimpleSchema({
structureId: {
type: SimpleSchema.RegEx.Id,
},
extensionId: {
type: SimpleSchema.RegEx.Id,
},
extension: {
type: String,
optional: true,
},
entiteNomCourt: {
type: String,
optional: true,
},
entiteNomLong: {
type: String,
optional: true,
},
familleNomCourt: {
type: String,
optional: true,
},
familleNomLong: {
type: String,
optional: true,
},
}).validator(),
run({ extensionId, extension, entiteNomCourt, entiteNomLong, familleNomCourt, familleNomLong, structureId = null }) {
const isAdmin = Roles.userIsInRole(this.userId, 'admin');
if (!isAdmin) {
throw new Meteor.Error('api.asam.assignStructureToAsam.notPermitted', i18n.__('api.users.adminNeeded'));
}
return AsamExtensions.update(
{ _id: extensionId },
{ $set: { structureId, extension, entiteNomCourt, entiteNomLong, familleNomCourt, familleNomLong } },
);
},
});
export const unassignStructureToAsam = new ValidatedMethod({
name: 'asam.unassignStructureToAsam',
validate: new SimpleSchema({ extensionId: { type: SimpleSchema.RegEx.Id } }).validator(),
run({ extensionId }) {
const isAdmin = Roles.userIsInRole(this.userId, 'admin');
if (!isAdmin) {
throw new Meteor.Error('api.asam.assignStructureToAsam.notPermitted', i18n.__('api.users.adminNeeded'));
}
return AsamExtensions.update({ _id: extensionId }, { $set: { structureId: null } });
},
});
export const deleteAsam = new ValidatedMethod({
name: 'asam.deleteAsam',
validate: new SimpleSchema({ extensionId: { type: SimpleSchema.RegEx.Id } }).validator(),
run({ extensionId }) {
const isAdmin = Roles.userIsInRole(this.userId, 'admin');
if (!isAdmin) {
throw new Meteor.Error('api.asam.assignStructureToAsam.notPermitted', i18n.__('api.users.adminNeeded'));
}
return AsamExtensions.remove({ _id: extensionId });
},
});
export const addNewAsam = new ValidatedMethod({
name: 'asam.addNewAsam',
validate: new SimpleSchema({
extension: {
type: String,
optional: true,
},
entiteNomCourt: {
type: String,
optional: true,
},
entiteNomLong: {
type: String,
optional: true,
},
familleNomCourt: {
type: String,
optional: true,
},
familleNomLong: {
type: String,
optional: true,
},
structureId: {
type: String,
optional: true,
},
}).validator(),
run({ extension, entiteNomCourt, entiteNomLong, familleNomCourt, familleNomLong, structureId }) {
const isAdmin = Roles.userIsInRole(this.userId, 'admin');
if (!isAdmin) {
throw new Meteor.Error('api.asamextensions.notPermitted', i18n.__('api.users.adminNeeded'));
}
return AsamExtensions.insert({
extension,
entiteNomCourt,
entiteNomLong,
familleNomCourt,
familleNomLong,
structureId,
});
},
});
const LISTS_METHODS = _.pluck([assignStructureToAsam, unassignStructureToAsam, addNewAsam], 'name');
if (Meteor.isServer) {
// Only allow 5 list operations per connection per second
DDPRateLimiter.addRule(
{
name(name) {
return _.contains(LISTS_METHODS, name);
},
// Rate limit per connection ID
connectionId() {
return true;
},
},
5,
1000,
);
}
import { Meteor } from 'meteor/meteor';
import AsamExtensions from '../asamextensions';
Meteor.publish('asamextensions.all', function getAllAsam() {
const searchResult = AsamExtensions.find(
{},
{
fields: AsamExtensions.publicFields,
sort: { extension: 1 },
limit: 10000,
},
);
return searchResult;
});
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { getLabel } from '../utils';
const BusinessReGrouping = new Mongo.Collection('BusinessReGrouping');
// Deny all client-side updates since we will be using methods to manage this collection
BusinessReGrouping.deny({
insert() {
return true;
},
update() {
return true;
},
remove() {
return true;
},
});
BusinessReGrouping.schema = new SimpleSchema(
{
name: {
type: String,
min: 1,
label: getLabel('api.businessReGrouping.labels.name'),
},
structure: {
type: SimpleSchema.RegEx.Id,
label: getLabel('api.businessReGrouping.labels.structure'),
defaultValue: '',
},
},
{ tracker: Tracker },
);
BusinessReGrouping.publicFields = {
name: 1,
structure: 1,
};
BusinessReGrouping.attachSchema(BusinessReGrouping.schema);
export default BusinessReGrouping;
import { Meteor } from 'meteor/meteor';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { _ } from 'meteor/underscore';
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { Roles } from 'meteor/alanning:roles';
import i18n from 'meteor/universe:i18n';
import { isActive, getLabel } from '../utils';
import BusinessReGrouping from './businessReGrouping';
import Services from '../services/services';
import Structures from '../structures/structures';
export const createBusinessReGrouping = new ValidatedMethod({
name: 'BusinessReGrouping.createBusinessReGrouping',
structure: 'BusinessReGrouping.createBusinessReGrouping',
validate: new SimpleSchema({
name: { type: String, min: 1, label: getLabel('api.businessReGrouping.labels.name') },
structure: { type: String, min: 1, label: getLabel('api.businessReGrouping.labels.structure') },
}).validator(),
run({ name, structure }) {
const authorized =
isActive(this.userId) &&
(Roles.userIsInRole(this.userId, 'admin') || Roles.userIsInRole(this.userId, 'adminStructure', structure));
const businessRegr = BusinessReGrouping.findOne({ name, structure });
// Check if business regrouping exists in structure ancestors
const currStructure = Structures.findOne(structure);
const businessRegrForStructureAncestors = BusinessReGrouping.find({
structure: { $in: currStructure?.ancestorsIds },
}).fetch();
if (businessRegr !== undefined) {
throw new Meteor.Error(
'api.businessReGrouping.createBusinessReGrouping.alreadyExists',
i18n.__('api.businessReGrouping.createBusinessReGrouping.nameAlreadyUse'),
);
}
if (
businessRegrForStructureAncestors.length > 0 &&
businessRegrForStructureAncestors.some((rg) => rg.name === name)
) {
throw new Meteor.Error(
'api.businessReGrouping.createBusinessReGrouping.nameAlreadyUsedForOneOfStructureAncestors',
i18n.__('api.businessReGrouping.createBusinessReGrouping.nameAlreadyUsedForOneOfStructureAncestors'),
);
}
if (!authorized) {
throw new Meteor.Error(
'api.businessReGrouping.createBusinessReGrouping.notPermitted',
i18n.__('api.users.adminNeeded'),
);
}
BusinessReGrouping.insert({
name,
structure,
});
},
});
export const removeBusinessReGrouping = new ValidatedMethod({
name: 'BusinessReGrouping.removeBusinessReGrouping',
validate: new SimpleSchema({
businessReGroupingId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
label: getLabel('api.businessReGrouping.labels.id'),
},
structure: { type: String, min: 1, label: getLabel('api.businessReGrouping.labels.structure') },
}).validator(),
run({ businessReGroupingId, structure }) {
// check businessReGrouping existence
const businessReGrouping = BusinessReGrouping.findOne(businessReGroupingId);
if (businessReGrouping === undefined) {
throw new Meteor.Error(
'api.businessReGrouping.removeBusinessReGrouping.unknownBusinessReGrouping',
i18n.__('api.businessReGrouping.unknownBusinessReGrouping'),
);
}
// check if current user has admin rights
const authorized =
isActive(this.userId) &&
(Roles.userIsInRole(this.userId, 'admin') || Roles.userIsInRole(this.userId, 'adminStructure', structure));
if (!authorized) {
throw new Meteor.Error(
'api.businessReGrouping.removeBusinessReGrouping.notPermitted',
i18n.__('api.users.adminNeeded'),
);
}
// remove businessReGrouping from services
Services.update({}, { $pull: { businessReGrouping: businessReGroupingId } }, { multi: true });
BusinessReGrouping.remove(businessReGroupingId);
},
});
export const updateBusinessReGrouping = new ValidatedMethod({
name: 'BusinessReGrouping.updateBusinessReGrouping',
validate: new SimpleSchema({
businessReGroupingId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
label: getLabel('api.businessReGrouping.labels.id'),
},
data: Object,
'data.name': { type: String, min: 1, label: getLabel('api.businessReGrouping.labels.name') },
'data.structure': { type: String, min: 1, label: getLabel('api.businessReGrouping.labels.structure') },
}).validator(),
run({ businessReGroupingId, data }) {
// check businessReGrouping existence
const businessReGrouping = BusinessReGrouping.findOne({ _id: businessReGroupingId });
if (businessReGrouping === undefined) {
throw new Meteor.Error(
'api.businessReGrouping.updateBusinessReGrouping.unknownBusinessReGrouping',
i18n.__('api.businessReGrouping.unknownBusinessReGrouping'),
);
}
// check if current user has admin rights
const authorized =
isActive(this.userId) &&
(Roles.userIsInRole(this.userId, 'admin') || Roles.userIsInRole(this.userId, 'adminStructure', data.structure));
if (!authorized) {
throw new Meteor.Error(
'api.businessReGrouping.updateBusinessReGrouping.notPermitted',
i18n.__('api.users.adminNeeded'),
);
}
BusinessReGrouping.update({ _id: businessReGroupingId }, { $set: data });
},
});
// Get list of all method names on User
const LISTS_METHODS = _.pluck([createBusinessReGrouping, removeBusinessReGrouping, updateBusinessReGrouping], 'name');
if (Meteor.isServer) {
// Only allow 5 list operations per connection per second
DDPRateLimiter.addRule(
{
name(name) {
return _.contains(LISTS_METHODS, name);
},
// Rate limit per connection ID
connectionId() {
return true;
},
},
5,
1000,
);
}