Source: personne/controllerApiGroupe.js


'use strict'

const flow = require('an-flow')
const { hasProp } = require('sesajstools')

/**
 * Répond sur certaines requetes OPTIONS
 * @private
 * @param {Context} context
 */
function optionsOk (context) {
  context.next(null, 'OK') // ne pas renvoyer de chaîne vide sinon 404
}

module.exports = function (component) {
  component.controller('api/groupe', function (EntityGroupe, $groupeRepository, $accessControl, $json, $personneRepository, $groupe, $session) {
    // les méthodes de $groupe qui nous intéressent
    const { addGestionnairesNames, followGroup, ignoreGroup, isManaged, isMemberOf, joinGroup, joinAndFollowGroup, quitGroup } = $groupe

    /**
     * Controleur de la route /api/groupe/
     * @Controller controllerApiGroupe
     */
    const controller = this

    let $ressourceRepository

    /**
     * Crée ou update un groupe
     * @route POST /api/groupe
     */
    controller.post('', function (context) {
      const data = context.post
      const myOid = $accessControl.getCurrentUserOid(context)
      const sendInternalError = (error) => $json.sendKo(context, error)

      // checks de base
      if (!myOid) return $json.denied(context, 'Vous devez être authentifié pour créer des groupes')
      if (!data.nom) return $json.sendKo(context, 'Impossible de créer un groupe sans nom')
      data.nom = data.nom.trim()
      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)
        // à 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)
      }).seq(function (groupeBdd) {
        const nextStep = this
        if (data.oid) {
          if (!groupeBdd) return $json.notFound(context, `Le groupe d’identifiant ${data.oid} n’existe pas`)
          if (!isManaged(context, groupeBdd)) return $json.denied(context, 'Vous n’êtes pas gestionnaire de ce groupe et ne pouvez pas le modifier')

          // si le nom change pas on passe à la suite
          const oldName = groupeBdd.nom
          const newName = data.nom
          if (!newName || newName === oldName) return nextStep(null, groupeBdd)

          // mais sinon faut répercuter partout
          flow().seq(function () {
            $personneRepository.renameGroup(oldName, newName, this)
          }).seq(function () {
            $session.renameGroup(context, oldName, newName)
            if (!$ressourceRepository) $ressourceRepository = lassi.service('$ressourceRepository')
            $ressourceRepository.renameGroup(oldName, newName, this)
          }).seq(function () {
            // maj personne & ressource ok, on peut changer le nom du groupe
            groupeBdd.nom = newName
            nextStep(null, groupeBdd)
          }).catch(sendInternalError)
          // fin rename group
        } else {
          if (groupeBdd) {
            return $json.sendKo(context, `Le groupe « ${data.nom} » existe déjà`)
          }
          const groupe = {
            nom: data.nom,
            gestionnaires: [myOid] // à la création c'est imposé
          }
          nextStep(null, EntityGroupe.create(groupe))
        }

      // on peut passer aux autres propriétés du groupe
      }).seq(function (groupe) {
        // les booléens
        if (hasProp(data, 'ouvert')) groupe.ouvert = Boolean(data.ouvert)
        if (hasProp(data, 'public')) groupe.public = Boolean(data.public)
        // description
        if (hasProp(data, 'description')) groupe.description = data.description
        // et les gestionnaires à ajouter éventuellement, on shunt si les deux champs sont vides
        // (si seul l'un des deux est vide ça tentera le chargement et renverra une erreur)
        if (!data.newGestionnairePid && !data.newGestionnaireNom) return this(null, groupe)
        // on va chercher l'oid de ce nouveau gestionnaire
        $personneRepository.loadByPidAndNom(data.newGestionnairePid, data.newGestionnaireNom, (error, personne) => {
          if (error) return this(error)
          if (!groupe.gestionnaires.includes(personne.oid)) groupe.gestionnaires.push(personne.oid)
          this(null, groupe)
          // else faudrait ajouter un warning, mais le front redirige s'il récupère du 200, on laisse tomber
        })
      }).seq(function (groupe) {
        groupe.store(this)
      }).seq(function (groupe) {
        // à la création, faut imposer joinAndFollow + ajouter les gestionnairesNames
        // lors d'une modif faut ajouter les gestionnairesNames s'ils ont changé
        // vu que cette étape est assez rare on simplifie le back en renvoyant toujours tout
        // (le front pourra évoluer comme il veut sans répercussions ici)
        if (data.oid) return this(null, groupe)

        joinAndFollowGroup(context, groupe.nom, (error) => {
          if (error) return this(error)
          this(null, groupe)
        })
      }).seq(function (groupe) {
        addGestionnairesNames(context, groupe, this)
      }).seq(function (groupe) {
        $json.sendOk(context, groupe)
      }).catch(sendInternalError)
    })
    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
     */
    controller.get(':oid', function (context) {
      const { oid } = context.arguments
      const isFullFormat = context.get.format === 'full'
      flow().seq(function () {
        $groupeRepository.load(oid, this)
      }).seq(function (groupe) {
        if (!groupe) return $json.notFound(context, `Le groupe d’identifiant ${oid} n’existe pas`)
        if (isFullFormat) addGestionnairesNames(context, groupe, this)
        else this(null, groupe)
      }).seq(function (groupe) {
        $json.sendOk(context, groupe)
      }).catch(function (error) {
        $json.sendKo(context, error)
      })
    })

    /**
     * Récupère les détails d'un groupe
     * @route GET /api/groupe/byNom/:nom
     */
    controller.get('byNom/:nom', function (context) {
      const { nom } = context.arguments
      const isFullFormat = context.get.format === 'full'
      flow().seq(function () {
        $groupeRepository.loadByNom(nom, this)
      }).seq(function (groupe) {
        if (!groupe) return $json.notFound(context, `Le groupe « ${nom} » n’existe pas`)
        if (isFullFormat) addGestionnairesNames(context, groupe, this)
        else this(null, groupe)
      }).seq(function (groupe) {
        $json.sendOk(context, groupe)
      }).catch(function (error) {
        $json.sendKo(context, error)
      })
    })

    /**
     * Create un groupe en donnant seulement le nom
     * @route GET /api/groupe/ajouter/:nom
     */
    controller.get('ajouter/:nom', function (context) {
      if (context.perf) {
        const msg = 'start-pers-' + context.post.id
        log.perf(context.response, msg)
      }
      if (!$accessControl.hasGenericPermission('createGroupe', context)) return $json.denied(context)
      const nom = context.arguments.nom
      flow().seq(function () {
        $groupeRepository.loadByNom(nom, this)
      }).seq(function (groupeBdd) {
        if (groupeBdd) return $json.sendKo(context, `Le groupe ${nom} existe déjà`)

        // on crée un nouveau groupe, par défaut fermé et public
        const groupe = EntityGroupe.create({
          nom,
          ouvert: false,
          public: true,
          gestionnaires: [$accessControl.getCurrentUserOid(context)]
        })
        groupe.store(this)
      }).seq(function (groupe) {
        joinAndFollowGroup(context, groupe.nom, this)
      }).seq(function () {
        $json.sendOk(context)
      }).catch(function (error) {
        $json.sendKo(context, error)
      })
    })

    /**
     * Ne plus suivre le groupe
     * @route GET /api/groupe/ignorer/:nom
     */
    controller.get('ignorer/:nom', function (context) {
      if (!$accessControl.isAuthenticated(context)) return $json.denied(context, 'Il faut être authentifié pour ignorer un groupe')
      ignoreGroup(context, context.arguments.nom, (error) => {
        if (error) return $json.sendKo(context, error)
        return $json.sendOk(context)
      })
    })

    /**
     * Retire le groupe au user courant
     * @route GET /groupe/quitter/:nom
     */
    controller.get('quitter/:nom', function (context) {
      if (!$accessControl.isAuthenticated(context)) return $json.denied(context, 'Il faut être authentifié pour quitter un groupe')
      quitGroup(context, context.arguments.nom, (error) => {
        if (error) return $json.sendKo(context, error)
        return $json.sendOk(context)
      })
    })

    /**
     * Abonne le user courant au groupe
     * @route GET /groupe/suivre/:nom
     */
    controller.get('suivre/:nom', function (context) {
      if (!$accessControl.isAuthenticated(context)) return $json.denied(context, 'Il faut être authentifié pour suivre un groupe')
      const nom = context.arguments.nom
      let groupe
      flow().seq(function () {
        $groupeRepository.loadByNom(nom, this)
      }).seq(function (grp) {
        if (!grp) return $json.sendKo(context, `Le groupe ${nom} n’existe pas`)
        if (!grp.public && !isManaged(context, grp)) return $json.sendKo(context, `Vous n’avez pas les droits suffisant pour suivre le groupe ${nom}`)
        groupe = grp
        followGroup(context, nom, this)
      }).seq(function () {
        addGestionnairesNames(context, groupe, this)
      }).seq(function () {
        $json.sendOk(context, groupe)
      }).catch(function (error) {
        $json.sendKo(context, error)
      })
    })

    /**
     * Ajoute le user courant au groupe
     * @route GET /groupe/rejoindre/:nom
     */
    controller.get('rejoindre/:nom', function (context) {
      if (!$accessControl.isAuthenticated(context)) return $json.denied(context, 'Il faut être authentifié pour rejoindre un groupe')
      const nom = context.arguments.nom
      let groupe
      flow().seq(function () {
        $groupeRepository.loadByNom(nom, this)
      }).seq(function (grp) {
        if (!grp) return $json.sendKo(context, `Le groupe ${nom} n’existe pas`)
        if (!grp.ouvert && !isMemberOf(context, grp) && !isManaged(context, grp)) return $json.sendKo(context, `Vous n’avez pas les droits suffisant pour rejoindre le groupe ${nom}`)
        groupe = grp
        joinGroup(context, nom, this)
      }).seq(function () {
        addGestionnairesNames(context, groupe, this)
      }).seq(function () {
        $json.sendOk(context, groupe)
      }).catch(function (error) {
        $json.sendKo(context, error)
      })
    })

    /**
     * Efface un groupe d'après son nom, appellera denied ou $json avec error ou deleted:nom
     * @private
     * @param {Context} context
     * @param nom
     */
    function deleteAndSend (context, nom) {
      log.debug('dans cb api deleteGroupe ' + nom)
      const myOid = $accessControl.getCurrentUserOid(context)
      // faut charger le groupe pour vérifier si l'utilisateur est admin (elle est probablement en cache)
      $groupeRepository.loadByNom(nom, function (error, groupe) {
        if (error) return $json.sendKo(context, error)
        if (!groupe) return $json.notFound(context, `Le groupe ${nom} n’existe pas`)
        if (!groupe.gestionnaires.includes(myOid)) return $json.denied(context, 'Vous n’avez pas le droit de supprimer ce groupe')
        flow().seq(function () {
          $groupeRepository.delete(nom, this)
        }).seq(function () {
          $personneRepository.removeGroup(nom, this)
        }).seq(function () {
          $json.sendOk(context, { deleted: nom })
        }).catch(function (error) {
          $json.sendKo(context, error)
        })
      })
    }

    /**
     * Répond ok pour les options delete
     * @private
     * @param {Context} context
     */
    function optionsDeleteOk (context) {
      context.setHeader('Access-Control-Allow-Methods', 'DELETE,OPTIONS')
      // context.setHeader('Access-Control-Allow-Headers', 'Origin,Content-Type,Accept')
      // et on laisse le middleware CORS faire son boulot
      context.next(null, 'OK') // ne pas renvoyer de chaîne vide sinon 404
    }

    /**
     * Delete groupe par nom, retourne {@link reponseDeleted}
     * @route DELETE /api/groupe/:
     * @param {String} nom
     */
    controller.delete(':nom', function (context) {
      deleteAndSend(context, context.arguments.nom)
    })

    controller.options(':nom', optionsDeleteOk)
  })
}