dictyBase/modware-user

View on GitHub
server/user.go

Summary

Maintainability
F
1 wk
Test Coverage
package server

import (
    "context"
    "fmt"
    "strings"

    "github.com/dictyBase/apihelpers/aphgrpc"
    "github.com/dictyBase/go-genproto/dictybaseapis/api/jsonapi"
    "github.com/dictyBase/go-genproto/dictybaseapis/user"
    "github.com/fatih/structs"
    "github.com/golang/protobuf/ptypes"
    "github.com/golang/protobuf/ptypes/any"
    "github.com/golang/protobuf/ptypes/empty"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/status"
    dat "gopkg.in/mgutz/dat.v2/dat"
    runner "gopkg.in/mgutz/dat.v2/sqlx-runner"
)

const (
    usrTableSel = `
            SELECT
                auth_user.auth_user_id,
                CAST(auth_user.email AS TEXT),
                auth_user.first_name,
                auth_user.last_name,
                auth_user.is_active,
                auth_user.created_at,
                auth_user.updated_at,
                auth_user_info.*
            FROM auth_user
        `
    usrTablesJoin = `
            JOIN auth_user_info
            ON auth_user.auth_user_id = auth_user_info.auth_user_id
    `
    userDbTable = "auth_user"
)

var usrTableStmt = fmt.Sprintf("%s %s", usrTableSel, usrTablesJoin)

var coreUserCols = []string{
    "first_name",
    "last_name",
    "email",
    "is_active",
}
var userInfoCols = []string{
    "auth_user_id",
    "organization",
    "group_name",
    "first_address",
    "second_address",
    "city",
    "state",
    "zipcode",
    "country",
    "phone",
}

type dbUser struct {
    AuthUserId     int64          `db:"auth_user_id"`
    FirstName      string         `db:"first_name"`
    LastName       string         `db:"last_name"`
    Email          string         `db:"email"`
    IsActive       bool           `db:"is_active"`
    Organization   dat.NullString `db:"organization"`
    GroupName      dat.NullString `db:"group_name"`
    FirstAddress   dat.NullString `db:"first_address"`
    SecondAddress  dat.NullString `db:"second_address"`
    City           dat.NullString `db:"city"`
    State          dat.NullString `db:"state"`
    Zipcode        dat.NullString `db:"zipcode"`
    Country        dat.NullString `db:"country"`
    Phone          dat.NullString `db:"phone"`
    CreatedAt      dat.NullTime   `db:"created_at"`
    UpdatedAt      dat.NullTime   `db:"updated_at"`
    AuthUserInfoId int64          `db:"auth_user_info_id"`
}

type dbCoreUser struct {
    AuthUserId int64        `db:"auth_user_id"`
    FirstName  string       `db:"first_name"`
    LastName   string       `db:"last_name"`
    Email      string       `db:"email"`
    IsActive   bool         `db:"is_active"`
    CreatedAt  dat.NullTime `db:"created_at"`
    UpdatedAt  dat.NullTime `db:"updated_at"`
}

type dbUserInfo struct {
    AuthUserId    int64          `db:"auth_user_id"`
    Organization  dat.NullString `db:"organization"`
    GroupName     dat.NullString `db:"group_name"`
    FirstAddress  dat.NullString `db:"first_address"`
    SecondAddress dat.NullString `db:"second_address"`
    City          dat.NullString `db:"city"`
    State         dat.NullString `db:"state"`
    Zipcode       dat.NullString `db:"zipcode"`
    Country       dat.NullString `db:"country"`
    Phone         dat.NullString `db:"phone"`
}

type UserService struct {
    *aphgrpc.Service
}

func userServiceOptions() *aphgrpc.ServiceOptions {
    return &aphgrpc.ServiceOptions{
        Resource:   "users",
        PathPrefix: "users",
        Include:    []string{"roles"},
        FilToColumns: map[string]string{
            "first_name": "auth_user.first_name",
            "last_name":  "auth_user.last_name",
            "email":      "auth_user.email",
        },
        FieldsToColumns: map[string]string{
            "first_name":     "auth_user.first_name",
            "last_name":      "auth_user.last_name",
            "email":          "auth_user.email",
            "created_at":     "auth_user.created_at",
            "updated_at":     "auth_user.updated_at",
            "organization":   "auth_user_info.organization",
            "group_name":     "auth_user_info.group_name",
            "first_address":  "auth_user_info.first_address",
            "second_address": "auth_user_info.second_address",
            "city":           "auth_user_info.city",
            "state":          "auth_user_info.state",
            "zipcode":        "auth_user_info.zipcode",
            "country":        "auth_user_info.country",
            "phone":          "auth_user_info.phone",
            "is_active":      "auth_user_info.is_active",
        },
        ReqAttrs: []string{"FirstName", "LastName", "Email"},
    }
}

func NewUserService(dbh *runner.DB, opt ...aphgrpc.Option) *UserService {
    so := userServiceOptions()
    for _, optfn := range opt {
        optfn(so)
    }
    srv := &aphgrpc.Service{Dbh: dbh}
    aphgrpc.AssignFieldsToStructs(so, srv)
    return &UserService{srv}
}

func (s *UserService) Healthz(ctx context.Context, r *jsonapi.HealthzIdRequest) (*empty.Empty, error) {
    return &empty.Empty{}, nil
}

func (s *UserService) ExistUser(ctx context.Context, r *jsonapi.IdRequest) (*jsonapi.ExistResponse, error) {
    found, err := s.existsResource(r.Id)
    return &jsonapi.ExistResponse{Exist: found}, err
}

func (s *UserService) GetUserByEmail(ctx context.Context, r *jsonapi.GetEmailRequest) (*user.User, error) {
    id, err := s.emailToResourceId(r.Email)
    if err != nil {
        return &user.User{}, aphgrpc.HandleError(ctx, err)
    }
    if id == 0 { // user with that email does not exist
        return &user.User{}, status.Error(codes.NotFound, fmt.Sprintf("email %s not found", r.Email))
    }
    return s.GetUser(
        ctx,
        &jsonapi.GetRequest{
            Id:      id,
            Include: r.Include,
            Fields:  r.Fields,
        })
}

func (s *UserService) GetUser(ctx context.Context, r *jsonapi.GetRequest) (*user.User, error) {
    params, md, err := aphgrpc.ValidateAndParseGetParams(s, r)
    if err != nil {
        grpc.SetTrailer(ctx, md)
        return new(user.User), status.Error(codes.InvalidArgument, err.Error())
    }
    gctx := aphgrpc.GetReqCtx(params, r)
    switch {
    case params.HasFields && params.HasInclude:
        u, err := s.getResourceWithSelectedAttr(gctx, r.Id)
        if err != nil {
            return &user.User{}, aphgrpc.HandleError(ctx, err)
        }
        err = s.buildResourceRelationships(r.Id, u)
        if err != nil {
            return &user.User{}, aphgrpc.HandleError(ctx, err)
        }
        return u, nil
    case params.HasFields:
        u, err := s.getResourceWithSelectedAttr(gctx, r.Id)
        if err != nil {
            return &user.User{}, aphgrpc.HandleError(ctx, err)
        }
        return u, nil
    case params.HasInclude:
        u, err := s.getResource(gctx, r.Id)
        if err != nil {
            return &user.User{}, aphgrpc.HandleError(ctx, err)
        }
        err = s.buildResourceRelationships(r.Id, u)
        if err != nil {
            return &user.User{}, aphgrpc.HandleError(ctx, err)
        }
        return u, nil
    default:
        u, err := s.getResource(gctx, r.Id)
        if err != nil {
            return &user.User{}, aphgrpc.HandleError(ctx, err)
        }
        return u, nil
    }
}

func (s *UserService) GetRelatedRoles(ctx context.Context, r *jsonapi.RelationshipRequest) (*user.RoleCollection, error) {
    rdata, err := s.getRoleResourceData(r.Id)
    if err != nil {
        return &user.RoleCollection{}, aphgrpc.HandleError(ctx, err)
    }
    return &user.RoleCollection{
        Data: rdata,
        Links: &jsonapi.Links{
            Self: s.GenCollResourceRelSelfLink(r.Id, "roles"),
        },
    }, nil
}

func (s *UserService) ListUsers(ctx context.Context, r *jsonapi.ListRequest) (*user.UserCollection, error) {
    params, md, err := aphgrpc.ValidateAndParseListParams(s, r)
    if err != nil {
        grpc.SetTrailer(ctx, md)
        return &user.UserCollection{}, status.Error(codes.InvalidArgument, err.Error())
    }
    lctx := aphgrpc.ListReqCtx(params, r)
    // has pagination query parameters
    if aphgrpc.HasPagination(r) {
        if r.Pagenum == 0 {
            r.Pagenum = aphgrpc.DefaultPagenum
        }
        if r.Pagesize == 0 {
            r.Pagesize = aphgrpc.DefaultPagesize
        }
        switch {
        // filter, fields and include parameters
        case params.HasFields && params.HasInclude && params.HasFilter:
            count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllSelectedFilteredRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize)
        // fields and includes
        case params.HasFields && params.HasInclude:
            count, err := s.GetCount(lctx, userDbTable)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllSelectedRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize)
        // fields and filters
        case params.HasFields && params.HasFilter:
            count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllSelectedFilteredRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize), nil
        // include and filter
        case params.HasInclude && params.HasFilter:
            count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllFilteredRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize)
        case params.HasFields:
            count, err := s.GetCount(lctx, userDbTable)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllSelectedRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize), nil
        case params.HasFilter:
            count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllFilteredRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize), nil
        case params.HasInclude:
            count, err := s.GetCount(lctx, userDbTable)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize)
        // only pagination
        default:
            count, err := s.GetCount(lctx, userDbTable)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            dbUsers, err := s.getAllRowsWithPaging(lctx, r.Pagenum, r.Pagesize)
            if err != nil {
                return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
            }
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, r.Pagenum, r.Pagesize), nil
        }
    }
    // request without any pagination query parameters
    switch {
    case params.HasFields && params.HasFilter && params.HasInclude:
        count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllSelectedFilteredRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    case params.HasFields && params.HasFilter:
        count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllSelectedFilteredRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize), nil
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    case params.HasFields && params.HasInclude:
        count, err := s.GetCount(lctx, "auth_user")
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllSelectedRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    case params.HasFilter && params.HasInclude:
        count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllFilteredRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    case params.HasFields:
        count, err := s.GetCount(lctx, userDbTable)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllSelectedRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize), nil
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    case params.HasFilter:
        count, err := s.GetAllFilteredCount(lctx, fmt.Sprintf("%s %s", userDbTable, usrTablesJoin))
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllFilteredRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize), nil
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    case params.HasInclude:
        count, err := s.GetCount(lctx, userDbTable)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithRelAndPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    default:
        count, err := s.GetCount(lctx, userDbTable)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        dbUsers, err := s.getAllRowsWithPaging(lctx, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize)
        if err != nil {
            return &user.UserCollection{}, aphgrpc.HandleError(ctx, err)
        }
        if count > aphgrpc.DefaultPagesize {
            return s.dbToCollResourceWithPagination(lctx, count, dbUsers, aphgrpc.DefaultPagenum, aphgrpc.DefaultPagesize), nil
        }
        return s.dbToCollResource(lctx, dbUsers), nil
    }
}

func (s *UserService) CreateUser(ctx context.Context, r *user.CreateUserRequest) (*user.User, error) {
    dbcuser := s.attrTodbCoreUser(r.Data.Attributes)
    retcols := []string{"auth_user_id", "created_at", "updated_at"}
    err := s.Dbh.InsertInto("auth_user").
        Columns(coreUserCols...).
        Record(dbcuser).
        Returning(retcols...).
        QueryStruct(dbcuser)
    if err != nil {
        grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseInsert)
        return &user.User{}, status.Error(codes.Internal, err.Error())
    }
    dbusrInfo := s.attrTodbUserInfo(r.Data.Attributes)
    dbusrInfo.AuthUserId = dbcuser.AuthUserId
    defUsrInfoCols := aphgrpc.GetDefinedTags(dbusrInfo, "db")
    if len(defUsrInfoCols) > 0 {
        err = s.Dbh.InsertInto("auth_user_info").
            Columns(defUsrInfoCols...).
            Record(dbusrInfo).
            Returning(userInfoCols...).
            QueryStruct(dbusrInfo)
        if err != nil {
            grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseInsert)
            return &user.User{}, status.Error(codes.Internal, err.Error())
        }
    }
    rstruct := structs.New(r).Field("Data").Field("Relationships")
    if !rstruct.IsZero() {
        if !rstruct.Field("Roles").IsZero() {
            for _, role := range r.Data.Relationships.Roles.Data {
                _, err = s.Dbh.InsertInto("auth_user_role").
                    Columns("auth_user_id", "auth_role_id").
                    Values(dbcuser.AuthUserId, role.Id).Exec()
                if err != nil {
                    grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseInsert)
                    return &user.User{}, status.Error(codes.Internal, err.Error())
                }
            }
        }
    }
    grpc.SetTrailer(ctx, metadata.Pairs("method", "POST"))
    return s.buildResource(
        context.TODO(),
        dbcuser.AuthUserId,
        s.dbToResourceAttributes(s.mergeTodbUser(dbcuser, dbusrInfo)),
    ), nil
}

func (s *UserService) CreateRoleRelationship(ctx context.Context, r *jsonapi.DataCollection) (*empty.Empty, error) {
    if len(r.Data) == 0 {
        return &empty.Empty{}, aphgrpc.HandleInsertArgError(ctx, fmt.Errorf("no id given"))
    }
    result, err := s.existsResource(r.Id)
    if err != nil {
        return &empty.Empty{}, aphgrpc.HandleError(ctx, err)
    }
    if !result {
        grpc.SetTrailer(ctx, aphgrpc.ErrNotFound)
        return &empty.Empty{}, status.Error(codes.NotFound, fmt.Sprintf("id %d not found", r.Id))
    }
    for _, rd := range r.Data {
        res, err := s.Dbh.Select("aurole.auth_user_role_id").
            From("auth_user_role aurole").
            Where("aurole.auth_user_id = $1 AND aurole.auth_role_id = $2", r.Id, rd.Id).
            Exec()
        if err != nil {
            grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseInsert)
            return &empty.Empty{}, status.Error(codes.Internal, err.Error())
        }
        if res.RowsAffected != 1 {
            _, err := s.Dbh.InsertInto("auth_user_role").
                Columns("auth_user_id", "auth_role_id").
                Values(r.Id, rd.Id).Exec()
            if err != nil {
                grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseInsert)
                return &empty.Empty{}, status.Error(codes.Internal, err.Error())
            }
        } else {
            return &empty.Empty{},
                aphgrpc.HandleExistError(
                    ctx,
                    fmt.Errorf("relationship with given id %d already exists", rd.Id),
                )
        }

    }
    grpc.SetTrailer(ctx, metadata.Pairs("method", "POST_NO_CONTENT"))
    return &empty.Empty{}, nil
}

func (s *UserService) UpdateUser(ctx context.Context, r *user.UpdateUserRequest) (*user.User, error) {
    result, err := s.existsResource(r.Id)
    if err != nil {
        return &user.User{}, aphgrpc.HandleError(ctx, err)
    }
    if !result {
        grpc.SetTrailer(ctx, aphgrpc.ErrNotFound)
        return &user.User{}, status.Error(codes.NotFound, fmt.Sprintf("id %d not found", r.Id))
    }
    dbcuser := s.attrTodbCoreUser(r.Data.Attributes)
    usrMap := aphgrpc.GetDefinedTagsWithValue(dbcuser, "db")
    if len(usrMap) > 0 {
        err := s.Dbh.Update("auth_user").
            SetMap(usrMap).
            Where("auth_user_id = $1", r.Data.Id).
            Returning([]string{"created_at", "updated_at"}...).
            QueryStruct(dbcuser)
        if err != nil {
            grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseUpdate)
            return &user.User{}, status.Error(codes.Internal, err.Error())
        }
    }
    dbusrInfo := s.attrTodbUserInfo(r.Data.Attributes)
    usrInfoMap := aphgrpc.GetDefinedTagsWithValue(dbusrInfo, "db")
    if len(usrInfoMap) > 0 {
        err := s.Dbh.Update("auth_user_info").
            SetMap(usrInfoMap).
            Where("auth_user_id = $1", r.Data.Id).
            Returning(userInfoCols...).
            QueryStruct(dbusrInfo)
        if err != nil {
            grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseUpdate)
            return &user.User{}, status.Error(codes.Internal, err.Error())
        }
    }
    rstruct := structs.New(r).Field("Data").Field("Relationships")
    if !rstruct.IsZero() {
        if !rstruct.Field("Roles").IsZero() {
            for _, role := range r.Data.Relationships.Roles.Data {
                _, err := s.Dbh.Update("auth_user_role").
                    Set("auth_role_id", role.Id).
                    Where("auth_user_id = $1", r.Data.Id).Exec()
                if err != nil {
                    grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseUpdate)
                    return &user.User{}, status.Error(codes.Internal, err.Error())
                }
            }
        }
    }
    return s.buildResource(
        context.TODO(),
        r.Data.Id,
        s.dbToResourceAttributes(s.mergeTodbUser(dbcuser, dbusrInfo)),
    ), nil
}

func (s *UserService) UpdateRoleRelationship(ctx context.Context, r *jsonapi.DataCollection) (*empty.Empty, error) {
    if len(r.Data) == 0 {
        return &empty.Empty{}, aphgrpc.HandleInsertArgError(ctx, fmt.Errorf("no id given"))
    }
    result, err := s.existsResource(r.Id)
    if err != nil {
        return &empty.Empty{}, aphgrpc.HandleError(ctx, err)
    }
    if !result {
        grpc.SetTrailer(ctx, aphgrpc.ErrNotFound)
        return &empty.Empty{}, status.Error(codes.NotFound, fmt.Sprintf("id %d not found", r.Id))
    }
    _, err = s.Dbh.DeleteFrom("auth_user_role").
        Where("auth_user_role.auth_user_id = $1", r.Id).
        Exec()
    if err != nil {
        grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseUpdate)
        return &empty.Empty{}, status.Error(codes.Internal, err.Error())
    }
    for _, rd := range r.Data {
        _, err := s.Dbh.InsertInto("auth_user_role").
            Columns("auth_user_id", "auth_role_id").
            Values(r.Id, rd.Id).Exec()
        if err != nil {
            grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseUpdate)
            return &empty.Empty{}, status.Error(codes.Internal, err.Error())
        }
    }
    return &empty.Empty{}, nil
}

func (s *UserService) DeleteUser(ctx context.Context, r *jsonapi.DeleteRequest) (*empty.Empty, error) {
    result, err := s.existsResource(r.Id)
    if err != nil {
        return &empty.Empty{}, aphgrpc.HandleError(ctx, err)
    }
    if !result {
        grpc.SetTrailer(ctx, aphgrpc.ErrNotFound)
        return &empty.Empty{}, status.Error(codes.NotFound, fmt.Sprintf("id %d not found", r.Id))
    }
    _, err = s.Dbh.DeleteFrom("auth_user").Where("auth_user_id = $1", r.Id).Exec()
    if err != nil {
        grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseDelete)
        return &empty.Empty{}, status.Error(codes.Internal, err.Error())
    }
    return &empty.Empty{}, nil
}

func (s *UserService) DeleteRoleRelationship(ctx context.Context, r *jsonapi.DataCollection) (*empty.Empty, error) {
    if len(r.Data) == 0 {
        return &empty.Empty{}, aphgrpc.HandleInsertArgError(ctx, fmt.Errorf("no id given"))
    }
    result, err := s.existsResource(r.Id)
    if err != nil {
        return &empty.Empty{}, aphgrpc.HandleError(ctx, err)
    }
    if !result {
        grpc.SetTrailer(ctx, aphgrpc.ErrNotFound)
        return &empty.Empty{}, status.Error(codes.NotFound, fmt.Sprintf("id %d not found", r.Id))
    }
    for _, rd := range r.Data {
        _, err := s.Dbh.DeleteFrom("auth_user_role").
            Where("auth_user_role.auth_user_id = $1 AND auth_user_role.auth_role_id = $2", r.Id, rd.Id).
            Exec()
        if err != nil {
            grpc.SetTrailer(ctx, aphgrpc.ErrDatabaseDelete)
            return &empty.Empty{}, status.Error(codes.Internal, err.Error())
        }
    }
    return &empty.Empty{}, nil
}

// All helper functions

func (s *UserService) emailToResourceId(email string) (int64, error) {
    var id int64
    err := s.Dbh.Select("auth_user_id").From(userDbTable).
        Where("email = $1", email).QueryScalar(&id)
    return id, err
}

func (s *UserService) existsResource(id int64) (bool, error) {
    r, err := s.Dbh.Select("auth_user_id").From("auth_user").
        Where("auth_user_id = $1", id).Exec()
    if err != nil {
        return false, err
    }
    if r.RowsAffected != 1 {
        return false, nil
    }
    return true, nil
}

// -- Functions that queries the storage and generates an user resource object

func (s *UserService) getResourceWithSelectedAttr(ctx context.Context, id int64) (*user.User, error) {
    params, ok := ctx.Value(aphgrpc.ContextKeyParams).(*aphgrpc.JSONAPIParams)
    if !ok {
        return &user.User{}, fmt.Errorf("no params object found in context")
    }
    dusr := new(dbUser)
    err := s.Dbh.SQL(
        fmt.Sprintf(
            "SELECT %s FROM auth_user %s %s",
            strings.Join(s.mapFieldsToColumnsWithCast(params.Fields), ","),
            usrTablesJoin,
            "WHERE auth_user.auth_user_id = $1",
        ), id).QueryStruct(dusr)
    if err != nil {
        return &user.User{}, err
    }
    return s.buildResource(ctx, id, s.dbToResourceAttributes(dusr)), nil
}

func (s *UserService) getResource(ctx context.Context, id int64) (*user.User, error) {
    dusr := new(dbUser)
    err := s.Dbh.SQL(
        fmt.Sprintf(
            "%s %s",
            usrTableStmt,
            "WHERE auth_user.auth_user_id = $1",
        ), id).QueryStruct(dusr)
    if err != nil {
        return &user.User{}, err
    }
    return s.buildResource(ctx, id, s.dbToResourceAttributes(dusr)), nil
}

// -- Functions that queries the storage and generates a database user object

func (s *UserService) getAllRows(ctx context.Context) ([]*dbUser, error) {
    var dusrRows []*dbUser
    err := s.Dbh.SQL(usrTableStmt).QueryStructs(&dusrRows)
    return dusrRows, err
}

func (s *UserService) getAllRowsWithPaging(ctx context.Context, pagenum int64, pagesize int64) ([]*dbUser, error) {
    var dusrRows []*dbUser
    err := s.Dbh.SQL(
        fmt.Sprintf(
            "%s LIMIT %d OFFSET %d",
            usrTableStmt,
            pagesize,
            (pagenum-1)*pagesize,
        )).
        QueryStructs(&dusrRows)
    return dusrRows, err
}

func (s *UserService) getAllSelectedRowsWithPaging(ctx context.Context, pagenum, pagesize int64) ([]*dbUser, error) {
    var dusrRows []*dbUser
    params, ok := ctx.Value(aphgrpc.ContextKeyParams).(*aphgrpc.JSONAPIParams)
    if !ok {
        return dusrRows, fmt.Errorf("no params object found in context")
    }
    err := s.Dbh.SQL(
        fmt.Sprintf(
            "SELECT %s FROM auth_user %s LIMIT %d OFFSET %d",
            strings.Join(s.mapFieldsToColumnsWithCast(params.Fields), ","),
            usrTablesJoin,
            pagesize,
            (pagenum-1)*pagesize,
        )).
        QueryStructs(&dusrRows)
    return dusrRows, err
}

func (s *UserService) getAllFilteredRowsWithPaging(ctx context.Context, pagenum, pagesize int64) ([]*dbUser, error) {
    var dusrRows []*dbUser
    params, ok := ctx.Value(aphgrpc.ContextKeyParams).(*aphgrpc.JSONAPIParams)
    if !ok {
        return dusrRows, fmt.Errorf("no params object found in context")
    }
    var bindVals []string
    for _, v := range aphgrpc.FilterToBindValue(params.Filters) {
        bindVals = append(bindVals, v.(string))
    }
    err := s.Dbh.SQL(
        fmt.Sprintf(
            "%s %s LIMIT %d OFFSET %d",
            usrTableStmt,
            aphgrpc.FilterToWhereClause(s, params.Filters),
            pagesize,
            (pagenum-1)*pagesize,
        ), strings.Join(bindVals, ","),
    ).QueryStructs(&dusrRows)
    return dusrRows, err
}

func (s *UserService) getAllSelectedFilteredRowsWithPaging(ctx context.Context, pagenum, pagesize int64) ([]*dbUser, error) {
    var dusrRows []*dbUser
    params, ok := ctx.Value(aphgrpc.ContextKeyParams).(*aphgrpc.JSONAPIParams)
    if !ok {
        return dusrRows, fmt.Errorf("no params object found in context")
    }
    var bindVals []string
    for _, v := range aphgrpc.FilterToBindValue(params.Filters) {
        bindVals = append(bindVals, v.(string))
    }
    err := s.Dbh.SQL(
        fmt.Sprintf(
            "SELECT %s FROM auth_user %s %s LIMIT %d OFFSET %d",
            strings.Join(s.mapFieldsToColumnsWithCast(params.Fields), ","),
            usrTablesJoin,
            aphgrpc.FilterToWhereClause(s, params.Filters),
            pagesize,
            (pagenum-1)*pagesize,
        ), strings.Join(bindVals, ","),
    ).QueryStructs(dusrRows)
    return dusrRows, err
}

// -- Functions that returns relationship resource objects

func (s *UserService) getRoleResourceData(id int64) ([]*user.RoleData, error) {
    var drole []*dbRole
    var rdata []*user.RoleData
    err := s.Dbh.Select("role.*").From(`
            auth_user_role
            JOIN auth_role role
            ON auth_user_role.auth_role_id = role.auth_role_id
        `).Where("auth_user_role.auth_user_id = $1", id).QueryStructs(&drole)
    if err != nil {
        return rdata, err
    }
    return NewRoleService(s.Dbh).dbToCollResourceData(context.TODO(), drole), nil
}

func (s *UserService) buildRoleResourceIdentifiers(roles []*user.RoleData) []*jsonapi.Data {
    jdata := make([]*jsonapi.Data, len(roles))
    for i, r := range roles {
        jdata[i] = &jsonapi.Data{
            Type: r.Type,
            Id:   r.Id,
        }
    }
    return jdata
}

// -- Functions that builds up the various parts of the final user resource objects

func (s *UserService) buildResourceData(ctx context.Context, id int64, uattr *user.UserAttributes) *user.UserData {
    return &user.UserData{
        Type:       s.GetResourceName(),
        Id:         id,
        Attributes: uattr,
        Relationships: &user.ExistingUserRelationships{
            Roles: &user.ExistingUserRelationships_Roles{
                Links: &jsonapi.Links{
                    Self:    aphgrpc.GenSelfRelationshipLink(s, "roles", id),
                    Related: aphgrpc.GenRelatedRelationshipLink(s, "roles", id),
                },
            },
        },
        Links: &jsonapi.Links{
            Self: s.GenResourceSelfLink(ctx, id),
        },
    }
}

func (s *UserService) buildResource(ctx context.Context, id int64, uattr *user.UserAttributes) *user.User {
    return &user.User{
        Data: s.buildResourceData(ctx, id, uattr),
        Links: &jsonapi.Links{
            Self: s.GenResourceSelfLink(ctx, id),
        },
    }
}

func (s *UserService) buildResourceRelationships(id int64, user *user.User) error {
    var allInc []*any.Any
    roles, err := s.getRoleResourceData(id)
    if err != nil {
        return err
    }
    incRoles, err := NewRoleService(s.Dbh).convertAllToAny(roles)
    if err != nil {
        return err
    }
    allInc = append(allInc, incRoles...)
    user.Data.Relationships.Roles.Data = s.buildRoleResourceIdentifiers(roles)
    user.Included = allInc
    return nil
}

// -- Functions that generates various user resource objects from
//    database user object.

func (s *UserService) dbToResourceAttributes(dusr *dbUser) *user.UserAttributes {
    return &user.UserAttributes{
        FirstName:     dusr.FirstName,
        LastName:      dusr.LastName,
        Email:         dusr.Email,
        IsActive:      dusr.IsActive,
        Organization:  aphgrpc.NullToString(dusr.Organization),
        GroupName:     aphgrpc.NullToString(dusr.GroupName),
        FirstAddress:  aphgrpc.NullToString(dusr.FirstAddress),
        SecondAddress: aphgrpc.NullToString(dusr.SecondAddress),
        City:          aphgrpc.NullToString(dusr.City),
        State:         aphgrpc.NullToString(dusr.State),
        Zipcode:       aphgrpc.NullToString(dusr.Zipcode),
        Country:       aphgrpc.NullToString(dusr.Country),
        Phone:         aphgrpc.NullToString(dusr.Phone),
        CreatedAt:     aphgrpc.NullToTime(dusr.CreatedAt),
        UpdatedAt:     aphgrpc.NullToTime(dusr.UpdatedAt),
    }
}

func (s *UserService) dbToCollResourceData(ctx context.Context, dbUsers []*dbUser) []*user.UserData {
    var udata []*user.UserData
    for _, dusr := range dbUsers {
        udata = append(udata, s.buildResourceData(ctx, dusr.AuthUserId, s.dbToResourceAttributes(dusr)))
    }
    return udata

}

func (s *UserService) dbToCollResource(ctx context.Context, dbUsers []*dbUser) *user.UserCollection {
    return &user.UserCollection{
        Data: s.dbToCollResourceData(ctx, dbUsers),
        Links: &jsonapi.PaginationLinks{
            Self: s.GenCollResourceSelfLink(ctx),
        },
    }
}

func (s *UserService) dbToCollResourceWithPagination(ctx context.Context, count int64, dbUsers []*dbUser, pagenum, pagesize int64) *user.UserCollection {
    udata := s.dbToCollResourceData(ctx, dbUsers)
    jsLinks, pages := s.GetPagination(ctx, count, pagenum, pagesize)
    return &user.UserCollection{
        Data:  udata,
        Links: jsLinks,
        Meta: &jsonapi.Meta{
            Pagination: &jsonapi.Pagination{
                Records: count,
                Total:   pages,
                Size:    pagesize,
                Number:  pagenum,
            },
        },
    }
}

func (s *UserService) dbToCollResourceWithRelAndPagination(ctx context.Context, count int64, dbUsers []*dbUser, pagenum, pagesize int64) (*user.UserCollection, error) {
    udata := s.dbToCollResourceData(ctx, dbUsers)
    var allRoles []*user.RoleData
    for i, _ := range udata {
        roles, err := s.getRoleResourceData(dbUsers[i].AuthUserId)
        if err != nil {
            return &user.UserCollection{}, err
        }
        udata[i].Relationships.Roles.Data = s.buildRoleResourceIdentifiers(roles)
        allRoles = append(allRoles, roles...)
    }
    incRoles, err := NewRoleService(s.Dbh).convertAllToAny(allRoles)
    if err != nil {
        return &user.UserCollection{}, err
    }
    jsLinks, pages := s.GetPagination(ctx, count, pagenum, pagesize)
    return &user.UserCollection{
        Data:     udata,
        Links:    jsLinks,
        Included: incRoles,
        Meta: &jsonapi.Meta{
            Pagination: &jsonapi.Pagination{
                Records: count,
                Total:   pages,
                Size:    pagesize,
                Number:  pagenum,
            },
        },
    }, nil
}

// -- Various utility functions

func (s *UserService) mergeTodbUser(dbcuser *dbCoreUser, dbusrInfo *dbUserInfo) *dbUser {
    return &dbUser{
        AuthUserId:    dbcuser.AuthUserId,
        FirstName:     dbcuser.FirstName,
        LastName:      dbcuser.LastName,
        Email:         dbcuser.Email,
        IsActive:      dbcuser.IsActive,
        CreatedAt:     dbcuser.CreatedAt,
        UpdatedAt:     dbcuser.UpdatedAt,
        Organization:  dbusrInfo.Organization,
        GroupName:     dbusrInfo.GroupName,
        FirstAddress:  dbusrInfo.FirstAddress,
        SecondAddress: dbusrInfo.SecondAddress,
        City:          dbusrInfo.City,
        State:         dbusrInfo.State,
        Zipcode:       dbusrInfo.Zipcode,
        Country:       dbusrInfo.Country,
        Phone:         dbusrInfo.Phone,
    }
}

func (s *UserService) attrTodbCoreUser(attr *user.UserAttributes) *dbCoreUser {
    return &dbCoreUser{
        FirstName: attr.FirstName,
        LastName:  attr.LastName,
        Email:     attr.Email,
        IsActive:  attr.IsActive,
    }
}

func (s *UserService) attrTodbUserInfo(attr *user.UserAttributes) *dbUserInfo {
    return &dbUserInfo{
        Organization:  dat.NullStringFrom(attr.Organization),
        GroupName:     dat.NullStringFrom(attr.GroupName),
        FirstAddress:  dat.NullStringFrom(attr.FirstAddress),
        SecondAddress: dat.NullStringFrom(attr.SecondAddress),
        City:          dat.NullStringFrom(attr.City),
        State:         dat.NullStringFrom(attr.State),
        Zipcode:       dat.NullStringFrom(attr.Zipcode),
        Country:       dat.NullStringFrom(attr.Country),
        Phone:         dat.NullStringFrom(attr.Phone),
    }
}

func (s *UserService) convertAllToAny(users []*user.UserData) ([]*any.Any, error) {
    aslice := make([]*any.Any, len(users))
    for i, u := range users {
        pkg, err := ptypes.MarshalAny(u)
        if err != nil {
            return aslice, err
        }
        aslice[i] = pkg
    }
    return aslice, nil
}

func (s *UserService) mapFieldsToColumnsWithCast(fields []string) []string {
    var columns []string
    for _, c := range s.MapFieldsToColumns(fields) {
        if c == "auth_user.email" {
            columns = append(columns, fmt.Sprintf("CAST(%s AS TEXT)", c))
        } else {
            columns = append(columns, c)
        }
    }
    return columns
}