You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: projectmembers.go
    path of file: ./repos/storj/satellite/satellitedb
    
    the code of the file until where you have to start completion: // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package satellitedb

import (
	"context"
	"strings"
	"time"

	"github.com/zeebo/errs"

	"storj.io/common/uuid"
	"storj.io/storj/satellite/console"
	"storj.io/storj/satellite/satellitedb/dbx"
)

// ensures that projectMembers implements console.ProjectMembers.
var _ console.ProjectMembers = (*projectMembers)(nil)

// ProjectMembers exposes methods to manage ProjectMembers table in database.
type projectMembers struct {
	methods dbx.Methods
	db      *satelliteDB
}

// GetByMemberID is a method for querying project member from the database by memberID.
func (pm *projectMembers) GetByMemberID(ctx context.Context, memberID uuid.UUID) (_ []console.ProjectMember, err error) {
	defer mon.Task()(&ctx)(&err)
	projectMembersDbx, err := pm.methods.All_ProjectMember_By_MemberId(ctx, dbx.ProjectMember_MemberId(memberID[:]))
	if err != nil {
		return nil, err
	}

	return projectMembersFromDbxSlice(ctx, projectMembersDbx)
}

// GetPagedWithInvitationsByProjectID is a method for querying project members and invitations from the database by projectID, offset and limit.
func (pm *projectMembers) GetPagedWithInvitationsByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.ProjectMembersCursor) (_ *console.ProjectMembersPage, err error) {
	defer mon.Task()(&ctx)(&err)

	search := "%" + strings.ReplaceAll(cursor.Search, " ", "%") + "%"

	if cursor.Limit > 50 {
		cursor.Limit = 50
	}

	if cursor.Limit == 0 {
		return nil, errs.New("limit cannot be 0")
	}

	if cursor.Page == 0 {
		return nil, errs.New("page cannot be 0")
	}

	page := &console.ProjectMembersPage{
		Search:         cursor.Search,
		Limit:          cursor.Limit,
		Offset:         uint64((cursor.Page - 1) * cursor.Limit),
		Order:          cursor.Order,
		OrderDirection: cursor.OrderDirection,
	}

	countQuery := `
		SELECT (
			SELECT COUNT(*)
			FROM project_members pm
			INNER JOIN users u ON pm.member_id = u.id
			WHERE pm.project_id = $1
			AND (
				u.email ILIKE $2 OR
				u.full_name ILIKE $2 OR
				u.short_name ILIKE $2
			)
		) + (
			SELECT COUNT(*)
			FROM project_invitations
			WHERE project_id = $1
			AND email ILIKE $2
		)`

	countRow := pm.db.QueryRowContext(ctx,
		countQuery,
		projectID[:],
		search)

	err = countRow.Scan(&page.Tot
    
func (pm *projectMembers) GetPagedWithInvitationsByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.ProjectMembersCursor) (_ *console.ProjectMembersPage, err error) {
	defer mon.Task()(&ctx)(&err)

	search := "%" + strings.ReplaceAll(cursor.Search, " ", "%") + "%"

	if cursor.Limit > 50 {
		cursor.Limit = 50
	}

	if cursor.Limit == 0 {
		return nil, errs.New("limit cannot be 0")
	}

	if cursor.Page == 0 {
		return nil, errs.New("page cannot be 0")
	}

	page := &console.ProjectMembersPage{
		Search:         cursor.Search,
		Limit:          cursor.Limit,
		Offset:         uint64((cursor.Page - 1) * cursor.Limit),
		Order:          cursor.Order,
		OrderDirection: cursor.OrderDirection,
	}

	countQuery := `
		SELECT (
			SELECT COUNT(*)
			FROM project_members pm
			INNER JOIN users u ON pm.member_id = u.id
			WHERE pm.project_id = $1
			AND (
				u.email ILIKE $2 OR
				u.full_name ILIKE $2 OR
				u.short_name ILIKE $2
			)
		) + (
			SELECT COUNT(*)
			FROM project_invitations
			WHERE project_id = $1
			AND email ILIKE $2
		)`

	countRow := pm.db.QueryRowContext(ctx,
		countQuery,
		projectID[:],
		search)

	err = countRow.Scan(&page.TotalCount)
	if err != nil {
		return nil, err
	}
	if page.TotalCount == 0 {
		return page, nil
	}
	if page.Offset > page.TotalCount-1 {
		return nil, errs.New("page is out of range")
	}

	membersQuery := `
		SELECT member_id, project_id, created_at, email, inviter_id FROM (
			(
				SELECT pm.member_id, pm.project_id, pm.created_at, u.email, u.full_name, NULL as inviter_id
				FROM project_members pm
				INNER JOIN users u ON pm.member_id = u.id
				WHERE pm.project_id = $1
				AND (
					u.email ILIKE $2 OR
					u.full_name ILIKE $2 OR
					u.short_name ILIKE $2
				)
			) UNION ALL (
				SELECT NULL as member_id, project_id, created_at, LOWER(email) as email, LOWER(SPLIT_PART(email, '@', 1)) as full_name, inviter_id
				FROM project_invitations pi
				WHERE project_id = $1
				AND email ILIKE $2
			)
		) results
		` + projectMembersSortClause(cursor.Order, page.OrderDirection) + `
		LIMIT $3 OFFSET $4`

	rows, err := pm.db.QueryContext(ctx,
		membersQuery,
		projectID[:],
		search,
		page.Limit,
		page.Offset,
	)
	if err != nil {
		return nil, err
	}
	defer func() {
		err = errs.Combine(err, rows.Close())
	}()

	for rows.Next() {
		var (
			memberID  uuid.NullUUID
			projectID uuid.UUID
			createdAt time.Time
			email     string
			inviterID uuid.NullUUID
		)

		err = rows.Scan(
			&memberID,
			&projectID,
			&createdAt,
			&email,
			&inviterID,
		)
		if err != nil {
			return nil, err
		}

		if memberID.Valid {
			page.ProjectMembers = append(page.ProjectMembers, console.ProjectMember{
				MemberID:  memberID.UUID,
				ProjectID: projectID,
				CreatedAt: createdAt,
			})
		} else {
			invite := console.ProjectInvitation{
				ProjectID: projectID,
				Email:     email,
				CreatedAt: createdAt,
			}
			if inviterID.Valid {
				invite.InviterID = &inviterID.UUID
			}
			page.ProjectInvitations = append(page.ProjectInvitations, invite)
		}

	}
	if err := rows.Err(); err != nil {
		return nil, err
	}

	page.PageCount = uint(page.TotalCount / uint64(cursor.Limit))
	if page.TotalCount%uint64(cursor.Limit) != 0 {
		page.PageCount++
	}

	page.CurrentPage = cursor.Page

	return page, err
}