Skip to content

Commit

Permalink
Merge pull request #135 from CPSSD/n/readfollows
Browse files Browse the repository at this point in the history
Add reading of follows to follows microservice
  • Loading branch information
iandioch authored Oct 29, 2018
2 parents 6e15bab + d497d74 commit 1b2d2ca
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 11 deletions.
15 changes: 15 additions & 0 deletions services/database/database.proto
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,23 @@ message Follow {
message DbFollowRequest {
enum RequestType {
INSERT = 0;
FIND = 1;
}

RequestType request_type = 1;

Follow entry = 2;

/* If request_type is FIND:
* - If match.followed is set, then the service will return all followers
* for this user's ID.
* - If match.follower is set, the service will return all users this
* person follows.
* - If both match.followed and match.follower are set, the service will
* return an entry if this follow exists, and none otherwise.
* - If neither are set, all follows in the database will be returned.
*/
Follow match = 3;
}

message DbFollowResponse {
Expand All @@ -104,6 +117,8 @@ message DbFollowResponse {

ResultType result_type = 1;
string error = 2;

repeated Follow results = 3;
}

service Database {
Expand Down
45 changes: 45 additions & 0 deletions services/database/follow_servicer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import sqlite3

import util

import database_pb2


Expand All @@ -10,8 +12,26 @@ def __init__(self, db, logger):
self._logger = logger
self._follow_type_handlers = {
database_pb2.DbFollowRequest.INSERT: self._follow_handle_insert,
database_pb2.DbFollowRequest.FIND: self._follow_handle_find,
}

def _db_tuple_to_entry(self, tup, entry):
if len(tup) != 2:
self._logger.warning(
"Error converting tuple to Follow: " +
"Wrong number of elements " + str(tup))
return False
try:
# You'd think there'd be a better way.
entry.follower = tup[0]
entry.followed = tup[1]
except Exception as e:
self._logger.warning(
"Error converting tuple to Follow: " +
str(e))
return False
return True

def Follow(self, request, context):
response = database_pb2.DbFollowResponse()
self._follow_type_handlers[request.request_type](request, response)
Expand All @@ -32,3 +52,28 @@ def _follow_handle_insert(self, req, resp):
resp.error = str(e)
return
resp.result_type = database_pb2.DbFollowResponse.OK

def _follow_handle_find(self, req, resp):
filter_clause, values = util.entry_to_filter(req.match)
try:
if not filter_clause:
query = 'SELECT * FROM follows'
self._logger.debug('Running query "%s"', query)
res = self._db.execute(query)
else:
query = 'SELECT * FROM follows WHERE ' + filter_clause
valstr = ', '.join(str(v) for v in values)
self._logger.debug('Running query "%s" with values (%s)',
query, valstr)
res = self._db.execute(query, *values)
except sqlite3.Error as e:
self._logger.warning('Got error reading DB: ' + str(e))
resp.result_type = database_pb2.DbFollowResponse.ERROR
resp.error = str(e)
return
resp.result_type = database_pb2.DbFollowResponse.OK
for tup in res:
if not self._db_tuple_to_entry(tup, resp.results.add()):
del resp.results[-1]

self._logger.debug('%d results of follower query.', len(resp.results))
29 changes: 29 additions & 0 deletions services/follows/follows.proto
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,36 @@ message FollowResponse {
string error = 2;
}

message FollowUser {
string handle = 1;
string host = 2;
string display_name = 3;
}

message GetFollowsRequest {
/* Eg. "admin", "[email protected]", "@jose" */
string username = 1;
}

message GetFollowsResponse {
enum ResultType {
OK = 0;
ERROR = 1;
// More types can be added here.
}

ResultType result_type = 1;

/* Should only be set if result_type is not OK. */
string error = 2;

/* Should only be set if result_type is OK. */
repeated FollowUser results = 3;
}

service Follows {
rpc SendFollowRequest(LocalToAnyFollow) returns (FollowResponse);
rpc ReceiveFollowRequest(ForeignToLocalFollow) returns (FollowResponse);
rpc GetFollowers(GetFollowsRequest) returns (GetFollowsResponse);
rpc GetFollowing(GetFollowsRequest) returns (GetFollowsResponse);
}
76 changes: 76 additions & 0 deletions services/follows/get_followers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from enum import Enum

import database_pb2
import follows_pb2


class GetFollowsReceiver:

def __init__(self, logger, util, database_stub):
self._logger = logger
self._util = util
self._database_stub = database_stub
self.RequestType = Enum('RequestType', 'FOLLOWING FOLLOWERS')

def _get_follows(self, request, context, request_type):
if request_type == self.RequestType.FOLLOWERS:
self._logger.debug('List of followers of %s requested',
request.username)
else:
self._logger.debug('List of users %s is following requested',
request.username)
resp = follows_pb2.GetFollowsResponse()

# Parse input username
handle, host = self._util.parse_username(
request.username)
if handle is None and host is None:
resp.result_type = follows_pb2.GetFollowsResponse.ERROR
resp.error = 'Could not parse queried username'
return resp

# Get user obj associated with given user handle & host from database
user_entry = self._util.get_user_from_db(handle, host)
if user_entry is None:
error = 'Could not find or create user {}@{}'.format(from_handle,
from_instance)
self._logger.error(error)
resp.result_type = follows_pb2.GetFollowersResponse.ERROR
resp.error = error
return resp
user_id = user_entry.global_id

# Get followers/followings for this user.
following_ids = None
if request_type == self.RequestType.FOLLOWERS:
following_ids = self._util.get_follows(followed_id=user_id).results
else:
following_ids = self._util.get_follows(follower_id=user_id).results

# Convert other following users and add to output proto.
for following_id in following_ids:
_id = following_id.followed
if request_type == self.RequestType.FOLLOWERS:
_id = following_id.follower
user = self._util.get_user_from_db(global_id=_id)
if user is None:
self._logger.warning('Could not find user for id %d',
_id)
continue

ok = self._util.convert_db_user_to_follow_user(user,
resp.results.add())
if not ok:
self._logger.warning('Could not convert user %s@%s to ' +
'FollowUser', user.handle, user.host)

resp.result_type = follows_pb2.GetFollowsResponse.OK
return resp

def GetFollowing(self, request, context):
self._logger.debug('GetFollowing, username = %s', request.username)
return self._get_follows(request, context, self.RequestType.FOLLOWING)

def GetFollowers(self, request, context):
self._logger.debug('GetFollowers, username = %s', request.username)
return self._get_follows(request, context, self.RequestType.FOLLOWERS)
5 changes: 3 additions & 2 deletions services/follows/send_follow.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ def SendFollowRequest(self, request, context):

# Get user IDs for follow.
follower_entry = self._util.get_user_from_db(
from_handle, from_instance)
handle=from_handle, host=from_instance)
if follower_entry is None:
error = 'Could not find or create user {}@{}'.format(from_handle,
from_instance)
self._logger.error(error)
resp.result_type = follows_pb2.FollowResponse.ERROR
resp.error = error
return resp
followed_entry = self._util.get_user_from_db(to_handle, to_instance)
followed_entry = self._util.get_user_from_db(handle=to_handle,
host=to_instance)
if followed_entry is None:
error = 'Could not find or create user {}@{}'.format(to_handle,
to_instance)
Expand Down
5 changes: 5 additions & 0 deletions services/follows/servicer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from get_followers import GetFollowsReceiver
from receive_follow import ReceiveFollowServicer
from send_follow import SendFollowServicer

Expand All @@ -14,3 +15,7 @@ def __init__(self, logger, util, database_stub):
self.SendFollowRequest = send_servicer.SendFollowRequest
rec_servicer = ReceiveFollowServicer(logger, util, database_stub)
self.ReceiveFollowRequest = rec_servicer.ReceiveFollowRequest

get_follows_receiver = GetFollowsReceiver(logger, util, database_stub)
self.GetFollowers = get_follows_receiver.GetFollowers
self.GetFollowing = get_follows_receiver.GetFollowing
63 changes: 54 additions & 9 deletions services/follows/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import follows_pb2
import database_pb2

MAX_FIND_RETRIES = 3
Expand Down Expand Up @@ -33,30 +34,43 @@ def _create_user_in_db(self, entry):
# TODO(iandioch): Respond to errors.
return insert_resp

def get_user_from_db(self, handle, host, attempt_number=0):
def get_user_from_db(self,
handle=None,
host=None,
global_id=None,
attempt_number=0):
if attempt_number > MAX_FIND_RETRIES:
self._logger.error('Retried query too many times.')
return None
self._logger.debug('User %s@%s requested from database', handle, host)
self._logger.debug('User %s@%s (id %s) requested from database',
handle, host, global_id)
user_entry = database_pb2.UsersEntry(
handle=handle,
host=host
host=host,
global_id=global_id
)
find_req = database_pb2.UsersRequest(
request_type=database_pb2.UsersRequest.FIND,
match=user_entry
)
find_resp = self._db.Users(find_req)
if len(find_resp.results) == 0:
self._logger.warning('No user %s@%s found', handle, host)
self._logger.warning('No user %s@%s (id %s) found',
handle, host, global_id)
if global_id is not None:
# Should not try to create a user and hope it has this ID.
return None
self._create_user_in_db(user_entry)
return self.get_user_from_db(handle, host, attempt_number=attempt_number+1)
return self.get_user_from_db(handle, host,
attempt_number=attempt_number + 1)
elif len(find_resp.results) == 1:
self._logger.debug('Found user %s@%s from database', handle, host)
self._logger.debug('Found user %s@%s (id %s) from database',
handle, host, global_id)
return find_resp.results[0]
else:
self._logger.error('> 1 user found in database for %s@%s' +
', returning first one.', handle, host)
self._logger.error('> 1 user found in database for %s@%s (id %s)' +
', returning first one.',
handle, host, global_id)
return find_resp.results[0]

def create_follow_in_db(self, follower_id, followed_id):
Expand All @@ -72,6 +86,37 @@ def create_follow_in_db(self, follower_id, followed_id):
)
follow_resp = self._db.Follow(follow_req)
if follow_resp.result_type == database_pb2.DbFollowResponse.ERROR:
self._logger.error('Could not add follow to database: %s', follow_resp.error)
self._logger.error('Could not add follow to database: %s',
follow_resp.error)
return follow_resp

def get_follows(self, follower_id=None, followed_id=None):
self._logger.debug('Finding follows <User ID %s following User ID %s>',
('*' if (follower_id is None) else str(follower_id)),
('*' if (followed_id is None) else str(followed_id)))
follow_entry = database_pb2.Follow(
follower=follower_id,
followed=followed_id
)
follow_req = database_pb2.DbFollowRequest(
request_type=database_pb2.DbFollowRequest.FIND,
match=follow_entry
)
follow_resp = self._db.Follow(follow_req)
if follow_resp.result_type == database_pb2.DbFollowResponse.ERROR:
self._logger.error('Could not add follow to database: %s',
follow_resp.error)
return follow_resp

def convert_db_user_to_follow_user(self, db_user, follow_user):
self._logger.warning('Converting db user %s@%s to follow user.',
db_user.handle, db_user.host)
try:
follow_user.handle = db_user.handle
follow_user.host = db_user.host
follow_user.display_name = db_user.display_name
except Exception as e:
self._logger.warning('Error converting db user to follow user: ' +
str(e))
return False
return True

0 comments on commit 1b2d2ca

Please sign in to comment.