...
 
Commits (4)
......@@ -63,7 +63,12 @@
/**
* @callback entityPersonneCallback
* @param {Error} [error] Erreur éventuelle
* @param {EntityPersonne} [personne] Une Entity Personne
* @param {EntityPersonne} [personne] Une entity Personne
*/
/**
* @callback entityPersonnesCallback
* @param {Error} [error] Erreur éventuelle
* @param {EntityPersonne[]} [personnes] Une liste d'entity Personne
*/
/**
......
......@@ -18,9 +18,10 @@ import ResourceCreate from './components/ResourceCreate'
import ResourceForm from './components/ResourceForm'
import ResourceSearch from './components/ResourceSearch'
import Notifications from './components/Notifications'
import GroupesPerso from './components/groupes/GroupesPerso'
import GroupeEdition from './components/groupes/GroupeEdition'
import GroupeListerMembres from './components/groupes/GroupeListerMembres'
import GroupesOuverts from './components/groupes/GroupesOuverts'
import GroupesPerso from './components/groupes/GroupesPerso'
import GroupesPublics from './components/groupes/GroupesPublics'
import NotFound from './components/NotFound'
import ErrorBoundary from './components/ErrorBoundary'
......@@ -36,8 +37,15 @@ const beforeSend = (report) => {
report.metaData.state = store.getState()
}
// ATTENTION à garder la liste des routes synchrones dans app/server/main/controllerMain.js
// Route fournit à son component les props
// - location: {pathname, search, hash}
// - match: {params, path, url}
// Par ex une route /foo/:bar appellée via /foo/baz va fournir
// match.path: '/foo/:bar'
// match.url: '/foo/baz'
// match.params.bar: 'baz'
// ATTENTION à garder la liste des routes synchrones dans app/server/main/controllerMain.js
const App = () => (
<ErrorBoundary beforeSend={beforeSend}>
<Provider store={store}>
......@@ -59,6 +67,7 @@ const App = () => (
<Route exact path="/ressource/rechercher" component={ResourceSearch} />
<Route exact path="/groupe/ajouter" component={GroupeEdition} />
<Route exact path="/groupe/editer/:groupe" component={GroupeEdition} />
<Route exact path="/groupe/listerMembres/:groupe" component={GroupeListerMembres} />
<Route exact path="/groupes/perso" component={GroupesPerso} />
<Route exact path="/groupes/ouverts" component={GroupesOuverts} />
<Route exact path="/groupes/publics" component={GroupesPublics} />
......
import {GET, POST, DELETE} from '../utils/httpMethods'
import {addNotification} from './notifications'
import {
getGroupesPersoUrl,
getSaveGroupeUrl,
getGroupeUrl,
getGroupeJoinUrl,
getGroupeFollowUrl,
getGroupeIgnoreUrl,
getGroupeJoinUrl,
getGroupeLeaveUrl,
getGroupeIgnoreUrl
getGroupesPersoUrl,
getSaveGroupeUrl
} from '../apiRoutes'
const dummyCb = () => {}
export const loadGroupes = () => dispatch => {
const requestSuccess = ({
groupes,
......@@ -67,7 +69,7 @@ const deleteGroupeAction = (nom) => ({
payload: { nom }
})
export const deleteGroupe = (nom, success = () => {}) => dispatch => {
export const deleteGroupe = (nom, success = dummyCb) => dispatch => {
const requestSuccess = () => {
dispatch(
addNotification({
......@@ -96,7 +98,7 @@ const leaveGroupeAction = (nom) => ({
export const leaveGroupe = (
nom,
success = () => {}
success = dummyCb
) => dispatch => {
const requestSuccess = () => {
dispatch(
......@@ -126,7 +128,7 @@ const ignoreGroupeAction = (nom) => ({
export const ignoreGroupe = (
nom,
success = () => {}
success = dummyCb
) => dispatch => {
const requestSuccess = () => {
dispatch(
......@@ -157,7 +159,7 @@ const joinGroupeAction = groupe => ({
export const joinGroupe = (
nom,
success = () => {}
success = dummyCb
) => dispatch => {
const requestSuccess = (groupe) => {
dispatch(
......@@ -187,7 +189,7 @@ const followGroupeAction = groupe => ({
export const followGroupe = (
nom,
success = () => {}
success = dummyCb
) => dispatch => {
const requestSuccess = (groupe) => {
dispatch(
......
import {GET, POST} from '../utils/httpMethods'
import {addNotification} from './notifications'
import {
getGroupeMembresUrl,
getGroupeRetirerMembreUrl
} from '../apiRoutes'
// la limite par défaut est à 100 dans le contrôleur, on le gère pas coté front
export const loadMembres = ({nom, skip}) => (dispatch) => GET(getGroupeMembresUrl({nom, skip}))
.then(({membres, nbMembres, nbAbonnes, skip, limit}) => dispatch({
type: 'LOAD_MEMBRES',
payload: {membres, nbMembres, nbAbonnes, skip, limit}
})).catch((error) => dispatch(
addNotification({
level: 'error',
message: `Le chargement des membres a échoué : ${error.message}`
})
))
export const retirerMembre = ({groupeNom, personneOid}) => (dispatch) => {
const url = getGroupeRetirerMembreUrl()
const body = {
groupeNom,
personneOid
}
return POST(url, {body}).then(({membre}) => {
dispatch(
addNotification({
level: 'info',
message: `Membre retiré : ${membre.prenom} ${membre.nom} (${membre.pid})`
})
)
dispatch({
type: 'RETIRER_MEMBRE',
payload: {membre}
})
}).catch(error => {
console.log(error)
dispatch(
addNotification({
level: 'error',
message: error.message
})
)
})
}
......@@ -76,6 +76,38 @@ const personneCurrent = () => `personne/current`
*/
export const getCurrentPersonneUrl = makeGetter(personneCurrent)
const membres = ({nom, skip, limit}) => {
let url = `groupe/listerMembres/${encodeURIComponent(nom)}`
const hasSkipOption = typeof skip === 'number' && skip
if (hasSkipOption) url += `?skip=${skip}`
if (typeof limit === 'number' && limit) url += `${hasSkipOption ? '&' : '?'}limit=${limit}`
return url
}
/**
* Liste des membres du groupe
* @type {routeGetter}
* @param {object} urlParams
* @param {string} urlParams.nom Le nom du groupe
* @param {number} [urlParams.skip] offset (si la liste précédente excédait la limite)
* @param {number} [urlParams.limit] limit max du nb de personnes qui seront retournées
* @param {string} [baseUrl]
*/
export const getGroupeMembresUrl = makeGetter(membres, ['nom'])
const groupeAjouterMembre = () => 'groupe/ajouterMembre'
/**
* Ajouter un membre à un groupe (cf doc du controleur pour les params à passer en post)
* @type {routeGetter}
*/
export const getGroupeAjouterMembreUrl = makeGetter(groupeAjouterMembre)
const groupeRetirerMembre = () => 'groupe/retirerMembre'
/**
* Retirer un membre d'un groupe (cf doc du controleur pour les params à passer en post)
* @type {routeGetter}
*/
export const getGroupeRetirerMembreUrl = makeGetter(groupeRetirerMembre)
const clone = ({oid}) => `clone/${oid}`
/**
* Cloner une ressource
......
......@@ -5,6 +5,10 @@ import {NavLink} from 'react-router-dom'
import {Fields} from 'redux-form'
import GroupesSelector from './GroupesSelector'
/**
* Composant pour sélectionner un groupe dans la liste (sur l'édition de ressource)
* @param {string[]} groupesList
*/
const GroupContainer = ({groupesList}) => {
if (groupesList.length) {
return (
......
......@@ -111,7 +111,6 @@ const getInitialValues = ({
match: {params: {groupe: groupeNom}},
// vient de la session (via ensureLogged appelé par groupesLoader)
personne: {
oid,
nom,
prenom
}
......
import PropTypes from 'prop-types'
import React, {Fragment} from 'react'
import {NavLink} from 'react-router-dom'
import groupeMembresLoader from './hoc/groupeMembresLoader'
import {saveGroupe} from 'client-react/actions/groupes'
import validate, {asyncBlurFields, asyncValidate} from 'client-react/utils/groupeValidate'
import onSubmitFail from 'client-react/utils/onSubmitFail'
import groupesLoader from 'client-react/components/groupes/hoc/groupesLoader'
const getHeader = ({nbMembres, skip, limit, length}) => {
if (nbMembres === 0) return 'Aucun membre'
if (length === nbMembres) {
return `${nbMembres} membre${nbMembres > 1 ? 's' : ''}`
}
return `${length} membre${length > 1 ? 's' : ''} (de ${skip + 1} à ${Math.min(skip + limit, nbMembres)} sur ${nbMembres})`
}
/**
* La liste des groupes publics (auth)
* @type PureComponent
* @param {object} props
* @param {Groupe[]} props.groupes
* @param {function} props.followGroupe
* @param {function} props.ignoreGroupe
* @param {function} props.groupesSuivis
*/
const GroupeListerMembres = ({
nom,
membres,
nbMembres,
nbAbonnes,
skip,
limit,
previous,
next,
ajouterMembre,
retirerMembre
}) => (
<Fragment>
<div className="fr">
<NavLink to={`/groupe/editer/${encodeURIComponent(nom)}`} className="btn--info"><i className="fa fa-edit"></i>Modifier</NavLink>
</div>
<h1 className="col-3">Groupe « {nom} » <i>({nbAbonnes} abonné{nbAbonnes > 1 ? 's' : ''})</i></h1>
<ul className="clear">
{previous ? (<a onClick={previous}>&lt;&lt;précédents</a>) : null}
<span style={{margin: '0 1rem'}}>{getHeader({nbMembres, skip, limit, length: (membres && membres.length) || 0})}</span>
{next ? (<a onClick={next}>suivants &gt;&gt;</a>) : null}
{membres.map(({nom, prenom, oid}, index) => (<li key={index}>{prenom} {nom} <button onClick={retirerMembre({oid})}>Retirer du groupe</button></li>))}
</ul>
<label className="field">Ajouter un membre <i>(en le faisant vous vous engagez à lui avoir demandé son avis avant)</i></label>
<form className="grid-6" onSubmit={(event, values, ...rest) => {
event.preventDefault()
console.log('onSubmit', Object.keys(event), event.target, event)
const {nom, pid} = event.data || {}
ajouterMembre({nom, pid})
}}>
<div className="field col-2">
<label htmlFor="form-membres-pid">Identifiant</label>
<input id="form-membres-pid" type="text" name="pid" placeholder="cf. « mes informations personnelles »"/>
</div>
<div className="field col-3">
<label htmlFor="form-membres-nom">Son nom</label>
<input id="form-membres-nom" name="nom" placeholder="Entrez également le nom (insensible à la casse)" />
</div>
<div style={{position: 'relative'}}>
<button type="submit" className="btn--primary" style={{position: 'absolute', bottom: '12px', left: '1rem'}}>Ajouter</button>
</div>
</form>
</Fragment>
)
GroupeListerMembres.propTypes = {
nom: PropTypes.string,
membres: PropTypes.array,
nbMembres: PropTypes.number,
nbAbonnes: PropTypes.number,
skip: PropTypes.number,
limit: PropTypes.number,
previous: PropTypes.func,
next: PropTypes.func,
ajouterMembre: PropTypes.func,
retirerMembre: PropTypes.func
}
// groupesListeLoader contient ensureLogged
export default groupeMembresLoader(GroupeListerMembres)
......@@ -59,7 +59,9 @@ const AdminLinksInner = ({
}) => (
<span className="links">
<a href="#" onClick={() => deleteGroupe(nom)} className="btn--info"><i className="fa fa-trash"></i>Supprimer</a>
<NavLink to={`/groupe/editer/${encodeURIComponent(nom)}`} className="btn--info"><i className="fa fa-edit"></i>Modifier</NavLink>
<NavLink
className="btn--info"
to={{
......@@ -67,6 +69,12 @@ const AdminLinksInner = ({
hash: 'results',
search: `groupes=${encodeURIComponent(nom)}`
}}><i className="fa fa-bookmark"></i> Voir les ressources du groupe</NavLink>
<NavLink
className="btn--info"
to={{pathname: `/groupe/listerMembres/${encodeURIComponent(nom)}`}}
><i className="fa fa-users" /> Membres du groupe</NavLink>
{groupesMembre.includes(nom)
? null // le lien quitter sera dans la liste des groupesMembre
: (<button className="btn--info" onClick={() => joinGroupe(nom)}><i className="fa fa-sign-in-alt"></i>Rejoindre</button>)
......
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {GET, POST} from 'client-react/utils/httpMethods'
import ensureLogged from 'client-react/hoc/ensureLogged'
import addNotify from 'client-react/hoc/addNotify'
import getDisplayName from 'client-react/utils/getDisplayName'
import {getGroupeAjouterMembreUrl, getGroupeMembresUrl, getGroupeRetirerMembreUrl} from 'client-react/apiRoutes'
/**
* Higher Order Component qui charge les membres d'un groupe (passera au component wrappé les props membres, nbMembres, nbAbonnes)
* @param {Component} WrappedComponent
* @return {Component} WrappedComponent enrichi
*/
const groupeMembresLoader = WrappedComponent => {
class GroupeMembresLoader extends Component {
constructor (props) {
console.log('constructeur GroupeMembresLoader avec les props', props)
super(props)
const {match: {params: {groupe}}} = props
this.state = {nom: groupe, membres: null, nbMembres: undefined, nbAbonnes: undefined, skip: undefined, limit: undefined}
}
refresh (skip) {
const urlOptions = {nom: this.state.nom}
if (skip) urlOptions.skip = skip
// pour debug suivant / précédent fixer ici une limite basse, genre urlOptions.limit = 3
const url = getGroupeMembresUrl(urlOptions)
GET(url)
.then(({membres, nbMembres, nbAbonnes, skip, limit}) => this.setState({membres, nbMembres, nbAbonnes, skip, limit}))
.catch(error => {
if (error.status === 403) {
// On affiche le message d'erreur sur la page, plus clair et évite une page vide
this.setState({errorMessage: error.message})
} else {
console.error(error)
this.props.notify({level: 'error', message: error.message})
}
})
}
ajouterMembre ({nom, pid}) {
const url = getGroupeAjouterMembreUrl()
const body = {
groupeNom: this.state.nom,
personneNom: nom,
pid
}
POST(url, {body}).then(({membre}) => {
this.props.notify(`Membre ajouté ${membre.prenom} ${membre.nom} (${membre.pid})`)
const membres = []
let toAdd = true
this.state.membres.forEach((m) => {
// on l'insère dès que c'est sa place dans l'ordre alpha
if (toAdd && membre.nom < m.nom) {
membres.push(membre)
toAdd = false
}
membres.push(m)
})
// pour éviter de refaire une requête et conserver un affichage cohérent on incrémente la limite
const newStateProps = {
membres,
nbMembres: this.state.nbMembres + 1,
limit: this.state.limit + 1
}
this.setState(newStateProps)
}).catch(error => {
console.log(error)
this.props.notify({level: 'error', message: error.message})
})
}
retirerMembre ({oid}) {
const url = getGroupeRetirerMembreUrl()
const body = {
groupeNom: this.state.nom,
personneOid: oid
}
POST(url, {body}).then(({membre}) => {
this.props.notify(`Membre supprimé : ${membre.prenom} ${membre.nom} (${membre.pid})`)
const newStateProps = {
membres: this.state.membres.filter(m => m.oid !== membre.oid),
nbMembres: this.state.nbMembres - 1,
limit: this.state.limit - 1
}
this.setState(newStateProps)
}).catch(error => {
console.log(error)
this.props.notify({level: 'error', message: error.message})
})
}
componentDidMount () {
this.refresh()
}
render () {
if (this.state.errorMessage) return (<p className="alert--warning">{this.state.errorMessage}</p>)
if (this.state.membres === null) return null
const childProps = {...this.props, ...this.state}
if (this.state.skip > 0) {
childProps.previous = () => this.refresh(Math.max(0, this.state.skip - this.state.limit))
}
if (this.state.nbMembres > this.state.skip + this.state.limit) {
childProps.next = () => this.refresh(this.state.skip + this.state.limit)
}
childProps.ajouterMembre = this.ajouterMembre.bind(this)
childProps.retirerMembre = this.retirerMembre.bind(this)
return (
<WrappedComponent {...childProps} />
)
}
}
GroupeMembresLoader.displayName = `groupesListeLoader(${getDisplayName(WrappedComponent)})`
GroupeMembresLoader.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({
groupe: PropTypes.string
})
}),
notify: PropTypes.func
}
return ensureLogged(addNotify(GroupeMembresLoader))
}
export default groupeMembresLoader
import {connect} from 'react-redux'
import {addNotification} from 'client-react/actions/notifications'
function _notify (dispatch, notification) {
if (!notification) return console.error(Error('notify appelé sans rien à notifier'))
if (notification instanceof Error) {
console.error(notification)
return dispatch(addNotification({message: notification.message, level: 'error'}))
}
// on accepte les string
const message = typeof notification === 'string' ? notification : notification.message
if (typeof message !== 'string') return console.error(Error('appel incorrect de notify'), notification)
if (!message) return console.error(Error('notify appelé avec un message vide'))
const level = notification.level || 'info'
dispatch(addNotification({message, level}))
}
const mapDispatchToProps = (dispatch) => ({
/**
* Affiche une notification
* @param {string|object|Error} notification Si c'est une Error, elle sera affichée en console et le message en notification, sinon il faut une string ou un objet avec au moins une propriété message
* @param {string} notification.message
* @param {string} [notification.level=info]
* @return {*}
*/
notify: _notify.bind(null, dispatch)
})
/**
* High Order Component qui fournit une méthode notify aux props de WrappedComponent
* @param {Component} WrappedComponent
* @return {Component} Le composant enrichi
*/
const addNotify = (WrappedComponent) => connect(null, mapDispatchToProps)(WrappedComponent)
export default addNotify
......@@ -81,6 +81,7 @@ module.exports = function (mainComponent) {
'/ressource/rechercher',
'/groupe/ajouter',
'/groupe/editer/:groupe',
'/groupe/listerMembres/:groupe',
'/groupes/perso',
'/groupes/ouverts',
'/groupes/publics'
......
......@@ -71,9 +71,9 @@ module.exports = function (component) {
if (!data.oid && !$accessControl.hasGenericPermission('createGroupe', context)) return $json.denied(context, 'Vous n’avez pas les droits suffisants pour créer un groupe')
flow().seq(function () {
if (data.oid) return EntityGroupe.match('oid').equals(data.oid).grabOne(this)
if (data.oid) return $groupeRepository.load(data.oid)
// à la création on cherche d'après le nom pour vérifier qu'il n'existe pas
EntityGroupe.match('nom').equals(data.nom).grabOne(this)
$groupeRepository.loadByNom(data.nom, this)
}).seq(function (groupeBdd) {
const nextStep = this
if (data.oid) {
......@@ -148,8 +148,67 @@ module.exports = function (component) {
controller.options('', optionsOk)
/**
* Récupère un groupe (ce serait plus logique sur GET /api/groupe/:oid mais on a déjà plein de route /api/groupe/actionQcq)
* @route GET /api/groupe/byId/:oid
* Ajoute un membre à un groupe. Il faut les propriétés groupeNom, personneNom, pid
* @route POST /api/groupe/ajouterMembre
*/
controller.post('ajouterMembre', function (context) {
const {groupeNom, personneNom, pid} = context.post
const myOid = $accessControl.getCurrentUserOid(context)
// checks de base
if (!myOid) return $json.denied(context, 'Vous devez être authentifié pour ajouter un membre à un groupe')
if (!groupeNom) return $json.sendKo(context, 'Impossible de modifier un groupe sans nom')
if (!personneNom || !pid || typeof personneNom !== 'string' || typeof pid !== 'string') return $json.sendKo(context, 'Paramètres invalides')
// on charge le groupe pour vérifier qu'on en est gestionnaire
let groupe
flow().seq(function () {
$groupeRepository.loadByNom(groupeNom, this)
}).seq(function (_groupe) {
if (!isManaged(context, _groupe)) return $json.denied(context, 'Vous n’êtes pas gestionnaire de ce groupe et ne pouvez pas modifier ses membres')
groupe = _groupe
$personneRepository.loadByPidAndNom(pid, personneNom, this)
}).seq(function (personne) {
$personneRepository.ajouterGroupesMembre(personne, groupe, this)
}).seq(function (personne) {
// on renvoie pas tout
const {nom, oid, pid, prenom} = personne
$json.sendOk(context, {membre: {nom, oid, pid, prenom}})
}).catch((error) => $json.sendKo(context, error))
})
/**
* Retire un membre d'un groupe. Il faut les propriétés groupeNom, personneOid
* @route POST /api/groupe/retirerMembre
*/
controller.post('retirerMembre', function (context) {
const {groupeNom, personneOid} = context.post
const myOid = $accessControl.getCurrentUserOid(context)
// checks de base
if (!myOid) return $json.denied(context, 'Vous devez être authentifié pour ajouter un membre à un groupe')
if (!groupeNom) return $json.sendKo(context, 'Impossible de modifier un groupe sans nom')
if (!groupeNom || !personneOid || typeof groupeNom !== 'string' || typeof personneOid !== 'string') return $json.sendKo(context, 'Paramètres invalides')
// on charge le groupe pour vérifier qu'on en est gestionnaire
let groupe
flow().seq(function () {
$groupeRepository.loadByNom(groupeNom, this)
}).seq(function (_groupe) {
if (!isManaged(context, _groupe)) return $json.denied(context, 'Vous n’êtes pas gestionnaire de ce groupe et ne pouvez pas modifier ses membres')
groupe = _groupe
$personneRepository.load(personneOid, this)
}).seq(function (personne) {
if (!personne) return $json.sendKo(context, `La personne ${personneOid} n’existe pas`)
$personneRepository.retirerGroupesMembre(personne, groupe, this)
}).seq(function (personne) {
// on renvoie pas tout
const {nom, oid, pid, prenom} = personne
$json.sendOk(context, {membre: {nom, oid, pid, prenom}})
}).catch((error) => $json.sendKo(context, error))
})
/**
* Récupère un groupe (d'après son oid)
* @route GET /api/groupe/:oid
*/
controller.get(':oid', function (context) {
const {oid} = context.arguments
......@@ -168,7 +227,7 @@ module.exports = function (component) {
})
/**
* Récupère les détails d'un groupe
* Récupère un groupe d'après son nom
* @route GET /api/groupe/byNom/:nom
*/
controller.get('byNom/:nom', function (context) {
......@@ -234,6 +293,38 @@ module.exports = function (component) {
})
})
controller.get('listerMembres/:nom', function (context) {
const myOid = $accessControl.getCurrentUserOid(context)
if (!myOid) return $json.denied(context, 'Vous devez être authentifié pour lister les membres du groupe')
const nom = context.arguments.nom.trim()
const options = {
skip: Number(context.get.skip) || 0,
limit: Number(context.get.limit) || 100
}
const data = options
flow().seq(function () {
$groupeRepository.loadByNom(nom, this)
}).seq(function (groupe) {
if (!groupe) return $json.notFound(context, `Le groupe « ${nom} » n’existe pas`)
if (!isManaged(context, groupe)) return $json.denied(context, 'Vous n’êtes pas gestionnaire de ce groupe et ne pouvez pas en lister les membres')
$personneRepository.countMembres(nom, this)
}).seq(function (nb) {
data.nbMembres = nb
if (!nb) return this(null, [])
$personneRepository.loadMembres(nom, options, this)
}).seq(function (personnes) {
data.membres = personnes.map(({nom, oid, pid, prenom}) => ({nom, oid, pid, prenom}))
$personneRepository.countAbonnes(nom, this)
}).seq(function (nbAbonnes) {
data.nbAbonnes = nbAbonnes
$json.sendOk(context, data)
}).catch(function (error) {
$json.sendKo(context, error)
})
})
/**
* Retire le groupe au user courant
* @route GET /groupe/quitter/:nom
......@@ -334,7 +425,7 @@ module.exports = function (component) {
/**
* Delete groupe par nom, retourne {@link reponseDeleted}
* @route DELETE /api/groupe/:
* @route DELETE /api/groupe/:nom
* @param {String} nom
*/
controller.delete(':nom', function (context) {
......
......@@ -49,7 +49,7 @@ module.exports = function (component) {
* @param {string[]} [options.whiteList] Une liste de noms de groupes que l'on peut ajouter sans vérifier si on est membre ou pas
* @param {errorCallback} next
*/
function addGroupe (context, ressource, groupeNom, options, next) {
function addGroupeToRess (context, ressource, groupeNom, options, next) {
if (!ressource) throw new Error('pas de ressource')
if (!groupeNom) throw new Error('pas de nom de groupe')
if (arguments.length === 4) {
......@@ -171,14 +171,14 @@ module.exports = function (component) {
const options = {
whiteList: (ressourceOriginale && ressourceOriginale.groupes) || []
}
addGroupe(context, ressourceNew, groupeNom, options, this)
addGroupeToRess(context, ressourceNew, groupeNom, options, this)
// @todo faudrait vérifier que l'on a pas viré de groupe auxquel on appartient pas, et si c'était le cas cloner ?
}).set(groupesAuteursVoulus).seqEach(function (groupeNom) {
const options = {
whiteList: (ressourceOriginale && ressourceOriginale.groupesAuteurs) || [],
isGroupeAuteur: true
}
addGroupe(context, ressourceNew, groupeNom, options, this)
addGroupeToRess(context, ressourceNew, groupeNom, options, this)
}).seq(function () {
next(null, ressourceNew)
}).catch(addErrorAndNext)
......
......@@ -40,40 +40,46 @@ const {looksLikePid} = require('../../utils/validators')
module.exports = function (component) {
component.service('$personneRepository', function (EntityPersonne, EntityGroupe, $cachePersonne, $groupeRepository) {
/**
* Ajoute un groupe à la personne, comme membre (en le créant s'il n'existait pas),
* @param {Personne|EntityPersonne} personne (sa prop groupesMembre sera modifiée)
* @param {string} groupeNom
* @param {errorCallback} next
* Ajoute un groupe à la personne, comme membre
* @param {EntityPersonne} personne
* @param {EntityGroupe} groupe
* @param {personneCallback} next appelé avec personne sauvegardé
* @memberOf $personneRepository
*/
function addGroupe (personne, groupeNom, next) {
const oid = personne.oid
if (!oid) return next(new Error('Impossible d’ajouter un groupe à une personne sans oid'))
function ajouterGroupesMembre (personne, groupe, next) {
if (!personne || !personne.oid || !groupe || !groupe.oid) return next(new Error('paramètres invalides'))
const {oid} = personne
const {nom} = groupe
if (!Array.isArray(personne.groupesMembre)) personne.groupesMembre = []
if (personne.groupesMembre.includes(groupeNom)) return next(Error(`La personne ${oid} était déjà membre du groupe ${groupeNom}`))
if (personne.groupesMembre.includes(nom)) {
console.error(Error(`La personne ${oid} était déjà membre du groupe ${nom}`))
return next(null, personne)
}
personne.groupesMembre.push(nom)
personne.store(next)
}
// on veut une EntityPersonne si on en a pas déjà une (faudra la sauvegarder)
const entityPersonne = personne.store ? personne : EntityPersonne.create(personne)
/**
* @callback nbCallback
* @param {Error} [error]
* @param {number} nombre entier positif ou nul
*/
/**
* Retourne le nb de followers d'un groupe
* @param {string} groupeNom
* @param {nbCallback} next
*/
function countAbonnes (groupeNom, next) {
EntityPersonne.match('groupesSuivis').equals(groupeNom).count(next)
}
// faut regarder si le groupe existe
flow().seq(function () {
$groupeRepository.loadByNom(groupeNom, this)
}).seq(function (groupe) {
if (groupe) return this(null, groupe)
// sinon on le crée
const newGroupe = {
nom: groupeNom,
gestionnaires: [oid]
}
EntityGroupe.create(newGroupe).store(this)
}).seq(function (groupe) {
entityPersonne.groupesMembre.push(groupe.nom)
entityPersonne.store(this)
}).seq(function (personneBdd) {
// on mute le groupesMembre du personne passé en argument
personne.groupesMembre = personneBdd.groupesMembre
next()
}).catch(next)
/**
* Retourne le nb de followers d'un groupe
* @param {string} groupeNom
* @param {nbCallback} next
*/
function countMembres (groupeNom, next) {
EntityPersonne.match('groupesMembre').equals(groupeNom).count(next)
}
/**
......@@ -180,6 +186,24 @@ module.exports = function (component) {
}).catch(next)
}
/**
* Retourne la liste des membres d'un groupe
* @param {string} groupeNom
* @param {object} [options]
* @param {object} [options.skip] offset
* @param {object} [options.limit=500] le nb max de personnes à retourner
* @param {entityPersonnesCallback} next
*/
function loadMembres (groupeNom, options, next) {
if (typeof options === 'function') {
next = options
options = {}
}
if (!options.limit) options.limit = 100
if (typeof options.skip !== 'number') options.skip = 0
EntityPersonne.match('groupesMembre').equals(groupeNom).sort('nom').grab(options, next)
}
/**
* Efface un groupe chez toutes les personnes qui en sont membre ou qui le suivent
* @param {string} groupName Nom du groupe
......@@ -258,6 +282,25 @@ module.exports = function (component) {
updateMembres(0)
}
/**
* Retire un groupe à la personne, comme membre,
* @param {EntityPersonne} personne
* @param {EntityGroupe} groupe
* @param {personneCallback} next appelé avec personne sauvegardé
* @memberOf $personneRepository
*/
function retirerGroupesMembre (personne, groupe, next) {
if (!personne || !personne.oid || !groupe || !groupe.oid) return next(new Error('paramètres invalides'))
const {oid} = personne
const {nom} = groupe
if (!Array.isArray(personne.groupesMembre) || !personne.groupesMembre.includes(nom)) {
log.error(Error(`La personne ${oid} n’était pas membre du groupe ${nom}`))
return next(null, personne)
}
personne.groupesMembre = personne.groupesMembre.filter(groupeNom => groupeNom !== nom)
personne.store(next)
}
/**
* Enregistre une personne en bdd (et met à jour le cache)
* @param {EntityPersonne} personne
......@@ -315,14 +358,18 @@ module.exports = function (component) {
* @service $personneRepository
*/
const $personneRepository = {
addGroupe,
ajouterGroupesMembre,
countAbonnes,
countMembres,
delete: deletePersonne,
load,
loadByOids,
loadByPidAndNom,
loadByPids,
loadMembres,
removeGroup,
renameGroup,
retirerGroupesMembre,
save,
updateOrCreate
}
......