...
 
Commits (17)
......@@ -74,6 +74,7 @@
"adminGroupNeeded": "You don't have admin rights on this group.",
"moderatedGroupOnly": "This action is available for moderated group only.",
"userNotFound": "User not found",
"groupAlreadyExist": "A group with the same name already exists",
"types": {
"open": "open",
"moderated": "moderated",
......@@ -174,7 +175,7 @@
}
},
"methods": {
"operationSuccessMsg": "Operation succedeed.",
"operationSuccessMsg": "Operation succeeded.",
"labels": {
"page": "Page",
"pageSize": "Page Size",
......
......@@ -74,6 +74,7 @@
"adminGroupNeeded": "Vous n'avez pas les droits d'administration sur ce groupe.",
"moderatedGroupOnly": "Cette action est possible seulement sur un groupe modéré.",
"userNotFound": "Utilisateur non retrouvé",
"groupAlreadyExist": "Un groupe avec le même nom existe déjà",
"types": {
"open": "ouvert",
"moderated": "modéré",
......
......@@ -9,6 +9,7 @@ import i18n from 'meteor/universe:i18n';
import { isActive, getLabel } from '../utils';
import Groups from './groups';
import { addGroup, removeElement } from '../personalspaces/methods';
import kcClient from '../kcClient';
export const favGroup = new ValidatedMethod({
name: 'groups.favGroup',
......@@ -76,17 +77,29 @@ export const createGroup = new ValidatedMethod({
if (!isActive(this.userId)) {
throw new Meteor.Error('api.groups.createGroup.notLoggedIn', i18n.__('api.users.mustBeLoggedIn'));
}
const groupId = Groups.insert({
name,
type,
content,
description,
owner: this.userId,
admins: [this.userId],
active: true,
});
Roles.addUsersToRoles(this.userId, 'admin', groupId);
favGroup._execute({ userId: this.userId }, { groupId });
try {
const groupId = Groups.insert({
name,
type,
content,
description,
owner: this.userId,
admins: [this.userId],
active: true,
});
Roles.addUsersToRoles(this.userId, 'admin', groupId);
favGroup._execute({ userId: this.userId }, { groupId });
} catch (error) {
if (error.code === 11000) {
throw new Meteor.Error('api.groups.createGroup.duplicateName', i18n.__('api.groups.groupAlreadyExist'));
} else {
throw error;
}
}
if (Meteor.isServer && Meteor.settings.public.enableKeycloak) {
// create associated groups and roles in keycloak
kcClient.addGroup({ name });
}
},
});
......@@ -109,6 +122,10 @@ export const removeGroup = new ValidatedMethod({
if (!authorized) {
throw new Meteor.Error('api.groups.removeGroup.notPermitted', i18n.__('api.groups.adminGroupNeeded'));
}
if (Meteor.isServer && Meteor.settings.public.enableKeycloak) {
// delete associated groups and roles in keycloak
kcClient.removeGroup(group);
}
// remove all roles set on this group
Roles.removeScope(groupId);
Groups.remove(groupId);
......@@ -161,7 +178,15 @@ export const updateGroup = new ValidatedMethod({
} else {
groupData = { ...data };
}
Groups.update({ _id: groupId }, { $set: groupData });
try {
Groups.update({ _id: groupId }, { $set: groupData });
} catch (error) {
if (error.code === 11000) {
throw new Meteor.Error('api.groups.updateGroup.duplicateName', i18n.__('api.groups.groupAlreadyExist'));
} else {
throw error;
}
}
},
});
......
......@@ -476,8 +476,8 @@ describe('groups', function () {
},
);
},
Error,
/E11000 duplicate key error collection: meteor.groups index: c2_name dup key: { name: "group4" }/,
Meteor.Error,
/api.groups.createGroup.duplicateName/,
);
});
it('does not create a group when not logged in', function () {
......@@ -564,8 +564,8 @@ describe('groups', function () {
() => {
updateGroup._execute({ userId }, { groupId, data: { name: 'group4' } });
},
Meteor.ClientError,
/E11000 duplicate key error collection: meteor.groups index: c2_name dup key: { name: "group4" }/,
Meteor.Error,
/api.groups.updateGroup.duplicateName/,
);
});
});
......
This diff is collapsed.
......@@ -61,6 +61,10 @@ PersonalSpaces.schema = new SimpleSchema(
'sorted.$.name': {
type: String,
},
'sorted.$.isExpanded': {
type: Boolean,
defaultValue: true,
},
'sorted.$.elements': {
type: Array,
defaultValue: [],
......
......@@ -15,6 +15,7 @@ import { structures } from '../structures';
import { favGroup, unfavGroup } from '../../groups/methods';
import PersonalSpaces from '../../personalspaces/personalspaces';
import { createRoleNotification, createRequestNotification } from '../../notifications/server/notifsutils';
import kcClient from '../../kcClient';
// users.findUsers: Returns users using pagination
// filter: string to search for in username/firstname/lastname/emails (case insensitive search)
......@@ -290,6 +291,10 @@ export const setAdmin = new ValidatedMethod({
if (user === undefined) {
throw new Meteor.Error('api.users.setAdmin.unknownUser', i18n.__('api.users.unknownUser'));
}
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.setAdmin(userId);
}
// add role to user collection
Roles.addUsersToRoles(userId, 'admin');
},
......@@ -359,6 +364,10 @@ export const unsetAdmin = new ValidatedMethod({
if (user === undefined) {
throw new Meteor.Error('api.users.setAdmin.unknownUser', i18n.__('api.users.unknownUser'));
}
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.unsetAdmin(userId);
}
// remove role from user collection
Roles.removeUsersFromRoles(userId, 'admin');
},
......@@ -392,6 +401,10 @@ export const setAdminOf = new ValidatedMethod({
if (group.admins.indexOf(userId) === -1) {
Groups.update(groupId, { $push: { admins: userId } });
}
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.setRole(userId, group, 'admin');
}
// Notify user
createRoleNotification(this.userId, userId, groupId, 'admin', true);
},
......@@ -428,6 +441,10 @@ export const unsetAdminOf = new ValidatedMethod({
if (!Roles.userIsInRole(userId, ['animator', 'member', 'candidate'], groupId)) {
unfavGroup._execute({ userId }, { groupId });
}
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.unsetRole(userId, group, 'admin');
}
// Notify user
createRoleNotification(this.userId, userId, groupId, 'admin', false);
},
......@@ -463,6 +480,10 @@ export const setAnimatorOf = new ValidatedMethod({
}
// update user personalSpace
favGroup._execute({ userId }, { groupId });
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.setRole(userId, group, 'animator');
}
// Notify user
createRoleNotification(this.userId, userId, groupId, 'animator', true);
},
......@@ -499,6 +520,10 @@ export const unsetAnimatorOf = new ValidatedMethod({
if (!Roles.userIsInRole(userId, ['member', 'admin', 'candidate'], groupId)) {
unfavGroup._execute({ userId }, { groupId });
}
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.unsetRole(userId, group, 'animator');
}
// Notify user
createRoleNotification(this.userId, userId, groupId, 'animator', false);
},
......@@ -547,6 +572,10 @@ export const setMemberOf = new ValidatedMethod({
}
// update user personalSpace
favGroup._execute({ userId }, { groupId });
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.setRole(userId, group, 'member');
}
// Notify user
createRoleNotification(this.userId, userId, groupId, 'member', true);
},
......@@ -585,6 +614,10 @@ export const unsetMemberOf = new ValidatedMethod({
if (!Roles.userIsInRole(userId, ['animator', 'admin', 'candidate'], groupId)) {
unfavGroup._execute({ userId }, { groupId });
}
if (Meteor.settings.public.enableKeycloak) {
// update user's groups in Keycloak
kcClient.unsetRole(userId, group, 'member');
}
// Notify user
createRoleNotification(this.userId, userId, groupId, 'member', false);
},
......
import { Meteor } from 'meteor/meteor';
import PersonalSpaces from '../../../api/personalspaces/personalspaces';
import fakeData from './fakeData.json';
import { updatePersonalSpace } from '../../../api/personalspaces/methods';
// import fakeData from './fakeData.json';
function createPersonalSpaces(personalspace) {
const { userId } = personalspace; // in fakeData file, userId is set to user mail
console.log(` Creating personalspace ${userId}.`);
const user = Accounts.findUserByEmail(userId);
PersonalSpaces.insert({ ...personalspace, userId: user._id });
}
// function createPersonalSpaces(personalspace) {
// const { userId } = personalspace; // in fakeData file, userId is set to user mail
// console.log(` Creating personalspace ${userId}.`);
// const user = Accounts.findUserByEmail(userId);
// PersonalSpaces.insert({ ...personalspace, userId: user._id });
// }
// /** When running app for first time, pass a settings file to set up a default user account. */
// if (PersonalSpaces.find().count() === 0) {
// if (Meteor.settings.private.fillWithFakeData) {
// console.log('Creating the default personalspaces');
// fakeData.defaultPersonalSpaces.map(createPersonalSpaces);
// } else {
// console.log('No default personalspaces to create ! Please invoke meteor with a settings file.');
// }
// }
/** When running app for first time, pass a settings file to set up a default user account. */
if (PersonalSpaces.find().count() === 0) {
if (Meteor.settings.private.fillWithFakeData) {
console.log('Creating the default personalspaces');
fakeData.defaultPersonalSpaces.map(createPersonalSpaces);
} else {
console.log('No default personalspaces to create ! Please invoke meteor with a settings file.');
}
if (Meteor.isDevelopment) {
// Regen users empty personalspace with current fav services and group roles
const usersWithEmptyPS = PersonalSpaces.find({ unsorted: [], sorted: [] }, { fields: { userId: 1, _id: 0 } });
usersWithEmptyPS.forEach((user) => {
const u = Meteor.users.findOne({ _id: user.userId }, { fields: { username: 1, favServices: 1, favGroups: 1 } });
console.log(`Regen personalspaces for ${u.username}...`);
const unsorted = [];
u.favServices.forEach((s) => {
unsorted.push({
element_id: s,
type: 'service',
});
});
u.favGroups.forEach((g) => {
unsorted.push({
element_id: g,
type: 'group',
});
});
updatePersonalSpace._execute({ userId: user.userId }, { data: { userId: user.userId, unsorted, sorted: [] } });
});
}
......@@ -20,7 +20,7 @@ import './db-initialize/Categories';
import './db-initialize/Groups';
import './db-initialize/Articles';
import './db-initialize/AppSettings';
// import './db-initialize/PersonalSpaces';
import './db-initialize/PersonalSpaces';
Meteor.startup(() => {
Migrations.migrateTo('latest');
......
......@@ -7,8 +7,8 @@ import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import BookmarkIcon from '@material-ui/icons/Bookmark';
import BookmarkBorderIcon from '@material-ui/icons/BookmarkBorder';
import AddIcon from '@material-ui/icons/Add';
import RemoveIcon from '@material-ui/icons/Remove';
// import OpenWithIcon from '@material-ui/icons/OpenWith';
import Tooltip from '@material-ui/core/Tooltip';
......@@ -229,7 +229,7 @@ function ServiceDetails({ service, favAction, isShort }) {
className={classes.fab}
onClick={handleFavorite}
>
{favorite ? <BookmarkBorderIcon /> : <BookmarkIcon />}
{favorite ? <AddIcon /> : <RemoveIcon />}
{/* {i18n.__(`components.ServiceDetails.${favorite ? '' : 'un'}pin`)} */}
</Button>
</Tooltip>
......
......@@ -7,7 +7,7 @@ import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import BookmarkIcon from '@material-ui/icons/Bookmark';
import RemoveIcon from '@material-ui/icons/Remove';
import Tooltip from '@material-ui/core/Tooltip';
import { Button, CardActionArea, CardActions } from '@material-ui/core';
import i18n from 'meteor/universe:i18n';
......@@ -110,7 +110,7 @@ function ServiceDetailsPersSpace({ service }) {
<CardActions className={classes.cardActions}>
<Tooltip title={favButtonLabel} aria-label={favButtonLabel}>
<Button variant="outlined" size="small" className={classes.fab} onClick={handleFavorite}>
<BookmarkIcon />
<RemoveIcon />
</Button>
</Tooltip>
</CardActions>
......
......@@ -58,7 +58,7 @@ const MsgHandler = () => {
}
return (
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
open={openError}
autoHideDuration={options.hideOnClick ? null : options.duration}
onClose={handleMsgClose}
......
......@@ -94,58 +94,18 @@ const useStyles = makeStyles((theme) => ({
}));
function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
const AUTOSAVE_INTERVAL = 3000;
const classes = useStyles();
const [customDrag, setcustomDrag] = useState(false);
const checkElementExists = (elem) => {
switch (elem.type) {
case 'service': {
return Services.findOne(elem.element_id) !== undefined;
}
case 'group': {
return Groups.findOne(elem.element_id) !== undefined;
}
default: {
return true;
}
}
};
const checkPersonalSpace = (ps) => {
let didDelete = false;
for (let i = ps.unsorted.length - 1; i >= 0; i -= 1) {
if (!checkElementExists(ps.unsorted[i])) {
ps.unsorted.splice(i, 1);
didDelete = true;
}
}
ps.sorted.map((zone, zi) => {
for (let i = zone.elements.length - 1; i >= 0; i -= 1) {
if (!checkElementExists(zone.elements[i])) {
ps.sorted[zi].elements.splice(i, 1);
didDelete = true;
}
}
return true;
});
if (didDelete) {
Meteor.call('personalspaces.updatePersonalSpace', { data: ps }, (err) => {
if (err) {
msg.error(err.reason);
}
});
}
return ps;
const handleCustomDrag = (event) => {
setcustomDrag(event.target.checked);
};
const [localPS, setLocalPS] = useState({});
useEffect(() => {
if (personalspace && allServices && allGroups) {
setLocalPS(checkPersonalSpace(personalspace));
setLocalPS(personalspace);
}
}, [personalspace]);
......@@ -157,10 +117,23 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
});
};
const handleCustomDrag = (event) => {
setcustomDrag(event.target.checked);
if (!event.target.checked) {
updatePersonalSpace();
const [psNeedUpdate, setPsNeedUpdate] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
if (psNeedUpdate) {
updatePersonalSpace();
setPsNeedUpdate(false);
}
}, AUTOSAVE_INTERVAL);
return () => clearTimeout(timer);
}, [psNeedUpdate]);
const setExpanded = (index) => {
if (typeof index === 'number') {
const { sorted } = localPS;
sorted[index].isExpanded = !sorted[index].isExpanded;
setLocalPS({ ...localPS, sorted });
setPsNeedUpdate(true);
}
};
......@@ -170,6 +143,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
if (sorted[index].name !== title) {
sorted[index].name = title.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
setLocalPS({ ...localPS, sorted });
setPsNeedUpdate(true);
}
}
};
......@@ -186,10 +160,16 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
}
};
const updateList = () => {
// Called on onEnd event of reactsortable zone
setPsNeedUpdate(true);
};
const delZone = (index) => {
const { sorted } = localPS;
sorted.splice(index, 1);
setLocalPS({ ...localPS, sorted });
setPsNeedUpdate(true);
};
const upZone = (zoneIndex) => {
......@@ -201,6 +181,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
...localPS,
sorted: [...remainingItems.slice(0, zoneIndex - 1), movedItem, ...remainingItems.slice(zoneIndex - 1)],
});
setPsNeedUpdate(true);
};
const downZone = (zoneIndex) => {
......@@ -212,6 +193,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
...localPS,
sorted: [...remainingItems.slice(0, zoneIndex + 1), movedItem, ...remainingItems.slice(zoneIndex + 1)],
});
setPsNeedUpdate(true);
};
const addZone = (where) => {
......@@ -219,6 +201,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
const newZone = {
zone_id: Random.id(),
name: i18n.__('pages.PersonalPage.newZone'),
isExpanded: true,
elements: [],
};
if (where === 0) {
......@@ -227,6 +210,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
sorted.push(newZone);
}
setLocalPS({ ...localPS, sorted });
setPsNeedUpdate(true);
};
const addLink = (zoneIndex) => {
......@@ -237,8 +221,13 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
title: '',
url: '',
};
if (sorted[zoneIndex].isExpanded !== true) {
// Expand zone to add the new link
sorted[zoneIndex].isExpanded = true;
}
sorted[zoneIndex].elements.unshift(newLink);
setLocalPS({ ...localPS, sorted });
setPsNeedUpdate(true);
};
const updateLink = (zoneIndex, link) => {
......@@ -247,7 +236,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
sorted[zoneIndex].elements[linkIndex].title = link.title;
sorted[zoneIndex].elements[linkIndex].url = link.url;
setLocalPS({ ...localPS, sorted });
updatePersonalSpace();
setPsNeedUpdate(true);
};
const delLink = (zoneIndex, linkId) => () => {
......@@ -255,7 +244,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
const removeIndex = sorted[zoneIndex].elements.map((item) => item.element_id).indexOf(linkId);
sorted[zoneIndex].elements.splice(removeIndex, 1);
setLocalPS({ ...localPS, sorted });
updatePersonalSpace();
setPsNeedUpdate(true);
};
return (
......@@ -317,12 +306,13 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
: i18n.__('pages.PersonalPage.unsorted')
}
setList={setZoneList}
updateList={updateList}
customDrag={customDrag}
/>,
<Divider key="div-000000000000" className={classes.divider} />,
]
: null}
{localPS.sorted.map(({ zone_id: zoneId, elements, name }, index) => [
{localPS.sorted.map(({ zone_id: zoneId, elements, name, isExpanded }, index) => [
<PersonalZone
key={`zone-${zoneId}`}
elements={elements}
......@@ -330,6 +320,7 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
title={name}
setTitle={setZoneTitle}
setList={setZoneList}
updateList={updateList}
delZone={delZone}
lastZone={localPS.sorted.length === index + 1}
moveDownZone={downZone}
......@@ -339,10 +330,12 @@ function PersonalPage({ personalspace, isLoading, allServices, allGroups }) {
delPersonalLink={delLink}
customDrag={customDrag}
isSorted
isExpanded={isExpanded}
setExpanded={setExpanded}
/>,
localPS.sorted.length !== index + 1 ? (
<Divider className={classes.divider} key={`div-${zoneId}`} />
) : null,
// localPS.sorted.length !== index + 1 ? (
// <Divider className={classes.divider} key={`div-${zoneId}`} />
// ) : null,
])}
{customDrag ? (
<>
......
......@@ -147,7 +147,7 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => {
method.call(args, (error) => {
setLoading(false);
if (error) {
msg.error(error.message);
msg.error(error.reason ? error.reason : error.message);
} else {
msg.success(i18n.__('api.methods.operationSuccessMsg'));
history.goBack();
......
......@@ -16,8 +16,8 @@ import PeopleIcon from '@material-ui/icons/People';
import LockIcon from '@material-ui/icons/Lock';
import ClearIcon from '@material-ui/icons/Clear';
import EditIcon from '@material-ui/icons/Edit';
import BookmarkIcon from '@material-ui/icons/Bookmark';
import BookmarkBorderIcon from '@material-ui/icons/BookmarkBorder';
import AddIcon from '@material-ui/icons/Add';
import RemoveIcon from '@material-ui/icons/Remove';
import Tooltip from '@material-ui/core/Tooltip';
import { useAppContext } from '../../contexts/context';
import Groups from '../../../api/groups/groups';
......@@ -301,7 +301,7 @@ const SingleGroupPage = ({ group = {}, ready, services }) => {
<Grid item>
<Tooltip title={favButtonLabel} aria-label={favButtonLabel}>
<Button className={classes.buttonFav} size="large" variant="outlined" onClick={handleFavorite}>
{favorite ? <BookmarkIcon /> : <BookmarkBorderIcon />}
{favorite ? <RemoveIcon /> : <AddIcon />}
</Button>
</Tooltip>
</Grid>
......
......@@ -5,8 +5,8 @@ import PropTypes from 'prop-types';
import i18n from 'meteor/universe:i18n';
import { useHistory, Link } from 'react-router-dom';
import { Container, makeStyles, Button, Typography, Grid, Chip, Tooltip, Fade } from '@material-ui/core';
import BookmarkIcon from '@material-ui/icons/Bookmark';
import BookmarkBorderIcon from '@material-ui/icons/BookmarkBorder';
import AddIcon from '@material-ui/icons/Add';
import RemoveIcon from '@material-ui/icons/Remove';
import ArrowBack from '@material-ui/icons/ArrowBack';
import Services from '../../../api/services/services';
import Spinner from '../../components/system/Spinner';
......@@ -124,8 +124,8 @@ const SingleServicePage = ({ service = [], ready, categories = [] }) => {
const favButton = (
<Tooltip title={favButtonLabel} aria-label={favButtonLabel}>
<Button variant="text" color="primary" disabled={loading} className={classes.fab} onClick={handleFavorite}>
{favorite ? <BookmarkIcon fontSize="large" /> : <BookmarkBorderIcon fontSize="large" />}
<Button variant="outlined" color="primary" disabled={loading} className={classes.fab} onClick={handleFavorite}>
{favorite ? <RemoveIcon fontSize="large" /> : <AddIcon fontSize="large" />}
</Button>
</Tooltip>
);
......
{
"name": "app",
"version": "0.0.2",
"version": "2.0.0-alpha",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -7926,6 +7926,28 @@
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
},
"dependencies": {
"fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"requires": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
}
}
}
},
"quill-delta": {
......
......@@ -15,6 +15,7 @@
"@material-ui/core": "^4.10.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"axios": "^0.19.2",
"bcrypt": "^3.0.8",
"body-parser": "^1.19.0",
"chai": "^4.2.0",
......
......@@ -38,7 +38,9 @@
"keycloak": {
"pubkey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvyVtOgah8FjiXmFcnavID9b0lEhCl5k7jISPkPweOQtB1+hRAajcT7Y4v9babrUAXVL7bAb8nRTJVrks3Jl+o/nfZjfLxx7vIE1mMsRGVAqPg88UgyP79fFBvWdPnrIaIweNqlH5r1WoBOVjsoX0rFNijZqQ5llcC/uUYQW35urjGUJIZlHDSOdp34qnrrnohE3714PUuiKHhqFx15MpzmO/KQuIt0lJXuJ3NgAwt1OndksvP03Kkm0ls7IvveF0GiM2dki2xPRAHvv3jC1hLdHm0kGdrn+EndX0xFwBl9kL5zcjILGwm1gyfeJdfzOQrBUf6WL+FpgNseWq5MD+vwIDAQAB",
"client": "sso",
"adminEmails": []
"adminEmails": [],
"adminUser": "",
"adminPassword": ""
},
"private": {
"fillWithFakeData": true,
......