Group Model

License

Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

module.exports = do ->

Dependencies

  ld = require 'lodash'
  cuid = require 'cuid'
  storage = require '../storage.js'
  common = require './common.js'
  GPREFIX = storage.DBPREFIX.GROUP
  UPREFIX = storage.DBPREFIX.USER
  PPREFIX = storage.DBPREFIX.PAD

Description

Groups belongs to users. Each user can have multiple groups of pads.

A group object can be represented like :

var group = { _id: 'autoGeneratedUniqueString', name: 'group1', pads: [ 'padkey1', 'padkey2' ], admins: [ 'userkey1', 'userkey2' ], users: [ 'ukey1' ], visibility: 'restricted' || 'public' || 'private', password: 'secret', readonly: false };

  group = {}

Public Functions

get

Group reading

This function uses common.getDel with del to false and GPREFIX fixed. It will takes mandatory key string and callback function. See common.getDel for documentation.

  group.get = ld.partial common.getDel, false, GPREFIX

set

This function adds a new group or updates an existing one. It checks the fields, throws error if needed, set defaults options. As arguments, it takes mandatory :

set creates an empty pads array in case of creation, otherwise it just gets back old value. pads array contains ids of pads attached to the group via model.pad creation or update.

  group.set = (params, callback) ->
    common.addSetInit params, callback, ['name', 'admin']
    g = group.fn.assignProps params
    if params._id
      g._id = params._id
      storage.db.get GPREFIX + g._id, (err, res) ->
        return callback err if err
        return callback new Error 'group does not exist' if not res
        g.pads = res.pads
        group.fn.checkSet g, callback
    else
      g._id = cuid()
      g.pads = []
      group.fn.checkSet g, callback

del

Group removal

This function uses common.getDel with del to true and GPREFIX fixed. It will takes mandatory key string and callback function. See common.getDel for documentation.

It uses the callback function to handle secondary indexes for users and pads. /

  group.del = (key, callback) ->
    if not ld.isFunction callback
      throw new TypeError 'callback must be a function'
    common.getDel true, GPREFIX, key, (err, g) ->
      return callback err if err
      group.fn.indexUsers true, g, (err) ->
        return callback err if err
        group.fn.cascadePads g, callback

Helper Functions

Helper here are public functions created to facilitate interaction with the API and improve performance, avoiding extra checking when not needed. TODO : may be written to improve API usage

  group.helper = {}

linkPads

linkPads is a function to attach new pads to an existing group. It takes mandatory arguments :

  group.helper.linkPads = ld.noop

  group.helper.unlinkPads = ld.noop

inviteUsers

string or array

  group.helper.inviteUsers = ld.noop

setAdmins

string or array

  group.helper.setAdmins = ld.noop

setPassword

string of false

  group.helper.setPassword = ld.noop

setPublic

boolean

  group.helper.setPublic = ld.noop

archive

boolean

  group.helper.archive = ld.noop

Internal Functions

These functions are not private like with closures, for testing purposes, but they are expected be used only internally by other MyPads functions. All of these are tested through public API.

  group.fn = {}

assignProps

assignProps takes params object and assign defaults if needed. It creates :

It returns the group object.

  group.fn.assignProps = (params) ->
    p = params
    g = name: p.name
    if ld.isArray(p.admins)
      p.admins = ld.filter(p.admins, ld.isString)
    else
      p.admins = []
    g.admins = ld.union [ p.admin ], p.admins
    g.users = ld.uniq p.users
    v = p.visibility
    vVal = ['restricted', 'private', 'public']
    isValidVisib = ld.isString(v) and ld.includes(vVal, v)
    g.visibility = if isValidVisib then v else 'restricted'
    g.password = if ld.isString p.password then p.password else null
    g.readonly = if ld.isBoolean p.readonly then p.readonly else false
    g

cascadePads

cascadePads is an asynchronous function which handle cascade removals after group removal. It takes :

  group.fn.cascadePads = (group, callback) ->
    if not ld.isEmpty group.pads
      padsKeys = ld.map group.pads, (p) -> PPREFIX + p
      storage.fn.delKeys padsKeys, (err, res) ->
        return callback err if err
        e = 'something goes wrong with cascade pads removal'
        return callback new Error e if not res
        callback null
    else
      callback null

indexUsers

indexUsers is an asynchronous function which handles secondary indexes for users.groups after group creation, update, removal. It takes :

  group.fn.indexUsers = (del, group, callback) ->
    usersKeys = ld.map ld.union(group.admins, group.users), (u) ->
      UPREFIX + u
    storage.fn.getKeys usersKeys, (err, users) ->
      return callback err if err
      ld.forIn users, (u, k) ->
        if del
          ld.pull u.groups, group._id
        else
          if not ld.includes u.groups, group._id
            u.groups.push group._id
        users[k] = u
      storage.fn.setKeys users, (err) ->
        return callback err if err
        callback null

set

set is internal function that set the user group into the database. It takes care of secondary indexes for users and pads by calling indexUsers.

It takes, as arguments :

  group.fn.set = (g, callback) ->
    storage.db.set GPREFIX + g._id, g, (err) ->
      return callback err if err
      group.fn.indexUsers false, g, (err) ->
        return callback err if err
        callback null, g

checkSet

checkSet will ensure that all users and pads exist. If true, it calls fn.set, else it will return an Error. checkSet takes :

  group.fn.checkSet = (g, callback) ->
    pre = ld.curry (pre, val) -> pre + val
    admins = ld.map g.admins, pre UPREFIX
    users = ld.map g.users, pre UPREFIX
    pads = ld.map g.pads, pre PPREFIX
    allKeys = ld.union admins, users, pads
    common.checkMultiExist allKeys, (err, res) ->
      return callback err if err
      if not res
        e = 'Some users, admins or pads have not been found'
        return callback new Error e
      group.fn.set g, callback

  return group