2017-07-24 16:24:21 +01:00
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
2018-09-26 17:59:56 +01:00
package user
2017-07-24 16:24:21 +01:00
import (
"database/sql"
"fmt"
2018-02-28 14:55:36 +00:00
"strconv"
2017-07-24 16:24:21 +01:00
"strings"
"time"
2019-04-01 12:02:23 +01:00
"github.com/documize/community/core/env"
2017-07-24 16:24:21 +01:00
"github.com/documize/community/domain"
2018-09-26 17:59:56 +01:00
"github.com/documize/community/domain/store"
2017-07-26 10:50:26 +01:00
"github.com/documize/community/model/user"
2017-10-05 15:02:39 -04:00
"github.com/jmoiron/sqlx"
2017-07-24 16:24:21 +01:00
"github.com/pkg/errors"
)
2018-09-26 17:59:56 +01:00
// Store provides data access to space information.
type Store struct {
store . Context
2018-09-27 15:14:48 +01:00
store . UserStorer
2017-07-26 10:50:26 +01:00
}
2017-07-24 16:24:21 +01:00
// Add adds the given user record to the user table.
2018-09-26 17:59:56 +01:00
func ( s Store ) Add ( ctx domain . RequestContext , u user . User ) ( err error ) {
2017-07-24 16:24:21 +01:00
u . Created = time . Now ( ) . UTC ( )
u . Revised = time . Now ( ) . UTC ( )
2018-09-26 17:59:56 +01:00
_ , err = ctx . Transaction . Exec ( s . Bind ( "INSERT INTO dmz_user (c_refid, c_firstname, c_lastname, c_email, c_initials, c_password, c_salt, c_reset, c_lastversion, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ) ,
2018-03-22 17:29:59 +00:00
u . RefID , u . Firstname , u . Lastname , strings . ToLower ( u . Email ) , u . Initials , u . Password , u . Salt , "" , u . LastVersion , u . Created , u . Revised )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute user insert" )
}
return
}
// Get returns the user record for the given id.
2018-09-26 17:59:56 +01:00
func ( s Store ) Get ( ctx domain . RequestContext , id string ) ( u user . User , err error ) {
err = s . Runtime . Db . Get ( & u , s . Bind ( `
2018-09-19 16:03:29 +01:00
SELECT id , c_refid AS refid , c_firstname AS firstname , c_lastname AS lastname , c_email AS email ,
c_initials AS initials , c_globaladmin AS globaladmin , c_password AS password , c_salt AS salt , c_reset AS reset ,
c_lastversion AS lastversion , c_created AS created , c_revised AS revised
FROM dmz_user
2018-09-26 17:59:56 +01:00
WHERE c_refid = ? ` ) ,
2018-09-19 16:03:29 +01:00
id )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "unable to execute select for user %s" , id ) )
}
return
}
// GetByDomain matches user by email and domain.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetByDomain ( ctx domain . RequestContext , domain , email string ) ( u user . User , err error ) {
2017-07-24 16:24:21 +01:00
email = strings . TrimSpace ( strings . ToLower ( email ) )
2018-09-26 17:59:56 +01:00
err = s . Runtime . Db . Get ( & u , s . Bind ( ` SELECT u . id , u . c_refid AS refid ,
2018-09-19 16:03:29 +01:00
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised
FROM dmz_user u , dmz_user_account a , dmz_org o
2018-09-26 17:59:56 +01:00
WHERE TRIM ( LOWER ( u . c_email ) ) = ? AND u . c_refid = a . c_userid AND a . c_orgid = o . c_refid AND TRIM ( LOWER ( o . c_domain ) ) = ? ` ) ,
2017-09-25 14:37:11 +01:00
email , domain )
2017-07-24 16:24:21 +01:00
if err != nil && err != sql . ErrNoRows {
err = errors . Wrap ( err , fmt . Sprintf ( "Unable to execute GetUserByDomain %s %s" , domain , email ) )
}
return
}
// GetByEmail returns a single row match on email.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetByEmail ( ctx domain . RequestContext , email string ) ( u user . User , err error ) {
2017-07-24 16:24:21 +01:00
email = strings . TrimSpace ( strings . ToLower ( email ) )
2018-09-26 17:59:56 +01:00
err = s . Runtime . Db . Get ( & u , s . Bind ( ` SELECT u . id , u . c_refid AS refid ,
2018-09-19 16:03:29 +01:00
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised
FROM dmz_user u
2018-09-26 17:59:56 +01:00
WHERE TRIM ( LOWER ( u . c_email ) ) = ? ` ) ,
2018-09-19 16:03:29 +01:00
email )
2017-07-24 16:24:21 +01:00
if err != nil && err != sql . ErrNoRows {
err = errors . Wrap ( err , fmt . Sprintf ( "execute select user by email %s" , email ) )
}
return
}
// GetByToken returns a user record given a reset token value.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetByToken ( ctx domain . RequestContext , token string ) ( u user . User , err error ) {
err = s . Runtime . Db . Get ( & u , s . Bind ( ` SELECT u . id , u . c_refid AS refid ,
2018-09-19 16:03:29 +01:00
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised
FROM dmz_user u
2018-09-26 17:59:56 +01:00
WHERE u . c_reset = ? ` ) ,
2018-09-19 16:03:29 +01:00
token )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "execute user select by token %s" , token ) )
}
return
}
// GetBySerial is used to retrieve a user via their temporary password salt value!
// This occurs when we you share a folder with a new user and they have to complete
// the onboarding process.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetBySerial ( ctx domain . RequestContext , serial string ) ( u user . User , err error ) {
err = s . Runtime . Db . Get ( & u , s . Bind ( ` SELECT u . id , u . c_refid AS refid ,
2018-09-19 16:03:29 +01:00
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised
FROM dmz_user u
2018-09-26 17:59:56 +01:00
WHERE u . c_salt = ? ` ) ,
2018-09-19 16:03:29 +01:00
serial )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "execute user select by serial %s" , serial ) )
}
return
}
// GetActiveUsersForOrganization returns a slice containing of active user records for the organization
// identified in the Persister.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetActiveUsersForOrganization ( ctx domain . RequestContext ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2018-09-26 17:59:56 +01:00
err = s . Runtime . Db . Select ( & u , s . Bind ( ` SELECT u . id , u . c_refid AS refid ,
2018-09-19 16:03:29 +01:00
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised ,
2018-09-19 16:03:29 +01:00
a . c_active AS active , a . c_editor AS editor , a . c_admin AS admin , a . c_users AS viewusers , a . c_analytics AS analytics
FROM dmz_user u , dmz_user_account a
2019-04-01 12:02:23 +01:00
WHERE u . c_refid = a . c_userid AND a . c_orgid = ? AND a . c_active = ` +s.IsTrue()+ `
2018-09-26 17:59:56 +01:00
ORDER BY u . c_firstname , u . c_lastname ` ) ,
2017-07-26 10:50:26 +01:00
ctx . OrgID )
2017-07-24 16:24:21 +01:00
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-01 19:14:27 +00:00
err = nil
}
2017-07-24 16:24:21 +01:00
if err != nil {
2017-07-26 10:50:26 +01:00
err = errors . Wrap ( err , fmt . Sprintf ( "get active users by org %s" , ctx . OrgID ) )
2017-07-24 16:24:21 +01:00
}
return
}
// GetUsersForOrganization returns a slice containing all of the user records for the organizaiton
2018-09-19 16:03:29 +01:00
// identified in the context.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetUsersForOrganization ( ctx domain . RequestContext , filter string , limit int ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2018-03-01 19:14:27 +00:00
filter = strings . TrimSpace ( strings . ToLower ( filter ) )
likeQuery := ""
if len ( filter ) > 0 {
2018-09-19 16:03:29 +01:00
likeQuery = " AND (LOWER(u.c_firstname) LIKE '%" + filter + "%' OR LOWER(u.c_lastname) LIKE '%" + filter + "%' OR LOWER(u.c_email) LIKE '%" + filter + "%') "
2018-03-01 19:14:27 +00:00
}
2019-04-01 12:02:23 +01:00
if s . Runtime . StoreProvider . Type ( ) == env . StoreTypeSQLServer {
err = s . Runtime . Db . Select ( & u , s . Bind ( ` SELECT TOP( ` + strconv . Itoa ( limit ) + ` ) u . id , u . c_refid AS refid ,
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
u . c_created AS created , u . c_revised AS revised ,
a . c_active AS active , a . c_editor AS editor , a . c_admin AS admin , a . c_users AS viewusers , a . c_analytics AS analytics
FROM dmz_user u , dmz_user_account a
WHERE u . c_refid = a . c_userid AND a . c_orgid = ? ` + likeQuery +
` ORDER BY u.c_firstname, u.c_lastname ` ) , ctx . OrgID )
} else {
err = s . Runtime . Db . Select ( & u , s . Bind ( ` SELECT u . id , u . c_refid AS refid ,
2018-09-19 16:03:29 +01:00
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised ,
2018-09-19 16:03:29 +01:00
a . c_active AS active , a . c_editor AS editor , a . c_admin AS admin , a . c_users AS viewusers , a . c_analytics AS analytics
FROM dmz_user u , dmz_user_account a
WHERE u . c_refid = a . c_userid AND a . c_orgid = ? ` + likeQuery +
2019-04-01 12:02:23 +01:00
` ORDER BY u.c_firstname, u.c_lastname LIMIT ` + strconv . Itoa ( limit ) ) , ctx . OrgID )
}
2018-03-01 19:14:27 +00:00
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-01 19:14:27 +00:00
err = nil
}
2017-07-24 16:24:21 +01:00
if err != nil {
2017-07-26 10:50:26 +01:00
err = errors . Wrap ( err , fmt . Sprintf ( " get users for org %s" , ctx . OrgID ) )
2017-07-24 16:24:21 +01:00
}
return
}
2017-10-05 15:02:39 -04:00
// GetSpaceUsers returns a slice containing all user records for given space.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetSpaceUsers ( ctx domain . RequestContext , spaceID string ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2018-09-26 17:59:56 +01:00
err = s . Runtime . Db . Select ( & u , s . Bind ( ` SELECT u . id , u . c_refid AS refid ,
2018-09-19 16:03:29 +01:00
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised ,
2018-09-19 16:03:29 +01:00
a . c_active AS active , a . c_editor AS editor , a . c_admin AS admin , a . c_users AS viewusers , a . c_analytics AS analytics
FROM dmz_user u , dmz_user_account a
2019-04-01 12:02:23 +01:00
WHERE a . c_orgid = ? AND u . c_refid = a . c_userid AND a . c_active = ` +s.IsTrue()+ ` AND u . c_refid IN (
2018-09-19 16:03:29 +01:00
SELECT c_whoid from dmz_permission WHERE c_orgid = ? AND c_who = ' user ' AND c_scope = ' object ' AND c_location = ' space ' AND c_refid = ?
UNION ALL
SELECT r . c_userid from dmz_group_member r LEFT JOIN dmz_permission p ON p . c_whoid = r . c_groupid WHERE p . c_orgid = ? AND p . c_who = ' role ' AND p . c_scope = ' object ' AND p . c_location = ' space ' AND p . c_refid = ?
2017-09-18 13:02:15 +01:00
)
2018-10-07 13:58:30 +01:00
ORDER BY u . c_firstname , u . c_lastname ` ) ,
2018-09-19 16:03:29 +01:00
ctx . OrgID , ctx . OrgID , spaceID , ctx . OrgID , spaceID )
2017-07-24 16:24:21 +01:00
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-08 10:51:12 +00:00
err = nil
}
2017-07-24 16:24:21 +01:00
if err != nil {
2017-07-26 10:50:26 +01:00
err = errors . Wrap ( err , fmt . Sprintf ( "get space users for org %s" , ctx . OrgID ) )
2017-07-24 16:24:21 +01:00
}
return
}
2017-10-05 15:02:39 -04:00
// GetUsersForSpaces returns users with access to specified spaces.
2018-09-26 17:59:56 +01:00
func ( s Store ) GetUsersForSpaces ( ctx domain . RequestContext , spaces [ ] string ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2017-10-09 10:56:59 -04:00
if len ( spaces ) == 0 {
return
}
2019-02-12 12:15:09 +00:00
query , args , err := sqlx . In ( `
2018-09-19 16:03:29 +01:00
SELECT u . id , u . c_refid AS refid ,
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised ,
2018-09-19 16:03:29 +01:00
a . c_active AS active , a . c_editor AS editor , a . c_admin AS admin , a . c_users AS viewusers , a . c_analytics AS analytics
FROM dmz_user u , dmz_user_account a
2019-04-01 12:02:23 +01:00
WHERE a . c_orgid = ? AND u . c_refid = a . c_userid AND a . c_active = ` +s.IsTrue()+ ` AND u . c_refid IN (
2018-09-19 16:03:29 +01:00
SELECT c_whoid from dmz_permission WHERE c_orgid = ? AND c_who = ' user ' AND c_scope = ' object ' AND c_location = ' space ' AND c_refid IN ( ? )
UNION ALL
SELECT r . c_userid from dmz_group_member r LEFT JOIN dmz_permission p ON p . c_whoid = r . c_groupid WHERE p . c_orgid = ? AND p . c_who = ' role ' AND p . c_scope = ' object ' AND p . c_location = ' space ' AND p . c_refid IN ( ? )
2017-10-05 15:02:39 -04:00
)
2019-02-12 12:15:09 +00:00
ORDER BY u . c_firstname , u . c_lastname ` ,
2018-09-19 16:03:29 +01:00
ctx . OrgID , ctx . OrgID , spaces , ctx . OrgID , spaces )
2019-02-12 12:15:09 +00:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "GetUsersForSpaces IN query failed %s" , ctx . UserID ) )
return
}
2017-10-05 15:02:39 -04:00
query = s . Runtime . Db . Rebind ( query )
err = s . Runtime . Db . Select ( & u , query , args ... )
2017-08-17 13:47:43 +01:00
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-08 10:51:12 +00:00
err = nil
}
2017-08-17 13:47:43 +01:00
if err != nil {
2017-10-05 15:02:39 -04:00
err = errors . Wrap ( err , fmt . Sprintf ( "get users for spaces for user %s" , ctx . UserID ) )
2017-08-17 13:47:43 +01:00
}
return
}
2017-07-24 16:24:21 +01:00
// UpdateUser updates the user table using the given replacement user record.
2018-09-26 17:59:56 +01:00
func ( s Store ) UpdateUser ( ctx domain . RequestContext , u user . User ) ( err error ) {
2017-07-24 16:24:21 +01:00
u . Revised = time . Now ( ) . UTC ( )
u . Email = strings . ToLower ( u . Email )
2018-09-19 16:03:29 +01:00
_ , err = ctx . Transaction . NamedExec ( "UPDATE dmz_user SET c_firstname=:firstname, c_lastname=:lastname, c_email=:email, c_revised=:revised, c_initials=:initials, c_lastversion=:lastversion WHERE c_refid=:refid" , & u )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "execute user update %s" , u . RefID ) )
}
return
}
// UpdateUserPassword updates a user record with new password and salt values.
2018-09-26 17:59:56 +01:00
func ( s Store ) UpdateUserPassword ( ctx domain . RequestContext , userID , salt , password string ) ( err error ) {
_ , err = ctx . Transaction . Exec ( s . Bind ( "UPDATE dmz_user SET c_salt=?, c_password=?, c_reset='' WHERE c_refid=?" ) ,
salt , password , userID )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute user update" )
}
return
}
// DeactiveUser deletes the account record for the given userID and persister.Context.OrgID.
2018-09-26 17:59:56 +01:00
func ( s Store ) DeactiveUser ( ctx domain . RequestContext , userID string ) ( err error ) {
_ , err = ctx . Transaction . Exec ( s . Bind ( "DELETE FROM dmz_user_account WHERE c_userid=? and c_orgid=?" ) ,
userID , ctx . OrgID )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute user deactivation" )
}
return
}
// ForgotUserPassword sets the password to '' and the reset field to token, for a user identified by email.
2018-09-26 17:59:56 +01:00
func ( s Store ) ForgotUserPassword ( ctx domain . RequestContext , email , token string ) ( err error ) {
_ , err = ctx . Transaction . Exec ( s . Bind ( "UPDATE dmz_user SET c_reset=?, c_password='' WHERE LOWER(c_email)=?" ) ,
token , strings . ToLower ( email ) )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute password reset" )
}
return
}
// CountActiveUsers returns the number of active users in the system.
2018-11-05 19:48:50 +00:00
func ( s Store ) CountActiveUsers ( ) ( c [ ] domain . SubscriptionUserAccount ) {
2019-04-01 12:02:23 +01:00
err := s . Runtime . Db . Select ( & c , "SELECT c_orgid AS orgid, COUNT(*) AS users FROM dmz_user_account WHERE c_active=" + s . IsTrue ( ) + " GROUP BY c_orgid ORDER BY c_orgid" )
2017-07-24 16:24:21 +01:00
if err != nil && err != sql . ErrNoRows {
s . Runtime . Log . Error ( "CountActiveUsers" , err )
}
return
}
2018-02-28 14:55:36 +00:00
// MatchUsers returns users that have match to either firstname, lastname or email.
2018-09-26 17:59:56 +01:00
func ( s Store ) MatchUsers ( ctx domain . RequestContext , text string , maxMatches int ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2018-02-28 14:55:36 +00:00
text = strings . TrimSpace ( strings . ToLower ( text ) )
likeQuery := ""
if len ( text ) > 0 {
2018-09-19 16:03:29 +01:00
likeQuery = " AND (LOWER(c_firstname) LIKE '%" + text + "%' OR LOWER(c_lastname) LIKE '%" + text + "%' OR LOWER(c_email) LIKE '%" + text + "%') "
2018-02-28 14:55:36 +00:00
}
2018-09-26 17:59:56 +01:00
err = s . Runtime . Db . Select ( & u , s . Bind (
2018-09-19 16:03:29 +01:00
` SELECT u . id , u . c_refid AS refid ,
u . c_firstname AS firstname , u . c_lastname AS lastname , u . c_email AS email ,
u . c_initials AS initials , u . c_globaladmin AS globaladmin ,
u . c_password AS password , u . c_salt AS salt , u . c_reset AS reset , u . c_lastversion AS lastversion ,
2018-09-20 12:47:47 +01:00
u . c_created AS created , u . c_revised AS revised ,
2018-09-19 16:03:29 +01:00
a . c_active AS active , a . c_editor AS editor , a . c_admin AS admin , a . c_users AS viewusers , a . c_analytics AS analytics
FROM dmz_user u , dmz_user_account a
2019-04-01 12:02:23 +01:00
WHERE a . c_orgid = ? AND u . c_refid = a . c_userid AND a . c_active = ` +s.IsTrue()+likeQuery+ ` ORDER BY u . c_firstname , u . c_lastname LIMIT ` + strconv . Itoa ( maxMatches ) ) ,
2018-02-28 14:55:36 +00:00
ctx . OrgID )
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-02-28 14:55:36 +00:00
err = nil
}
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "matching users for org %s" , ctx . OrgID ) )
}
return
}