Add custom profile image (#745)

* Add profile image field to db

* Add set profile image rpc

* Got clear profile image working

* Add has custom profile image field in profile rpc respones
This commit is contained in:
Jonathan Zernik 2021-01-29 01:27:59 -08:00 committed by GitHub
parent 4c004a60bd
commit 627acea07c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 226 additions and 20 deletions

View file

@ -1,3 +1,4 @@
import os
import uuid
import grpc
@ -7,6 +8,7 @@ from squeak.params import SelectParams
from proto import squeak_admin_pb2
from proto import squeak_admin_pb2_grpc
from proto import squeak_server_pb2_grpc
from tests.util import bytes_to_base64_string
from tests.util import generate_signing_key
from tests.util import get_address
from tests.util import load_lightning_client
@ -143,3 +145,13 @@ def peer_id(server_stub, admin_stub):
@pytest.fixture
def random_name():
yield "random_name_{}".format(uuid.uuid1())
@pytest.fixture
def random_image():
yield os.urandom(567)
@pytest.fixture
def random_image_base64_string(random_image):
yield bytes_to_base64_string(random_image)

View file

@ -475,14 +475,6 @@ def test_make_contact_profile(server_stub, admin_stub):
def test_set_profile_following(server_stub, admin_stub, contact_profile_id):
# Get the existing profile
get_squeak_profile_response = admin_stub.GetSqueakProfile(
squeak_admin_pb2.GetSqueakProfileRequest(
profile_id=contact_profile_id,
)
)
assert not get_squeak_profile_response.squeak_profile.following
# Set the profile to be following
admin_stub.SetSqueakProfileFollowing(
squeak_admin_pb2.SetSqueakProfileFollowingRequest(
@ -499,16 +491,24 @@ def test_set_profile_following(server_stub, admin_stub, contact_profile_id):
)
assert get_squeak_profile_response.squeak_profile.following
# Set the profile to be not following
admin_stub.SetSqueakProfileFollowing(
squeak_admin_pb2.SetSqueakProfileFollowingRequest(
profile_id=contact_profile_id,
following=False,
)
)
def test_set_profile_sharing(server_stub, admin_stub, contact_profile_id):
# Get the existing profile
# Get the squeak profile again
get_squeak_profile_response = admin_stub.GetSqueakProfile(
squeak_admin_pb2.GetSqueakProfileRequest(
profile_id=contact_profile_id,
)
)
assert not get_squeak_profile_response.squeak_profile.sharing
assert not get_squeak_profile_response.squeak_profile.following
def test_set_profile_sharing(server_stub, admin_stub, contact_profile_id):
# Set the profile to be sharing
admin_stub.SetSqueakProfileSharing(
squeak_admin_pb2.SetSqueakProfileSharingRequest(
@ -525,6 +525,22 @@ def test_set_profile_sharing(server_stub, admin_stub, contact_profile_id):
)
assert get_squeak_profile_response.squeak_profile.sharing
# Set the profile to be not sharing
admin_stub.SetSqueakProfileSharing(
squeak_admin_pb2.SetSqueakProfileSharingRequest(
profile_id=contact_profile_id,
sharing=False,
)
)
# Get the squeak profile again
get_squeak_profile_response = admin_stub.GetSqueakProfile(
squeak_admin_pb2.GetSqueakProfileRequest(
profile_id=contact_profile_id,
)
)
assert not get_squeak_profile_response.squeak_profile.sharing
def test_rename_profile(server_stub, admin_stub, contact_profile_id, random_name):
# Rename the profile to something new
@ -544,6 +560,49 @@ def test_rename_profile(server_stub, admin_stub, contact_profile_id, random_name
assert get_squeak_profile_response.squeak_profile.profile_name == random_name
def test_set_profile_image(server_stub, admin_stub, contact_profile_id, random_image, random_image_base64_string):
print("random_image: {}".format(random_image))
print("random_image_base64_string: {}".format(random_image_base64_string))
# Set the profile image to something new
admin_stub.SetSqueakProfileImage(
squeak_admin_pb2.SetSqueakProfileImageRequest(
profile_id=contact_profile_id,
profile_image=random_image,
)
)
# Get the squeak profile
get_squeak_profile_response = admin_stub.GetSqueakProfile(
squeak_admin_pb2.GetSqueakProfileRequest(
profile_id=contact_profile_id,
)
)
print("get_squeak_profile_response.squeak_profile.profile_image: {}".format(
get_squeak_profile_response.squeak_profile.profile_image,
))
assert get_squeak_profile_response.squeak_profile.profile_image == random_image_base64_string
assert get_squeak_profile_response.squeak_profile.has_custom_profile_image
# Clear the profile image
admin_stub.ClearSqueakProfileImage(
squeak_admin_pb2.ClearSqueakProfileImageRequest(
profile_id=contact_profile_id,
)
)
# Get the squeak profile
get_squeak_profile_response = admin_stub.GetSqueakProfile(
squeak_admin_pb2.GetSqueakProfileRequest(
profile_id=contact_profile_id,
)
)
print("get_squeak_profile_response.squeak_profile.profile_image: {}".format(
get_squeak_profile_response.squeak_profile.profile_image,
))
assert get_squeak_profile_response.squeak_profile.profile_image != random_image_base64_string
assert not get_squeak_profile_response.squeak_profile.has_custom_profile_image
def test_delete_profile(server_stub, admin_stub, contact_profile_id):
# Delete the profile
admin_stub.DeleteSqueakProfile(

View file

@ -1,5 +1,6 @@
from __future__ import print_function
import base64
import time
from contextlib import contextmanager
@ -100,6 +101,11 @@ def subtract_tweak(n, tweak):
return scalar_to_bytes(sum_int)
def bytes_to_base64_string(data: bytes) -> str:
encoded_string = base64.b64encode(data)
return encoded_string.decode('utf-8')
@contextmanager
def connect_peer(lightning_client, lightning_host, remote_pubkey):
# Connect the peer

View file

@ -116,6 +116,14 @@ service SqueakAdmin {
*/
rpc DeleteSqueakProfile (DeleteSqueakProfileRequest) returns (DeleteSqueakProfileReply) {}
/** sqkadmin: `setsqueakprofileimage`
*/
rpc SetSqueakProfileImage (SetSqueakProfileImageRequest) returns (SetSqueakProfileImageReply) {}
/** sqkadmin: `clearsqueakprofileimage`
*/
rpc ClearSqueakProfileImage (ClearSqueakProfileImageRequest) returns (ClearSqueakProfileImageReply) {}
/** sqkadmin: `makesqueak`
*/
rpc MakeSqueak (MakeSqueakRequest) returns (MakeSqueakReply) {}
@ -359,6 +367,28 @@ message DeleteSqueakProfileRequest {
message DeleteSqueakProfileReply {
}
message SetSqueakProfileImageRequest {
/// The profile id
int32 profile_id = 1;
/// The profile image
bytes profile_image = 2;
}
message SetSqueakProfileImageReply {
}
message ClearSqueakProfileImageRequest {
/// The profile id
int32 profile_id = 1;
/// The profile image
bytes profile_image = 2;
}
message ClearSqueakProfileImageReply {
}
message SqueakProfile {
/// The profile id
int32 profile_id = 1;
@ -377,6 +407,12 @@ message SqueakProfile {
/// Following
bool following = 6;
/// The profile image
string profile_image = 7;
/// Has custom profile image
bool has_custom_profile_image = 8;
}
message MakeSqueakRequest {

View file

@ -145,7 +145,11 @@ class SqueakAdminServerHandler(object):
squeak_profile = self.squeak_controller.get_squeak_profile(profile_id)
if squeak_profile is None:
return None
logger.info("Got squeak profile with image: {}".format(
squeak_profile.profile_image))
squeak_profile_msg = squeak_profile_to_message(squeak_profile)
logger.info("Got squeak profile msg with image: {}".format(
squeak_profile_msg.profile_image))
return squeak_admin_pb2.GetSqueakProfileReply(
squeak_profile=squeak_profile_msg,
)
@ -215,6 +219,35 @@ class SqueakAdminServerHandler(object):
self.squeak_controller.delete_squeak_profile(profile_id)
return squeak_admin_pb2.DeleteSqueakProfileReply()
def handle_set_squeak_profile_image(self, request):
profile_id = request.profile_id
profile_image = request.profile_image
logger.info(
"Handle set squeak profile image with profile id: {}".format(
profile_id,
)
)
logger.info(
"Handle set squeak profile image with image: {}".format(
profile_image,
)
)
self.squeak_controller.set_squeak_profile_image(
profile_id, profile_image)
return squeak_admin_pb2.SetSqueakProfileImageReply()
def handle_clear_squeak_profile_image(self, request):
profile_id = request.profile_id
logger.info(
"Handle clear squeak profile image with profile id: {}".format(
profile_id,
)
)
self.squeak_controller.clear_squeak_profile_image(
profile_id,
)
return squeak_admin_pb2.ClearSqueakProfileImageReply()
def handle_get_squeak_profile_private_key(self, request):
profile_id = request.profile_id
logger.info(

View file

@ -97,6 +97,12 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
def DeleteSqueakProfile(self, request, context):
return self.handler.handle_delete_squeak_profile(request)
def SetSqueakProfileImage(self, request, context):
return self.handler.handle_set_squeak_profile_image(request)
def ClearSqueakProfileImage(self, request, context):
return self.handler.handle_clear_squeak_profile_image(request)
def GetSqueakProfilePrivateKey(self, request, context):
return self.handler.handle_get_squeak_profile_private_key(request)

View file

@ -54,6 +54,9 @@ def squeak_profile_to_message(squeak_profile):
if squeak_profile is None:
return None
has_private_key = squeak_profile.private_key is not None
profile_image = squeak_profile.profile_image or DEFAULT_PROFILE_IMAGE
has_custom_profile_image = squeak_profile.profile_image is not None
image_base64_str = bytes_to_base64_string(profile_image)
return squeak_admin_pb2.SqueakProfile(
profile_id=squeak_profile.profile_id,
profile_name=squeak_profile.profile_name,
@ -61,6 +64,8 @@ def squeak_profile_to_message(squeak_profile):
address=squeak_profile.address,
sharing=squeak_profile.sharing,
following=squeak_profile.following,
profile_image=image_base64_str,
has_custom_profile_image=has_custom_profile_image,
)

View file

@ -130,8 +130,9 @@ class SqueakController:
profile_name=profile_name,
private_key=signing_key_bytes,
address=str(address),
sharing=False,
following=False,
sharing=True,
following=True,
profile_image=None,
)
return self.squeak_db.insert_profile(squeak_profile)
@ -148,6 +149,7 @@ class SqueakController:
address=str(address),
sharing=False,
following=False,
profile_image=None,
)
return self.squeak_db.insert_profile(squeak_profile)
@ -161,7 +163,8 @@ class SqueakController:
private_key=None,
address=squeak_address,
sharing=False,
following=False,
following=True,
profile_image=None,
)
return self.squeak_db.insert_profile(squeak_profile)
@ -193,6 +196,12 @@ class SqueakController:
def delete_squeak_profile(self, profile_id: int):
self.squeak_db.delete_profile(profile_id)
def set_squeak_profile_image(self, profile_id: int, profile_image: bytes):
self.squeak_db.set_profile_image(profile_id, profile_image)
def clear_squeak_profile_image(self, profile_id: int):
self.squeak_db.set_profile_image(profile_id, None)
def get_squeak_profile_private_key(self, profile_id: int):
profile = self.get_squeak_profile(profile_id)
if profile.private_key is None:

View file

@ -10,3 +10,4 @@ class SqueakProfile(NamedTuple):
address: str
sharing: bool
following: bool
profile_image: Optional[bytes]

View file

@ -0,0 +1,33 @@
"""Add profile image column to profile table
Revision ID: 6851569c46e3
Revises: d5a06570ed3e
Create Date: 2021-01-28 23:25:31.015184
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '6851569c46e3'
down_revision = 'd5a06570ed3e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('profile', schema=None) as batch_op:
batch_op.add_column(
sa.Column('profile_image', sa.Binary(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('profile', schema=None) as batch_op:
batch_op.drop_column('profile_image')
# ### end Alembic commands ###

View file

@ -66,6 +66,7 @@ class Models:
Column("address", String(35), unique=True, nullable=False),
Column("sharing", Boolean, nullable=False),
Column("following", Boolean, nullable=False),
Column("profile_image", Binary, nullable=True),
)
self.peers = Table(

View file

@ -652,12 +652,15 @@ class SqueakDb:
with self.get_connection() as connection:
connection.execute(delete_profile_stmt)
# sql = """
# DELETE FROM profile
# WHERE profile_id=%s;
# """
# with self.get_cursor() as curs:
# curs.execute(sql, (profile_id,))
def set_profile_image(self, profile_id: int, profile_image: bytes):
""" Set a profile image. """
stmt = (
self.profiles.update()
.where(self.profiles.c.profile_id == profile_id)
.values(profile_image=profile_image)
)
with self.get_connection() as connection:
connection.execute(stmt)
def get_unverified_block_squeaks(self) -> List[bytes]:
""" Get all squeaks without block header. """
@ -1157,6 +1160,7 @@ class SqueakDb:
address=row["address"],
sharing=row["sharing"],
following=row["following"],
profile_image=row["profile_image"],
)
def _parse_squeak_entry_with_profile(self, row) -> SqueakEntryWithProfile:

View file

@ -75,6 +75,7 @@ def signing_profile():
address=str(address),
sharing=False,
following=False,
profile_image=None,
)