Skip to content
Commits on Source (16)
......@@ -25,7 +25,8 @@
}
},
"globals": {
"msg": false
"msg": false,
"globalThis": false
},
"plugins": ["meteor", "import", "prettier", "i18n", "react", "jsx"],
"rules": {
......
......@@ -39,7 +39,7 @@ stages:
- !reference [.rules-map, on-dev]
- !reference [.rules-map, not-on-semantic-release-commit]
- !reference [.rules-map, on-branch]
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13.3
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.15
cache:
key:
files:
......@@ -71,7 +71,7 @@ cache-dependencies:
# This job update dependencies
policy: pull-push
script:
- meteor npm ci
- meteor npm install
###############################################################################
# `test` stage: `meteor-lint`, `meteor-tests`
......
......@@ -5,17 +5,17 @@
# but you can also edit it by hand.
meteor-base@1.5.1 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.16.7 # The database Meteor supports right now
mobile-experience@1.1.1 # Packages for a great mobile UX
mongo@1.16.8 # The database Meteor supports right now
jquery # Wrapper package for npm-installed jquery
reactive-var@1.0.12 # Reactive variable for tracker
tracker@1.3.2 # Meteor's client-side reactive programming library
tracker@1.3.3 # Meteor's client-side reactive programming library
standard-minifier-css@1.9.2 # CSS minifier run for production mode
standard-minifier-js@2.8.1 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers
ecmascript@0.16.7 # Enable ECMAScript2015+ syntax in app code
typescript@4.9.4 # Enable TypeScript syntax in .ts and .tsx modules
ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code
typescript@4.9.5 # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0 # Server-side component of the `meteor shell` command
svelte:compiler
......@@ -23,12 +23,12 @@ rdb:svelte-meteor-data
static-html@1.3.2
dburles:factory
johanbrook:publication-collector
accounts-base@2.2.8
accounts-base@2.2.10
aldeed:collection2
aldeed:schema-index
mdg:validated-method
meteortesting:mocha
accounts-password@2.3.4
accounts-password@2.4.0
alanning:roles
universe:i18n
percolate:find-from-publication
......
accounts-base@2.2.8
accounts-oauth@1.4.2
accounts-password@2.3.4
alanning:roles@3.6.0
accounts-base@2.2.10
accounts-oauth@1.4.3
accounts-password@2.4.0
alanning:roles@3.6.2
aldeed:collection2@3.5.0
aldeed:schema-index@3.0.0
aldeed:schema-index@3.1.0
allow-deny@1.1.1
autoupdate@1.8.0
babel-compiler@7.10.4
babel-compiler@7.10.5
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.1.3
boilerplate-generator@1.7.1
blaze-tools@1.1.4
boilerplate-generator@1.7.2
caching-compiler@1.2.2
caching-html-compiler@1.2.1
caching-html-compiler@1.2.2
callback-hook@1.5.1
check@1.3.2
dburles:factory@1.5.0
ddp@1.4.1
ddp-client@2.6.1
ddp-common@1.4.0
ddp-rate-limiter@1.2.0
ddp-server@2.6.2
ddp-rate-limiter@1.2.1
ddp-server@2.7.0
diff-sequence@1.1.2
dynamic-import@0.7.3
ecmascript@0.16.7
ecmascript@0.16.8
ecmascript-runtime@0.8.1
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.3
email@2.2.5
eoleteam:accounts-keycloak@2.1.0
eoleteam:keycloak-oauth@2.2.0
eoleteam:keycloak-oauth@2.3.0
es5-shim@4.8.0
fetch@0.1.3
fetch@0.1.4
geojson-utils@1.0.11
hot-code-push@1.0.4
html-tools@1.1.3
htmljs@1.1.1
html-tools@1.1.4
htmljs@1.2.1
http@2.0.0
id-map@1.1.1
inter-process-messaging@0.1.1
johanbrook:publication-collector@1.1.0
jquery@3.0.0
launch-screen@1.3.0
launch-screen@2.0.0
localstorage@1.2.0
logging@1.3.2
logging@1.3.3
mdg:validated-method@1.3.0
meteor@1.11.3
meteor@1.11.5
meteor-base@1.5.1
meteortesting:browser-tests@1.5.3
meteortesting:mocha@2.1.0
......@@ -55,17 +55,17 @@ meteortesting:mocha-core@8.1.2
minifier-css@1.6.4
minifier-js@2.7.5
minimongo@1.9.3
mobile-experience@1.1.0
mobile-experience@1.1.1
mobile-status-bar@1.1.0
modern-browsers@0.1.9
modules@0.19.0
modern-browsers@0.1.10
modules@0.20.0
modules-runtime@0.13.1
mongo@1.16.7
mongo@1.16.9
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.16.0
oauth@2.2.0
npm-mongo@4.17.2
oauth@2.2.1
oauth2@1.3.2
ordered-dict@1.1.0
percolate:find-from-publication@0.2.1
......@@ -74,27 +74,27 @@ raix:eventemitter@1.0.0
random@1.2.1
rate-limit@1.1.1
rdb:svelte-meteor-data@0.3.1
react-fast-refresh@0.2.7
react-fast-refresh@0.2.8
reactive-var@1.0.12
reload@1.3.1
retry@1.1.0
routepolicy@1.1.1
service-configuration@1.3.1
service-configuration@1.3.3
sha@1.0.9
shell-server@0.5.0
socket-stream-client@0.5.1
spacebars-compiler@1.3.1
socket-stream-client@0.5.2
spacebars-compiler@1.3.2
standard-minifier-css@1.9.2
standard-minifier-js@2.8.1
static-html@1.3.2
svelte:compiler@3.46.4
templating-tools@1.2.2
templating-tools@1.2.3
tmeasday:check-npm-versions@1.0.2
tracker@1.3.2
typescript@4.9.4
underscore@1.0.13
tracker@1.3.3
typescript@4.9.5
underscore@1.6.1
universe:i18n@1.32.6
url@1.3.2
webapp@1.13.5
webapp@1.13.8
webapp-hashing@1.1.1
zodern:types@1.0.10
zodern:types@1.0.13
# The tag here should match the Meteor version of your app, per .meteor/release
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13.3
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.15
# Copy app package.json and package-lock.json into container
#COPY ./app/package*.json $APP_SOURCE_FOLDER/
......
# Changelog
## [1.12.0](https://gitlab.mim-libre.fr/alphabet/mezig/compare/release/1.11.0...release/1.12.0) (2024-06-17)
### Features
* **about:** create about page ([861ab30](https://gitlab.mim-libre.fr/alphabet/mezig/commit/861ab3025568b4c9ba2bd2835e7e6ed4f9273767))
* **meteor:** update meteor and packages to 2.15 ([0977345](https://gitlab.mim-libre.fr/alphabet/mezig/commit/097734565eb976d3ffecd9e3bd8b13572c3f6418))
* **meteor:** update meteor to v2.14 and meteor packages ([1032919](https://gitlab.mim-libre.fr/alphabet/mezig/commit/103291910fa204385b6690ee92678a357725352f))
* **node:** update outdated npm packages ([6e08950](https://gitlab.mim-libre.fr/alphabet/mezig/commit/6e089507c5f38c69050f2c30bac5d5df6d2bf89b))
* **npm:** update npm outdated packages to wanted version ([3ddb544](https://gitlab.mim-libre.fr/alphabet/mezig/commit/3ddb54426c01dc31f5d75b36ffe27881d97ca9b2))
### Bug Fixes
* **ci:** update gitlab ci process ([ec3ee4e](https://gitlab.mim-libre.fr/alphabet/mezig/commit/ec3ee4e709e4fa72fef4f26df57d3281d0535813))
* **librairies:** fix errors in lint ([0a3b861](https://gitlab.mim-libre.fr/alphabet/mezig/commit/0a3b8617c985a4661130fe1a644a6a70a9f1d731))
* **libraries:** update vulnerable libraries and adapt application ([32a312b](https://gitlab.mim-libre.fr/alphabet/mezig/commit/32a312b4667a4d213e51910613acc22001a8f6bc))
## [1.11.0](https://gitlab.mim-libre.fr/alphabet/mezig/compare/release/1.10.0...release/1.11.0) (2024-01-30)
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { getLabel } from '../utils';
const AppSettings = new Mongo.Collection('appsettings');
......@@ -31,7 +30,7 @@ AppSettings.schema = new SimpleSchema(
label: getLabel('api.appsettings.labels.textMaintenance'),
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
{ clean: { removeEmptyStrings: false } },
);
AppSettings.publicFields = {
......
......@@ -7,6 +7,7 @@ import Mezigs from './mezigs';
import getFavicon from '../getFavicon';
import updateSkills from '../skills/server/methods';
import { validateString } from '../utils';
import RegEx from '../regExp';
Meteor.methods({
'mezigs.checkProfile': function checkProfile() {
......@@ -83,7 +84,7 @@ export const createMezig = new ValidatedMethod({
// export const updateProfilPic = new ValidatedMethod({
// name: 'mezigs.updateProfilPic',
// validate: new SimpleSchema({
// userId: { type: String, regEx: SimpleSchema.RegEx.Id },
// userId: { type: String, regEx: RegEx.Id },
// data: Mezigs.schema.omit('username', 'firstName', 'lastName'),
// }).validator({ clean: true }),
// run({ userId, data }) {
......@@ -95,7 +96,7 @@ export const createMezig = new ValidatedMethod({
export const updateMezig = new ValidatedMethod({
name: 'mezigs.updateMezig',
validate: new SimpleSchema({
mezigId: { type: String, regEx: SimpleSchema.RegEx.Id },
mezigId: { type: String, regEx: RegEx.Id },
data: Mezigs.schema.omit('username', 'firstName', 'lastName', 'structure'),
}).validator({ clean: true }),
async run({ mezigId, data }) {
......@@ -188,7 +189,7 @@ export const updateMezig = new ValidatedMethod({
export const removeMezig = new ValidatedMethod({
name: 'mezigs.removeMezig',
validate: new SimpleSchema({
mezigId: { type: String, regEx: SimpleSchema.RegEx.Id },
mezigId: { type: String, regEx: RegEx.Id },
}).validator({ clean: true }),
run({ mezigId }) {
// check if logged in
......
......@@ -2,9 +2,9 @@ import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { Factory } from 'meteor/dburles:factory';
import { Random } from 'meteor/random';
import RegEx from '../regExp';
const Mezigs = new Mongo.Collection('mezigs');
......@@ -21,87 +21,84 @@ Mezigs.deny({
},
});
Mezigs.schema = new SimpleSchema(
{
blacklist: {
type: Boolean,
optional: false,
defaultValue: false,
},
firstName: {
type: String,
optional: true,
},
lastName: {
type: String,
optional: true,
},
email: {
type: String,
regEx: SimpleSchema.RegEx.Email,
optional: true,
},
username: {
type: String,
optional: false,
unique: true,
},
publicName: {
type: String,
unique: true,
},
profilPic: {
type: String,
optional: true,
},
biography: {
type: String,
optional: true,
},
skills: {
type: Array,
optional: true,
defaultValue: [],
},
'skills.$': {
type: String,
},
links: {
type: Array,
optional: true,
defaultValue: [],
},
'links.$': {
type: Object,
},
'links.$.label': {
type: String,
},
'links.$.URL': {
type: String,
},
'links.$.isSocialNetwork': {
type: Boolean,
},
'links.$.isPublic': {
type: Boolean,
defaultValue: true,
},
'links.$.favicon': {
type: String,
defaultValue: '',
},
profileChecked: {
type: Boolean,
defaultValue: false,
},
structure: {
type: String,
defaultValue: '',
},
},
{ tracker: Tracker },
);
Mezigs.schema = new SimpleSchema({
blacklist: {
type: Boolean,
optional: false,
defaultValue: false,
},
firstName: {
type: String,
optional: true,
},
lastName: {
type: String,
optional: true,
},
email: {
type: String,
regEx: RegEx.Email,
optional: true,
},
username: {
type: String,
optional: false,
unique: true,
},
publicName: {
type: String,
unique: true,
},
profilPic: {
type: String,
optional: true,
},
biography: {
type: String,
optional: true,
},
skills: {
type: Array,
optional: true,
defaultValue: [],
},
'skills.$': {
type: String,
},
links: {
type: Array,
optional: true,
defaultValue: [],
},
'links.$': {
type: Object,
},
'links.$.label': {
type: String,
},
'links.$.URL': {
type: String,
},
'links.$.isSocialNetwork': {
type: Boolean,
},
'links.$.isPublic': {
type: Boolean,
defaultValue: true,
},
'links.$.favicon': {
type: String,
defaultValue: '',
},
profileChecked: {
type: Boolean,
defaultValue: false,
},
structure: {
type: String,
defaultValue: '',
},
});
Mezigs.searchFields = {
blacklist: 1,
......
// this domain regex matches all domains that have at least one .
// sadly IPv4 Adresses will be caught too but technically those are valid domains
// this expression is extracted from the original RFC 5322 mail expression
// a modification enforces that the tld consists only of characters
// this domain regex matches everythign that could be a domain in intranet
const rxDomain = '(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z](?:[a-z-]*[a-z])?';
// that means "localhost" is a valid domain
// strict IPv4 expression which allows 0-255 per oktett
const rxNameDomain = '(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.|$))+';
// strict IPv6 expression which allows (and validates) all shortcuts
const rxIPv4 = '(?:(?:[0-1]?\\d{1,2}|2[0-4]\\d|25[0-5])(?:\\.|$)){4}';
const rxIPv6 =
'(?:(?:[\\dA-Fa-f]{1,4}(?::|$)){8}' + // full adress
'|(?=(?:[^:\\s]|:[^:\\s])*::(?:[^:\\s]|:[^:\\s])*$)' + // or min/max one '::'
'[\\dA-Fa-f]{0,4}(?:::?(?:[\\dA-Fa-f]{1,4}|$)){1,6})'; // and short adress
// this allows domains (also localhost etc) and ip adresses
const rxWeakDomain = '(?:'.concat([rxNameDomain, rxIPv4, rxIPv6].join('|'), ')');
// min and max are used to set length boundaries
// set both for explicit lower and upper bounds
// set min as integer and max to null for explicit lower bound and arbitrary upper bound
// set none for arbitrary length
// set only min for fixed length
// character list: https://github.com/meteor/meteor/blob/release/0.8.0/packages/random/random.js#L88
// string length: https://github.com/meteor/meteor/blob/release/0.8.0/packages/random/random.js#L143
const isValidBound = function isValidBound(value, lower) {
return !value || (Number.isSafeInteger(value) && value > lower);
};
// unique id from the random package also used by minimongo
const idOfLength = function idOfLength(min, max) {
if (!isValidBound(min, 0)) throw new Error('Expected a non-negative safe integer, got '.concat(min));
if (!isValidBound(max, min))
throw new Error('Expected a non-negative safe integer greater than 1 and greater than min, got '.concat(max));
let bounds;
if (min && max) bounds = ''.concat(min, ',').concat(max);
else if (min && max === null) bounds = ''.concat(min, ',');
else if (min && !max) bounds = ''.concat(min);
else if (!min && !max) bounds = '0,';
else throw new Error('Unexpected state for min ('.concat(min, ') and max (').concat(max, ')'));
return new RegExp('^[23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz]{'.concat(bounds, '}$'));
};
const RegEx = {
// We use the RegExp suggested by W3C in http://www.w3.org/TR/html5/forms.html#valid-e-mail-address
// This is probably the same logic used by most browsers when type=email, which is our goal. It is
// a very permissive expression. Some apps may wish to be more strict and can write their own RegExp.
Email:
// eslint-disable-next-line max-len
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
// Like Email but requires the TLD (.com, etc)
EmailWithTLD:
// eslint-disable-next-line max-len
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
Domain: new RegExp('^'.concat(rxDomain, '$')),
WeakDomain: new RegExp('^'.concat(rxWeakDomain, '$')),
IP: new RegExp('^(?:'.concat(rxIPv4, '|').concat(rxIPv6, ')$')),
IPv4: new RegExp('^'.concat(rxIPv4, '$')),
IPv6: new RegExp('^'.concat(rxIPv6, '$')),
// URL RegEx from https://gist.github.com/dperini/729294
// DEPRECATED! Known 2nd degree polynomial ReDoS vulnerability.
// Use a custom validator such as this to validate URLs:
// custom() {
// if (!this.isSet) return;
// try {
// new URL(this.value);
// } catch (err) {
// return 'badUrl';
// }
// }
// eslint-disable-next-line max-len
Url: /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i,
// default id is defined with exact 17 chars of length
Id: idOfLength(17),
idOfLength,
// allows for a 5 digit zip code followed by a whitespace or dash and then 4 more digits
// matches 11111 and 11111-1111 and 11111 1111
ZipCode: /^\d{5}(?:[-\s]\d{4})?$/,
// taken from Google's libphonenumber library
// https://github.com/googlei18n/libphonenumber/blob/master/javascript/i18n/phonenumbers/phonenumberutil.js
// reference the VALID_PHONE_NUMBER_PATTERN key
// allows for common phone number symbols including + () and -
// DEPRECATED! Known 2nd degree polynomial ReDoS vulnerability.
// Instead, use a custom validation function, with a high quality
// phone number validation package that meets your needs.
Phone:
// eslint-disable-next-line max-len
/^[0-90-9٠-٩۰-۹]{2}$|^[++]*(?:[-x‐-―−ー--/ ­​⁠ ()()[].[\]/~⁓∼~*]*[0-90-9٠-٩۰-۹]){3,}[-x‐-―−ー--/ ­​⁠ ()()[].[\]/~⁓∼~*A-Za-z0-90-9٠-٩۰-۹]*(?:;ext=([0-90-9٠-٩۰-۹]{1,20})|[ \t,]*(?:e?xt(?:ensi(?:?))?n?|e?xtn?|доб|anexo)[:..]?[ \t,-]*([0-90-9٠-٩۰-۹]{1,20})#?|[ \t,]*(?:[xx##~~]|int|int)[:..]?[ \t,-]*([0-90-9٠-٩۰-۹]{1,9})#?|[- ]+([0-90-9٠-٩۰-۹]{1,6})#|[ \t]*(?:,{2}|;)[:..]?[ \t,-]*([0-90-9٠-٩۰-۹]{1,15})#?|[ \t]*(?:,)+[:..]?[ \t,-]*([0-90-9٠-٩۰-۹]{1,9})#?)?$/i, // eslint-disable-line no-irregular-whitespace
};
export default RegEx;
/* eslint-disable func-names */
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { Factory } from 'meteor/dburles:factory';
import { Random } from 'meteor/random';
......@@ -32,7 +31,7 @@ Skills.schema = new SimpleSchema(
min: 0,
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
{ clean: { removeEmptyStrings: false } },
);
Skills.publicFields = {
......
......@@ -5,11 +5,12 @@ import i18n from 'meteor/universe:i18n';
import { _ } from 'meteor/underscore';
import { getLabel, isActive } from '../utils';
import Structures from './structures';
import RegEx from '../regExp';
export const getAllChilds = new ValidatedMethod({
name: 'structures.getAllChilds',
validate: new SimpleSchema({
structureId: { type: String, regEx: SimpleSchema.RegEx.Id, label: getLabel('api.structures.labels.id') },
structureId: { type: String, regEx: RegEx.Id, label: getLabel('api.structures.labels.id') },
}).validator(),
run({ structureId }) {
const structure = Structures.findOne({ _id: structureId });
......@@ -43,7 +44,7 @@ export const getStructures = new ValidatedMethod({
export const getOneStructure = new ValidatedMethod({
name: 'structures.getOneStructure',
validate: new SimpleSchema({
structureId: { type: String, regEx: SimpleSchema.RegEx.Id, label: getLabel('api.structures.labels.id') },
structureId: { type: String, regEx: RegEx.Id, label: getLabel('api.structures.labels.id') },
}).validator(),
run({ structureId }) {
const structure = Structures.findOne({ _id: structureId });
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { getLabel } from '../utils';
import RegEx from '../regExp';
const Structures = new Mongo.Collection('structures');
......@@ -52,98 +52,93 @@ export const defaultIntroduction = [
},
];
Structures.schema = new SimpleSchema(
{
name: {
type: String,
min: 1,
label: getLabel('api.structures.labels.name'),
},
parentId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
label: getLabel('api.structures.labels.parentId'),
optional: true,
defaultValue: null,
},
childrenIds: {
type: Array,
label: getLabel('api.structures.labels.childrenIds'),
defaultValue: [],
},
'childrenIds.$': { type: String, regEx: SimpleSchema.RegEx.Id },
ancestorsIds: {
type: Array,
label: getLabel('api.structures.labels.ancestorsIds'),
defaultValue: [],
},
'ancestorsIds.$': { type: String, regEx: SimpleSchema.RegEx.Id },
introduction: {
type: Array,
label: getLabel('api.structures.labels.introduction.label'),
defaultValue: defaultIntroduction,
},
'introduction.$': {
type: IntroductionSchema,
},
contactEmail: {
type: String,
label: getLabel('api.structures.labels.contactEmail'),
optional: true,
defaultValue: null,
regEx: SimpleSchema.RegEx.Email,
},
groupId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
label: getLabel('api.structures.labels.groupId'),
optional: true,
defaultValue: null,
},
asamExtensionsIds: {
type: Array,
defaultValue: [],
label: getLabel('api.structures.labels.asamExtensionsIds'),
},
'asamExtensionsIds.$': { type: String, regEx: SimpleSchema.RegEx.Id },
userStructureValidationMandatory: {
type: Boolean,
optional: true,
label: getLabel('api.appsettings.label.userStructureValidationMandatory'),
},
iconUrlImage: {
type: String,
label: getLabel('api.structures.labels.iconUrlImage'),
optional: true,
},
coverUrlImage: {
type: String,
label: getLabel('api.structures.labels.coverUrlImage'),
optional: true,
},
externalUrl: {
type: String,
label: getLabel('api.structures.labels.externalUrl'),
optional: true,
defaultValue: null,
},
sendMailToParent: {
type: Boolean,
optional: true,
defaultValue: false,
label: getLabel('api.appsettings.label.sendMailToParent'),
},
sendMailToStructureAdmin: {
type: Boolean,
optional: true,
defaultValue: false,
label: getLabel('api.appsettings.label.sendMailToStructureAdmin'),
},
Structures.schema = new SimpleSchema({
name: {
type: String,
min: 1,
label: getLabel('api.structures.labels.name'),
},
{
tracker: Tracker,
parentId: {
type: String,
regEx: RegEx.Id,
label: getLabel('api.structures.labels.parentId'),
optional: true,
defaultValue: null,
},
childrenIds: {
type: Array,
label: getLabel('api.structures.labels.childrenIds'),
defaultValue: [],
},
'childrenIds.$': { type: String, regEx: RegEx.Id },
ancestorsIds: {
type: Array,
label: getLabel('api.structures.labels.ancestorsIds'),
defaultValue: [],
},
'ancestorsIds.$': { type: String, regEx: RegEx.Id },
introduction: {
type: Array,
label: getLabel('api.structures.labels.introduction.label'),
defaultValue: defaultIntroduction,
},
'introduction.$': {
type: IntroductionSchema,
},
contactEmail: {
type: String,
label: getLabel('api.structures.labels.contactEmail'),
optional: true,
defaultValue: null,
regEx: RegEx.Email,
},
groupId: {
type: String,
regEx: RegEx.Id,
label: getLabel('api.structures.labels.groupId'),
optional: true,
defaultValue: null,
},
);
asamExtensionsIds: {
type: Array,
defaultValue: [],
label: getLabel('api.structures.labels.asamExtensionsIds'),
},
'asamExtensionsIds.$': { type: String, regEx: RegEx.Id },
userStructureValidationMandatory: {
type: Boolean,
optional: true,
label: getLabel('api.appsettings.label.userStructureValidationMandatory'),
},
iconUrlImage: {
type: String,
label: getLabel('api.structures.labels.iconUrlImage'),
optional: true,
},
coverUrlImage: {
type: String,
label: getLabel('api.structures.labels.coverUrlImage'),
optional: true,
},
externalUrl: {
type: String,
label: getLabel('api.structures.labels.externalUrl'),
optional: true,
defaultValue: null,
},
sendMailToParent: {
type: Boolean,
optional: true,
defaultValue: false,
label: getLabel('api.appsettings.label.sendMailToParent'),
},
sendMailToStructureAdmin: {
type: Boolean,
optional: true,
defaultValue: false,
label: getLabel('api.appsettings.label.sendMailToStructureAdmin'),
},
});
Structures.publicFields = {
_id: 1,
......
import { Meteor } from 'meteor/meteor';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { Roles } from 'meteor/alanning:roles';
import i18n from 'meteor/universe:i18n';
import { getLabel } from '../utils';
import checkDomain from '../domains';
import logServer from '../logging';
import RegEx from '../regExp';
const AppRoles = ['admin'];
......@@ -36,7 +36,7 @@ Meteor.users.schema = new SimpleSchema(
},
'emails.$.address': {
type: String,
regEx: SimpleSchema.RegEx.Email,
regEx: RegEx.Email,
label: getLabel('api.users.labels.emailAddress'),
},
'emails.$.verified': {
......@@ -79,7 +79,7 @@ Meteor.users.schema = new SimpleSchema(
label: getLabel('api.users.labels.favServices'),
},
'favServices.$': {
type: { type: String, regEx: SimpleSchema.RegEx.Id },
type: { type: String, regEx: RegEx.Id },
},
favGroups: {
type: Array,
......@@ -87,7 +87,7 @@ Meteor.users.schema = new SimpleSchema(
label: getLabel('api.users.labels.favGroups'),
},
'favGroups.$': {
type: { type: String, regEx: SimpleSchema.RegEx.Id },
type: { type: String, regEx: RegEx.Id },
},
favUserBookmarks: {
type: Array,
......@@ -95,7 +95,7 @@ Meteor.users.schema = new SimpleSchema(
label: getLabel('api.users.labels.favUserBookmarks'),
},
'favUserBookmarks.$': {
type: { type: String, regEx: SimpleSchema.RegEx.Id },
type: { type: String, regEx: RegEx.Id },
},
structure: {
type: String,
......@@ -104,7 +104,7 @@ Meteor.users.schema = new SimpleSchema(
},
primaryEmail: {
type: String,
regEx: SimpleSchema.RegEx.Email,
regEx: RegEx.Email,
optional: true,
label: getLabel('api.users.labels.primaryEmail'),
},
......@@ -166,7 +166,7 @@ Meteor.users.schema = new SimpleSchema(
label: getLabel('api.users.labels.articlesEnable'),
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
{ clean: { removeEmptyStrings: false } },
);
if (Meteor.isServer) {
......
......@@ -3,6 +3,7 @@ import i18n from 'meteor/universe:i18n';
import SimpleSchema from 'simpl-schema';
import Mezigs from './mezigs/mezigs';
import Skills from './skills/skills';
import RegEx from './regExp';
export function isActive(userId) {
if (!userId) return false;
......@@ -23,60 +24,62 @@ export const checkPaginationParams = new SimpleSchema({
export function registerSchemaMessages() {
const regExpMessages = [
{ exp: SimpleSchema.RegEx.Email, msg: 'SimpleSchema.RegEx.Email' },
{ exp: SimpleSchema.RegEx.EmailWithTLD, msg: 'SimpleSchema.RegEx.EmailWithTLD' },
{ exp: SimpleSchema.RegEx.Domain, msg: 'SimpleSchema.RegEx.Domain' },
{ exp: SimpleSchema.RegEx.WeakDomain, msg: 'SimpleSchema.RegEx.WeakDomain' },
{ exp: SimpleSchema.RegEx.IP, msg: 'SimpleSchema.RegEx.IP' },
{ exp: SimpleSchema.RegEx.IPv4, msg: 'SimpleSchema.RegEx.IPv4' },
{ exp: SimpleSchema.RegEx.IPv6, msg: 'SimpleSchema.RegEx.IPv6' },
{ exp: SimpleSchema.RegEx.Url, msg: 'SimpleSchema.RegEx.Url' },
{ exp: SimpleSchema.RegEx.Id, msg: 'SimpleSchema.RegEx.Id' },
{ exp: SimpleSchema.RegEx.ZipCode, msg: 'SimpleSchema.RegEx.ZipCode' },
{ exp: SimpleSchema.RegEx.Phone, msg: 'SimpleSchema.RegEx.Phone' },
{ exp: RegEx.Email, msg: 'SimpleSchema.RegEx.Email' },
{ exp: RegEx.EmailWithTLD, msg: 'SimpleSchema.RegEx.EmailWithTLD' },
{ exp: RegEx.Domain, msg: 'SimpleSchema.RegEx.Domain' },
{ exp: RegEx.WeakDomain, msg: 'SimpleSchema.RegEx.WeakDomain' },
{ exp: RegEx.IP, msg: 'SimpleSchema.RegEx.IP' },
{ exp: RegEx.IPv4, msg: 'SimpleSchema.RegEx.IPv4' },
{ exp: RegEx.IPv6, msg: 'SimpleSchema.RegEx.IPv6' },
{ exp: RegEx.Url, msg: 'SimpleSchema.RegEx.Url' },
{ exp: RegEx.Id, msg: 'SimpleSchema.RegEx.Id' },
{ exp: RegEx.ZipCode, msg: 'SimpleSchema.RegEx.ZipCode' },
{ exp: RegEx.Phone, msg: 'SimpleSchema.RegEx.Phone' },
];
SimpleSchema.setDefaultMessages({
messages: {
en: {
required: (ctx) => i18n.__('SimpleSchema.required', ctx),
minString: (ctx) => i18n.__('SimpleSchema.minString', ctx),
maxString: (ctx) => i18n.__('SimpleSchema.maxString', ctx),
minNumber: (ctx) => i18n.__('SimpleSchema.minNumber', ctx),
maxNumber: (ctx) => i18n.__('SimpleSchema.maxNumber', ctx),
minNumberExclusive: (ctx) => i18n.__('SimpleSchema.minNumberExclusive', ctx),
maxNumberExclusive: (ctx) => i18n.__('SimpleSchema.maxNumberExclusive', ctx),
minDate: (ctx) => i18n.__('SimpleSchema.minDate', ctx),
maxDate: (ctx) => i18n.__('SimpleSchema.maxDate', ctx),
badDate: (ctx) => i18n.__('SimpleSchema.badDate', ctx),
minCount: (ctx) => i18n.__('SimpleSchema.minCount', ctx),
maxCount: (ctx) => i18n.__('SimpleSchema.maxCount', ctx),
noDecimal: (ctx) => i18n.__('SimpleSchema.noDecimal', ctx),
notAllowed: (ctx) => i18n.__('SimpleSchema.notAllowed', ctx),
expectedType: (ctx) => {
const finalCtx = { ...ctx };
const i18nEntry = `SimpleSchema.dataTypes.${ctx.dataType}`;
const typeTranslated = i18n.__(i18nEntry);
if (typeTranslated !== i18nEntry) {
// translatation for type is available
finalCtx.dataType = typeTranslated;
}
return i18n.__('SimpleSchema.expectedType', finalCtx);
},
keyNotInSchema: (ctx) => i18n.__('SimpleSchema.keyNotInSchema', ctx),
regEx({ label, regExp }) {
// See if there's one where exp matches this expression
let msgObj;
if (regExp) {
msgObj = regExpMessages.find((o) => o.exp && o.exp.toString() === regExp);
}
const regExpMessage = msgObj ? i18n.__(msgObj.msg) : i18n.__('SimpleSchema.RegEx.Default');
return `${label} ${regExpMessage}`;
},
},
const customMessages = {
required: (ctx, label) => i18n.__('SimpleSchema.required', { ...ctx, label }),
minString: (ctx, label) => i18n.__('SimpleSchema.minString', { ...ctx, label }),
maxString: (ctx, label) => i18n.__('SimpleSchema.maxString', { ...ctx, label }),
minNumber: (ctx, label) => i18n.__('SimpleSchema.minNumber', { ...ctx, label }),
maxNumber: (ctx, label) => i18n.__('SimpleSchema.maxNumber', { ...ctx, label }),
minNumberExclusive: (ctx, label) => i18n.__('SimpleSchema.minNumberExclusive', { ...ctx, label }),
maxNumberExclusive: (ctx, label) => i18n.__('SimpleSchema.maxNumberExclusive', { ...ctx, label }),
minDate: (ctx, label) => i18n.__('SimpleSchema.minDate', { ...ctx, label }),
maxDate: (ctx, label) => i18n.__('SimpleSchema.maxDate', { ...ctx, label }),
badDate: (ctx, label) => i18n.__('SimpleSchema.badDate', { ...ctx, label }),
minCount: (ctx, label) => i18n.__('SimpleSchema.minCount', { ...ctx, label }),
maxCount: (ctx, label) => i18n.__('SimpleSchema.maxCount', { ...ctx, label }),
noDecimal: (ctx, label) => i18n.__('SimpleSchema.noDecimal', { ...ctx, label }),
notAllowed: (ctx, label) => i18n.__('SimpleSchema.notAllowed', { ...ctx, label }),
expectedType: (ctx, label) => {
const finalCtx = { ...ctx, label };
const i18nEntry = `SimpleSchema.dataTypes.${ctx.dataType}`;
const typeTranslated = i18n.__(i18nEntry);
if (typeTranslated !== i18nEntry) {
// translatation for type is available
finalCtx.dataType = typeTranslated;
}
return i18n.__('SimpleSchema.expectedType', finalCtx);
},
keyNotInSchema: (ctx, label) => i18n.__('SimpleSchema.keyNotInSchema', { ...ctx, label }),
regEx: (ctx, label) => {
// See if there's one where exp matches this expression
let msgObj;
if (ctx.regExp) {
msgObj = regExpMessages.find((o) => o.exp && o.exp.toString() === ctx.regExp);
}
const regExpMessage = msgObj ? i18n.__(msgObj.msg) : i18n.__('SimpleSchema.RegEx.Default');
return `${label} ${regExpMessage}`;
},
};
globalThis.simpleSchemaGlobalConfig = {
getErrorMessage(error, label) {
if (typeof customMessages[error.type] === 'function') {
return customMessages[error.type](error, label);
}
return undefined;
},
});
};
}
export function genRandomPassword(pwdlen = 16) {
......
......@@ -4,6 +4,7 @@
import { Meteor } from 'meteor/meteor';
import { useTracker } from 'meteor/rdb:svelte-meteor-data';
import { _ } from 'svelte-i18n';
import { SvelteToast } from '@zerodevx/svelte-toast';
import Matomo, { matomo } from '@dexlib/svelte-matomo';
import Search from './pages/Search.svelte';
import Profil from './pages/Profil.svelte';
......@@ -15,6 +16,7 @@
import Admin from './pages/Admin.svelte';
import Maintenance from './pages/Maintenance.svelte';
import NoAccount from './pages/NoAccount.svelte';
import About from './pages/About.svelte';
import AppSettings from './../api/appsettings/appsettings';
import { onMount } from 'svelte';
......@@ -43,12 +45,15 @@
<Spinner />
{:else if userRedirect === false}
<Nav bind:userRedirect bind:profileOk bind:userActive />
<SvelteToast />
<Router {url}>
<Route path="/about" component={About} />
<div class="container">
{#if $settings.maintenance}
<Maintenance />
{:else if profileOk === false && userActive === true}
<div>
userActive
<Route>
<EditProfil bind:profileOk />
</Route>
......
<script>
import { _ } from 'svelte-i18n';
import { toast } from '@zerodevx/svelte-toast';
export let showModal;
export let browser;
export let os;
export let platform;
let dialog; // HTMLDialogElement
const handleClickModal = () => {
navigator.clipboard.writeText(
`Navigateur: ${browser.name},
Version: ${JSON.stringify(browser.version)},
Os: ${JSON.stringify(os.name)},
Appareil: ${JSON.stringify(platform.type)}`,
);
toast.push($_('ui.AboutPage.Modal.success'));
dialog.close();
};
$: if (dialog && showModal) dialog.showModal();
</script>
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-noninteractive-element-interactions -->
<dialog bind:this={dialog} on:close={() => (showModal = false)} on:click|self={() => dialog.close()}>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div on:click|stopPropagation>
<slot name="header" />
<slot />
<!-- svelte-ignore a11y-autofocus -->
<button class="buttonInfos" autofocus on:click={() => dialog.close()}>{$_('ui.AboutPage.Modal.close')}</button>
<button class="buttonInfos" on:click={() => handleClickModal()}>{$_('ui.AboutPage.Modal.copy')}</button>
</div>
</dialog>
<style>
dialog {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 20px;
}
.buttonInfos {
align-items: center;
justify-content: center;
vertical-align: middle;
font-weight: 500;
font-size: 0.875rem;
line-height: 1.75;
text-transform: uppercase;
min-width: 64px;
padding: 6px 16px;
border-radius: 8px;
color: #fff;
background-color: #011caa;
border: none;
cursor: pointer;
}
</style>
......@@ -158,8 +158,11 @@
</Text>
</Item>
<Separator />
<Item disabled>
<AppVersion />
<Item on:SMUI:action={() => navigate('/about', { replace: false })}>
<Text class="MenuText">
<PrimaryText>{$_('ui.about')}</PrimaryText>
<SecondaryText />
</Text>
</Item>
</List>
</Menu>
......
<script>
import { _ } from 'svelte-i18n';
import Bowser from 'bowser';
import PackageJSON from '../../../package.json';
import Modal from '../components/Modal.svelte';
let version = PackageJSON.version;
const bowser = Bowser.parse(window.navigator.userAgent);
const { browser, os, platform } = bowser;
let showModal = false;
</script>
<svelte:head>
<title>{$_('ui.about')}</title>
</svelte:head>
<div class="containerPaper">
<div class="imgContainer">
<img class="imageSize" src="/favicon.ico" alt="puce eole" />
</div>
<div class="textZone">
<h1 class="title is-1">
<i class="titleH1" style="color: #372F84">Mezig - version {version}</i>
</h1>
<p>
{$_('ui.AboutPage.developped')}{' '}
<a title="EUPL 1.2" target="_blank" rel="noreferrer noopener" href="https://eupl.eu/1.2/fr/"> EUPL 1.2 </a>{' '}
{$_('ui.AboutPage.socle')}{' '}
<a title="EOLE 3" target="_blank" rel="noreferrer noopener" href="https://pcll.ac-dijon.fr/eole/eole-3/">
EOLE³
</a>
</p>
<p>
{$_('ui.AboutPage.by')}{' '}
<a title="PCLL" target="_blank" rel="noreferrer noopener" href="https://pcll.ac-dijon.fr/pcll/">
Pôle de Compétences Logiciels Libres
</a>{' '}
{$_('ui.AboutPage.and')}{' '}
<a title="MENJ" target="_blank" rel="noreferrer noopener" href="https://www.education.gouv.fr/">
Ministère de l&apos;Éducation Nationale et de la Jeunesse
</a>{' '}
{$_('ui.AboutPage.contributions')}{' '}
<a title="DINUM" target="_blank" rel="noreferrer noopener" href="https://www.numerique.gouv.fr/dinum/">
Direction Interministérielle du Numérique
</a>{' '}
{$_('ui.AboutPage.external')}
</p>
<p>
{$_('ui.AboutPage.links')}{' '}
<a title="wiki eole" target="_blank" rel="noreferrer noopener" href="https://wiki.eole.education/">
documentation de l'application.
</a>
</p>
<p>
{$_('ui.AboutPage.exchange')}{' '}
<a title={$_('ui.AboutPage.chat')} target="_blank" rel="noreferrer noopener" href="https://chat.mim-libre.fr">
{$_('ui.AboutPage.chat')}.
</a>
</p>
<p>
{$_('ui.AboutPage.news')}{' '}
<a title="Mastodon" target="_blank" rel="noreferrer noopenner" href="https://mastodon.eole.education/@EOLE">
Mastodon.
</a>
</p>
<p>
{$_('ui.AboutPage.contributing')}{' '}
<a
title={$_('ui.AboutPage.deposit')}
target="_blank"
rel="noreferrer noopenner"
href="https://gitlab.mim-libre.fr/alphabet/mezig"
>
{$_('ui.AboutPage.deposit')}.
</a>
</p>
<br />
<button class="buttonInfos" on:click={() => (showModal = true)}>
{$_('ui.AboutPage.information')}
</button>
</div>
</div>
<Modal {browser} {os} {platform} bind:showModal>
<p>
{$_('ui.AboutPage.Modal.navigator')}
{JSON.stringify(browser.name)}
</p>
<p>
{$_('ui.AboutPage.Modal.version')}
{JSON.stringify(browser.version)}
</p>
<p>
{$_('ui.AboutPage.Modal.os')}
{JSON.stringify(os.name)}
</p>
<p>
{$_('ui.AboutPage.Modal.device')}
{JSON.stringify(platform.type)}
</p>
</Modal>
<style>
.imageSize {
height: 10vw;
place-content: center;
}
.containerPaper {
display: flex;
flex-direction: row;
width: fit-content;
margin: 5vw auto;
height: 50vh;
padding: 10;
place-items: center;
background-color: #fff;
color: #040d3e;
-webkit-transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
border-radius: 8px;
box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);
}
.imgContainer {
display: flex;
width: 25%;
justify-content: center;
}
.textZone {
width: 50vw;
}
.buttonInfos {
align-items: center;
justify-content: center;
vertical-align: middle;
font-weight: 500;
font-size: 0.875rem;
line-height: 1.75;
text-transform: uppercase;
min-width: 64px;
padding: 6px 16px;
border-radius: 8px;
color: #fff;
background-color: #011caa;
border: none;
cursor: pointer;
}
@media screen and (max-width: 768px) {
.containerPaper {
padding: 6px 0px;
overflow: auto;
flex-direction: column;
margin: 1vw auto;
}
.buttonInfos {
margin-bottom: 5vh;
}
.textZone {
width: 90%;
}
}
</style>