Skip to content
Commits on Source (27)
......@@ -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
before_script:
- cd app
cache:
......@@ -73,7 +73,7 @@ cache-dependencies:
# This job update dependencies
policy: pull-push
script:
- meteor npm ci
- meteor npm install
###############################################################################
# `test` stage: `meteor-lint`, `meteor-tests`
......
# 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/
......
......@@ -24,7 +24,8 @@
}
},
"globals": {
"msg": false
"msg": false,
"globalThis": false
},
"plugins": ["meteor", "import", "prettier", "i18n", "react", "jsx"],
"ignorePatterns": ["packages/**/*.js", "packages/**/*.jsx"],
......
......@@ -5,15 +5,15 @@
# 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
reactive-var@1.0.12 # Reactive variable for tracker
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
hot-module-replacement@0.5.3 # Update client in development without reloading the page
......@@ -25,7 +25,7 @@ aldeed:collection2
aldeed:schema-index
mdg:validated-method
universe:i18n
accounts-password@2.3.4
accounts-password@2.4.0
# testing
meteortesting:mocha
......@@ -33,7 +33,7 @@ hwillson:stub-collections
johanbrook:publication-collector
dburles:factory
random@1.2.1
service-configuration@1.3.1
service-configuration@1.3.3
matb33:collection-hooks
email@2.2.5
......
accounts-base@2.2.8
accounts-oauth@1.4.2
accounts-password@2.3.4
accounts-base@2.2.10
accounts-oauth@1.4.3
accounts-password@2.4.0
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:collection-helpers@1.1.0
......@@ -20,36 +20,36 @@ 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
hot-module-replacement@0.5.3
html-tools@1.1.3
htmljs@1.1.1
html-tools@1.1.4
htmljs@1.2.1
http@2.0.0
hwillson:stub-collections@1.0.9
id-map@1.1.1
inter-process-messaging@0.1.1
johanbrook:publication-collector@1.1.0
launch-screen@1.3.0
launch-screen@2.0.0
localstorage@1.2.0
logging@1.3.2
matb33:collection-hooks@1.3.0
logging@1.3.3
matb33:collection-hooks@1.3.1
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
......@@ -58,45 +58,45 @@ mexar:mdt@0.2.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
modules-runtime-hot@0.14.2
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
promise@0.12.2
raix:eventemitter@1.0.0
random@1.2.1
rate-limit@1.1.1
react-fast-refresh@0.2.7
react-fast-refresh@0.2.8
react-meteor-data@2.7.2
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
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
......@@ -21,6 +21,31 @@
}
},
"pages": {
"AboutPage": {
"developped": "Agenda is a service from LaBoite licensed as",
"socle": "part of the base",
"by": "Made by",
"and": "and",
"contributions": "in partnership with the",
"external": "and external contributors.",
"links": "You can consult the",
"exchange": "You can communicate with us on the",
"chat": "Mim-libre chat",
"news": "Find the latest information on ",
"contributing": "Want to participate in improving the portal? Find the project on ",
"deposit": "Gitlab Mim-libre",
"information": "Troubleshooting Information",
"Modal": {
"information": "Your informations",
"navigator": "Navigator :",
"version": "Version :",
"os": "Os :",
"device": "Device :",
"close": "Close",
"copy": "Copy informations",
"success": "Copy success"
}
},
"AddEvent": {
"title": "Add a new event",
"validateButton": "Validate",
......@@ -101,11 +126,14 @@
"timeGridWeek": "Week",
"dayGridMonth": "Month",
"importButton": "Import",
"importButtonTooltip": "Import a calendar",
"exportButton": "Export",
"exportButtonTooltip": "Export this view",
"displayWeekendButton": "Show Week-End",
"cantMoveRecurrent": "Recurrent event can not be moved",
"eventsImported": "Events imported successfully",
"importPending": "Import in progress..."
"importPending": "Import in progress...",
"importError": "Unable to import calendar"
},
"Footer": {
"legal": "Legal",
......@@ -122,7 +150,8 @@
"menuLogoutLabel": "Logout",
"menuAddEvent": "Add event",
"copyCaldavUrl": "My Caldav URL",
"successCopyCaldav": "URL copied successfully"
"successCopyCaldav": "URL copied successfully",
"menuAboutLabel": "About"
},
"AppVersion": {
"title": "Version"
......
......@@ -21,6 +21,31 @@
}
},
"pages": {
"AboutPage": {
"developped": "Agenda est un service de LaBoite sous licence",
"socle": "faisant partie du socle",
"by": "Réalisation du",
"and": "du",
"contributions": "en partenariat avec la",
"external": "et des contributeurs externes.",
"links": "Vous pouvez consulter la",
"exchange": "Vous pouvez échanger avec nous sur le",
"chat": "chat Mim-libre",
"news": "Retrouvez les dernières informations du pôle sur ",
"contributing": "Envie de participer à l'amélioration du portail ? Retouvez le projet sur la",
"deposit": "forge Mim-libre",
"information": "Informations de dépannage",
"Modal": {
"information": "Vos informations",
"navigator": "Navigateur :",
"version": "Version :",
"os": "Os :",
"device": "Appareil :",
"close": "Fermer",
"copy": "Copier les informations",
"success": "Informations copié avec succès"
}
},
"AddEvent": {
"title": "Ajouter un évènement",
"validateButton": "Valider",
......@@ -100,12 +125,15 @@
"listWeek": "Liste",
"timeGridWeek": "Semaine",
"dayGridMonth": "Mois",
"importButton": "Import",
"exportButton": "Export",
"importButton": "Importer",
"importButtonTooltip": "Importer un calendrier",
"exportButton": "Exporter",
"exportButtonTooltip": "Exporter la vue courante",
"displayWeekendButton": "Week-Ends",
"cantMoveRecurrent": "Un évènement récurrent ne peut pas être déplacé",
"eventsImported": "Évènements importés avec succès",
"importPending": "Import en cours..."
"importPending": "Import en cours...",
"importError": "Impossible d'importer l'agenda"
},
"Footer": {
"legal": "Mentions légales",
......@@ -122,7 +150,8 @@
"menuLogoutLabel": "Se déconnecter",
"menuAddEvent": "Ajouter évènement",
"copyCaldavUrl": "Mon URL Caldav",
"successCopyCaldav": "Url copié avec succès"
"successCopyCaldav": "Url copié avec succès",
"menuAboutLabel": "À propos"
},
"AppVersion": {
"title": "Version"
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { getLabel } from '../utils/functions';
const AppSettings = new Mongo.Collection('appsettings');
......@@ -66,7 +65,7 @@ AppSettings.schema = new SimpleSchema(
label: getLabel('api.appsettings.labels.personal_data'),
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
{ clean: { removeEmptyStrings: false } },
);
AppSettings.publicFields = {
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import moment from 'moment';
import { getLabel } from '../utils/functions';
import RegEx from '../utils/regExp';
export const EventsAgenda = new Mongo.Collection('eventsAgenda');
......@@ -46,138 +46,135 @@ const settingsParticipant = new SimpleSchema({
},
groupId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
optional: true,
},
});
EventsAgenda.schema = new SimpleSchema(
{
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
min: 1,
},
eventType: {
type: String,
optional: true,
},
title: {
type: String,
},
location: {
type: String,
optional: true,
},
recurrent: {
type: Boolean,
defaultValue: false,
},
daysOfWeek: {
type: Array,
optional: true,
},
'daysOfWeek.$': {
type: Number,
optional: true,
},
description: {
type: String,
optional: true,
},
// startRecur: {
// type: Date,
// optional: true,
// },
// endRecur: {
// type: Date,
// optional: true,
// },
start: {
type: Date,
},
end: {
type: Date,
},
startTime: {
type: String,
optional: true,
autoValue() {
if (!this.field('allDay').value && this.field('recurrent').value) {
return moment(this.field('start').value).format('HH:mm');
}
return this.unset() || null;
},
},
endTime: {
type: String,
optional: true,
autoValue() {
if (!this.field('allDay').value && this.field('recurrent').value) {
return moment(this.field('end').value).format('HH:mm');
}
return this.unset() || null;
},
},
allDay: {
type: Boolean,
defaultValue: false,
},
groups: {
type: Array,
defaultValue: [],
},
'groups.$': {
type: settingsGroup,
optional: true,
},
participants: {
type: Array,
defaultValue: [],
},
'participants.$': {
type: settingsParticipant,
optional: true,
},
guests: {
type: Array,
defaultValue: [],
},
'guests.$': {
type: String,
optional: true,
EventsAgenda.schema = new SimpleSchema({
_id: {
type: String,
regEx: RegEx.Id,
min: 1,
},
eventType: {
type: String,
optional: true,
},
title: {
type: String,
},
location: {
type: String,
optional: true,
},
recurrent: {
type: Boolean,
defaultValue: false,
},
daysOfWeek: {
type: Array,
optional: true,
},
'daysOfWeek.$': {
type: Number,
optional: true,
},
description: {
type: String,
optional: true,
},
// startRecur: {
// type: Date,
// optional: true,
// },
// endRecur: {
// type: Date,
// optional: true,
// },
start: {
type: Date,
},
end: {
type: Date,
},
startTime: {
type: String,
optional: true,
autoValue() {
if (!this.field('allDay').value && this.field('recurrent').value) {
return moment(this.field('start').value).format('HH:mm');
}
return this.unset() || null;
},
userId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue() {
if (this.isInsert) {
return this.userId;
}
return this.value;
},
},
endTime: {
type: String,
optional: true,
autoValue() {
if (!this.field('allDay').value && this.field('recurrent').value) {
return moment(this.field('end').value).format('HH:mm');
}
return this.unset() || null;
},
createdAt: {
type: Date,
label: getLabel('api.articles.labels.createdAt'),
optional: true,
autoValue() {
if (this.isInsert) {
return new Date();
}
return this.value;
},
},
allDay: {
type: Boolean,
defaultValue: false,
},
groups: {
type: Array,
defaultValue: [],
},
'groups.$': {
type: settingsGroup,
optional: true,
},
participants: {
type: Array,
defaultValue: [],
},
'participants.$': {
type: settingsParticipant,
optional: true,
},
guests: {
type: Array,
defaultValue: [],
},
'guests.$': {
type: String,
optional: true,
},
userId: {
type: String,
regEx: RegEx.Id,
autoValue() {
if (this.isInsert) {
return this.userId;
}
return this.value;
},
updatedAt: {
type: Date,
label: getLabel('api.articles.labels.updatedAt'),
autoValue() {
},
createdAt: {
type: Date,
label: getLabel('api.articles.labels.createdAt'),
optional: true,
autoValue() {
if (this.isInsert) {
return new Date();
},
}
return this.value;
},
},
updatedAt: {
type: Date,
label: getLabel('api.articles.labels.updatedAt'),
autoValue() {
return new Date();
},
},
{ tracker: Tracker },
);
});
EventsAgenda.publicFields = {
title: 1,
......
......@@ -7,6 +7,7 @@ import i18n from 'meteor/universe:i18n';
import { isActive, validateString } from '../utils/functions';
import Events from './events';
import RegEx from '../utils/regExp';
export const validateEvent = (data) => {
validateString(data.title);
......@@ -118,7 +119,7 @@ export const deleteEvent = new ValidatedMethod({
validate: new SimpleSchema({
eventId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
}).validator({ clean: true }),
......@@ -142,7 +143,7 @@ export const changeUserStatus = new ValidatedMethod({
validate: new SimpleSchema({
eventId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
status: Number,
}).validator({ clean: true }),
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import RegEx from '../utils/regExp';
const Groups = new Mongo.Collection('groups');
......@@ -17,86 +17,83 @@ Groups.deny({
},
});
Groups.schema = new SimpleSchema(
{
name: {
type: String,
index: true,
unique: true,
min: 1,
max: 60,
},
slug: {
type: String,
index: true,
unique: true,
min: 1,
},
description: {
type: String,
optional: true,
},
content: {
type: String,
optional: true,
},
active: { type: Boolean },
groupPadID: {
type: String,
optional: true,
},
digest: {
type: String,
optional: true,
},
type: {
type: SimpleSchema.Integer,
allowedValues: [0, 5, 10], // 0 Ouvert, 5 Modéré, 10 Fermé
},
applications: {
type: Array,
optional: true,
},
'applications.$': {
type: String,
},
owner: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
admins: {
type: Array,
defaultValue: [],
},
'admins.$': { type: String, regEx: SimpleSchema.RegEx.Id },
animators: {
type: Array,
defaultValue: [],
},
'animators.$': { type: String, regEx: SimpleSchema.RegEx.Id },
members: {
type: Array,
defaultValue: [],
},
'members.$': { type: String, regEx: SimpleSchema.RegEx.Id },
candidates: {
type: Array,
defaultValue: [],
},
'candidates.$': { type: String, regEx: SimpleSchema.RegEx.Id },
numCandidates: {
type: Number,
defaultValue: 0,
},
plugins: {
type: Object,
defaultValue: {},
optional: true,
blackbox: true,
},
},
{ tracker: Tracker },
);
Groups.schema = new SimpleSchema({
name: {
type: String,
index: true,
unique: true,
min: 1,
max: 60,
},
slug: {
type: String,
index: true,
unique: true,
min: 1,
},
description: {
type: String,
optional: true,
},
content: {
type: String,
optional: true,
},
active: { type: Boolean },
groupPadID: {
type: String,
optional: true,
},
digest: {
type: String,
optional: true,
},
type: {
type: SimpleSchema.Integer,
allowedValues: [0, 5, 10], // 0 Ouvert, 5 Modéré, 10 Fermé
},
applications: {
type: Array,
optional: true,
},
'applications.$': {
type: String,
},
owner: {
type: String,
regEx: RegEx.Id,
},
admins: {
type: Array,
defaultValue: [],
},
'admins.$': { type: String, regEx: RegEx.Id },
animators: {
type: Array,
defaultValue: [],
},
'animators.$': { type: String, regEx: RegEx.Id },
members: {
type: Array,
defaultValue: [],
},
'members.$': { type: String, regEx: RegEx.Id },
candidates: {
type: Array,
defaultValue: [],
},
'candidates.$': { type: String, regEx: RegEx.Id },
numCandidates: {
type: Number,
defaultValue: 0,
},
plugins: {
type: Object,
defaultValue: {},
optional: true,
blackbox: true,
},
});
Groups.typeLabels = {
0: 'api.groups.types.open',
......
......@@ -8,7 +8,17 @@ Meteor.publish('groups.member', function groupMember() {
}
return Groups.find(
{
$or: [{ admins: this.userId }, { animators: this.userId }, { members: this.userId }],
$or: [
{ $and: [{ admins: this.userId }, { type: 15 }] },
{
$and: [
{ type: { $ne: 15 } },
{
$or: [{ members: this.userId }, { admins: this.userId }, { animators: this.userId }],
},
],
},
],
},
{ fields: Groups.publicFields, limit: 100, sort: { name: -1 } },
);
......
import { Meteor } from 'meteor/meteor';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { getLabel } from '../utils/functions';
import RegEx from '../utils/regExp';
Meteor.users.schema = new SimpleSchema(
{
username: {
type: String,
optional: true,
label: getLabel('api.users.labels.username'),
},
firstName: {
type: String,
optional: true,
label: getLabel('api.users.labels.firstName'),
},
lastName: {
type: String,
optional: true,
label: getLabel('api.users.labels.lastName'),
},
emails: {
type: Array,
optional: true,
label: getLabel('api.users.labels.emails'),
},
'emails.$': {
type: Object,
},
'emails.$.address': {
type: String,
regEx: SimpleSchema.RegEx.Email,
label: getLabel('api.users.labels.emailAddress'),
},
'emails.$.verified': {
type: Boolean,
label: getLabel('api.users.labels.emailVerified'),
},
createdAt: {
type: Date,
label: getLabel('api.users.labels.createdAt'),
},
lastLogin: {
type: Date,
label: getLabel('api.users.labels.lastLogin'),
optional: true,
},
profile: {
type: Object,
optional: true,
blackbox: true,
label: getLabel('api.users.labels.profile'),
},
// Make sure this services field is in your schema if you're using any of the accounts packages
services: {
type: Object,
optional: true,
blackbox: true,
label: getLabel('api.users.labels.services'),
},
// In order to avoid an 'Exception in setInterval callback' from Meteor
heartbeat: {
type: Date,
optional: true,
label: getLabel('api.users.labels.heartbeat'),
},
structure: {
type: String,
optional: true,
label: getLabel('api.users.labels.structure'),
},
primaryEmail: {
type: String,
regEx: SimpleSchema.RegEx.Email,
optional: true,
label: getLabel('api.users.labels.primaryEmail'),
},
language: {
type: String,
optional: true,
label: getLabel('api.users.labels.language'),
},
logoutType: {
type: String,
optional: true,
allowedValues: ['ask', 'local', 'global'],
label: getLabel('api.users.labels.logoutType'),
},
avatar: {
type: String,
optional: true,
label: getLabel('api.users.labels.avatar'),
},
},
{ tracker: Tracker },
);
Meteor.users.schema = new SimpleSchema({
username: {
type: String,
optional: true,
label: getLabel('api.users.labels.username'),
},
firstName: {
type: String,
optional: true,
label: getLabel('api.users.labels.firstName'),
},
lastName: {
type: String,
optional: true,
label: getLabel('api.users.labels.lastName'),
},
emails: {
type: Array,
optional: true,
label: getLabel('api.users.labels.emails'),
},
'emails.$': {
type: Object,
},
'emails.$.address': {
type: String,
regEx: RegEx.Email,
label: getLabel('api.users.labels.emailAddress'),
},
'emails.$.verified': {
type: Boolean,
label: getLabel('api.users.labels.emailVerified'),
},
createdAt: {
type: Date,
label: getLabel('api.users.labels.createdAt'),
},
lastLogin: {
type: Date,
label: getLabel('api.users.labels.lastLogin'),
optional: true,
},
profile: {
type: Object,
optional: true,
blackbox: true,
label: getLabel('api.users.labels.profile'),
},
// Make sure this services field is in your schema if you're using any of the accounts packages
services: {
type: Object,
optional: true,
blackbox: true,
label: getLabel('api.users.labels.services'),
},
// In order to avoid an 'Exception in setInterval callback' from Meteor
heartbeat: {
type: Date,
optional: true,
label: getLabel('api.users.labels.heartbeat'),
},
structure: {
type: String,
optional: true,
label: getLabel('api.users.labels.structure'),
},
primaryEmail: {
type: String,
regEx: RegEx.Email,
optional: true,
label: getLabel('api.users.labels.primaryEmail'),
},
language: {
type: String,
optional: true,
label: getLabel('api.users.labels.language'),
},
logoutType: {
type: String,
optional: true,
allowedValues: ['ask', 'local', 'global'],
label: getLabel('api.users.labels.logoutType'),
},
avatar: {
type: String,
optional: true,
label: getLabel('api.users.labels.avatar'),
},
});
Meteor.users.selfFields = {
username: 1,
......
import { Meteor } from 'meteor/meteor';
import i18n from 'meteor/universe:i18n';
import SimpleSchema from 'simpl-schema';
import RegEx from './regExp';
export function isActive(userId) {
if (!userId) return false;
......@@ -25,60 +25,62 @@ export function getLabel(i18nLabel) {
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;
},
});
};
}
const levels = ['info', 'warning', 'error'];
......
// 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;
......@@ -88,6 +88,9 @@ const Footer = () => {
</a>
);
})}
<a className={classes.link} target="_blank" rel="noreferrer noopener" href="/about">
{i18n.__(`components.MainMenu.menuAboutLabel`)}
</a>
</>
);
};
......
......@@ -10,7 +10,6 @@ import Tooltip from '@material-ui/core/Tooltip';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import PropTypes from 'prop-types';
import AppVersion from '../system/AppVersion';
import LogoutDialog from '../system/LogoutDialog';
import UserAvatar from '../system/UserAvatar';
import LanguageSwitcher from '../system/LanguageSwitcher';
......@@ -147,8 +146,8 @@ const MainMenu = ({ user = {} }) => {
<T>menuLogoutLabel</T>
</MenuItem>
<Divider />
<MenuItem>
<AppVersion />
<MenuItem onClick={() => handleMenuClick('/about')}>
<T>menuAboutLabel</T>
</MenuItem>
</Menu>
<LanguageSwitcher topbar />
......
......@@ -20,10 +20,12 @@ export const CUSTOM_BUTTONS = ({ importFunc, exportFunc, weekendFunc }) => ({
import: {
text: i18n.__('components.Calendar.importButton'),
click: importFunc,
hint: i18n.__('components.Calendar.importButtonTooltip'),
},
export: {
text: i18n.__('components.Calendar.exportButton'),
click: exportFunc,
hint: i18n.__('components.Calendar.exportButtonTooltip'),
},
displayWeekEnds: {
text: i18n.__('components.Calendar.displayWeekendButton'),
......@@ -136,9 +138,17 @@ export const importICSToAgenda = (eFiles, setImporting) => {
const reader = new FileReader();
reader.onload = async (eFile) => {
const file = eFile.target.result;
const data = [];
// Parse
const parseF = importICS.sync.parseICS(file);
let parseF;
try {
parseF = importICS.sync.parseICS(file);
} catch (error) {
setImporting(false);
console.log(error);
msg.error(i18n.__('components.Calendar.importError'));
return;
}
const data = [];
// eslint-disable-next-line no-restricted-syntax
for (const k in parseF) {
// eslint-disable-next-line no-prototype-builtins
......
......@@ -17,6 +17,7 @@ import Spinner from '../components/system/Spinner';
import { useAppContext } from '../contexts/context';
import SiteInMaintenance from '../components/system/SiteInMaintenance';
import UserFailed from '../components/system/UserFailed';
import AboutPage from '../pages/About';
const useStyles = makeStyles((theme) => ({
main: {
......@@ -47,6 +48,7 @@ const MainLayout = ({ userFailed, setUserFailed }) => {
<>
<Switch>
<Route exact path={ROUTES.HOME} component={Calendar} />
<Route exact path={ROUTES.ABOUT} component={AboutPage} />
<Route exact path={ROUTES.LOGOUT} component={Logout} />
<Route exact path={ROUTES.LOGIN} component={Login} />
<Route exact path={ROUTES.ADD_EVENT} component={AddEvent} />
......