mirror of
https://github.com/hexolan/panels.git
synced 2026-03-26 12:40:21 +00:00
init comment-service
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import logging
|
||||
from typing import Type
|
||||
|
||||
from google.protobuf import message
|
||||
from aiokafka import AIOKafkaConsumer
|
||||
from aiokafka.structs import ConsumerRecord
|
||||
|
||||
from comment_service.models.config import Config
|
||||
from comment_service.models.service import CommentDBRepository
|
||||
|
||||
|
||||
class EventConsumer:
|
||||
"""An abstract consumer base class.
|
||||
|
||||
Attributes:
|
||||
CONSUMER_TOPIC: The topic to consume events from.
|
||||
CONSUMER_EVENT_TYPE (Type[message.Message]): The protobuf class type of the event msgs (used for deserialisation).
|
||||
_db_repo (Type[CommentDBRepository]): The repository interface for modifying data.
|
||||
_consumer (aiokafka.AIOKafkaConsumer): The underlying Kafka instance.
|
||||
|
||||
"""
|
||||
CONSUMER_TOPIC: str
|
||||
CONSUMER_EVENT_TYPE: Type[message.Message]
|
||||
|
||||
def __init__(self, config: Config, db_repo: Type[CommentDBRepository]) -> None:
|
||||
"""Initialise the event consumer.
|
||||
|
||||
Args:
|
||||
config (Config): The app configuration instance (to access brokers list).
|
||||
db_repo (Type[CommentDBRepository]): The repository interface for updating data.
|
||||
|
||||
"""
|
||||
self._db_repo = db_repo
|
||||
self._consumer = AIOKafkaConsumer(
|
||||
self.CONSUMER_TOPIC,
|
||||
bootstrap_servers=config.kafka_brokers,
|
||||
group_id="comment-service"
|
||||
)
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Begin consuming messages."""
|
||||
await self._consumer.start()
|
||||
try:
|
||||
async for msg in self._consumer:
|
||||
await self._process_msg(msg)
|
||||
except Exception as e:
|
||||
logging.error(f"error whilst consuming messages (on topic '{self.CONSUMER_TOPIC}'): {e}")
|
||||
finally:
|
||||
await self._consumer.stop()
|
||||
|
||||
async def _process_msg(self, msg: ConsumerRecord) -> None:
|
||||
"""Process a recieved message.
|
||||
|
||||
The messages are deserialise from bytes into their protobuf form,
|
||||
then passed to the `_process_event` method to handle the logic.
|
||||
|
||||
Args:
|
||||
msg (kafka.Message): The event to process.
|
||||
|
||||
"""
|
||||
try:
|
||||
event = self.CONSUMER_EVENT_TYPE()
|
||||
event.ParseFromString(msg.value)
|
||||
assert event.type != ""
|
||||
await self._process_event(event)
|
||||
except AssertionError:
|
||||
logging.error("invalid event recieved")
|
||||
return
|
||||
except Exception as e:
|
||||
logging.error("error whilst processing recieved event:", e)
|
||||
return
|
||||
|
||||
async def _process_event(self, event: Type[message.Message]) -> None:
|
||||
"""Process a recieved event.
|
||||
|
||||
Args:
|
||||
event (Type[message.Message]): The event serialised to protobuf form.
|
||||
|
||||
"""
|
||||
raise NotImplementedError("required consumer method (_process_event) not implemented")
|
||||
@@ -0,0 +1,33 @@
|
||||
import logging
|
||||
|
||||
from comment_service.models.proto import post_pb2
|
||||
from comment_service.events.base_consumer import EventConsumer
|
||||
|
||||
|
||||
class PostEventConsumer(EventConsumer):
|
||||
"""Consumer class responsible for 'post' events.
|
||||
|
||||
Attributes:
|
||||
CONSUMER_TOPIC: The topic to consume events from.
|
||||
CONSUMER_EVENT_TYPE (post_pb2.PostEvent): Kafka messages are serialised to this type.
|
||||
_db_repo (CommentDBRepository): The repository interface for modifying data.
|
||||
_consumer (aiokafka.AIOKafkaConsumer): The underlying Kafka instance.
|
||||
|
||||
"""
|
||||
CONSUMER_TOPIC = "post"
|
||||
CONSUMER_EVENT_TYPE = post_pb2.PostEvent
|
||||
|
||||
async def _process_event(self, event: post_pb2.PostEvent) -> None:
|
||||
"""Process a recieved event.
|
||||
|
||||
In response to a Post deleted event, delete any comments
|
||||
that fall under that post.
|
||||
|
||||
Args:
|
||||
event (post_pb2.PostEvent): The decoded protobuf message.
|
||||
|
||||
"""
|
||||
if event.type == "deleted":
|
||||
assert event.data.id != ""
|
||||
await self._db_repo.delete_post_comments(event.data.id)
|
||||
logging.info("succesfully processed PostEvent (type: 'deleted')")
|
||||
89
services/comment-service/comment_service/events/producer.py
Normal file
89
services/comment-service/comment_service/events/producer.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import asyncio
|
||||
|
||||
from aiokafka import AIOKafkaProducer
|
||||
|
||||
from comment_service.models.config import Config
|
||||
from comment_service.models.service import Comment
|
||||
from comment_service.models.proto import comment_pb2
|
||||
|
||||
|
||||
class CommentEventProducer:
|
||||
"""Service event producer.
|
||||
|
||||
Attributes:
|
||||
_producer (aiokafka.AIOKafkaProducer): The underlying Kafka instance.
|
||||
_pending_sends (set): Background events that are still awaiting a response.
|
||||
|
||||
"""
|
||||
def __init__(self, config: Config) -> None:
|
||||
"""Initialise the event producer.
|
||||
|
||||
Args:
|
||||
config (Config): The app configuration instance (to access Kafka brokers list).
|
||||
|
||||
"""
|
||||
self._producer = AIOKafkaProducer(
|
||||
bootstrap_servers=config.kafka_brokers,
|
||||
client_id="auth-service"
|
||||
)
|
||||
self._pending_sends = set()
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Start the underlying Kafka instance (open a connection)."""
|
||||
await self._producer.start()
|
||||
|
||||
async def _send_event(self, event: comment_pb2.CommentEvent) -> None:
|
||||
"""Send an event.
|
||||
|
||||
Execute the sending action as a background task to avoid
|
||||
delaying a response to the request.
|
||||
|
||||
Args:
|
||||
event (comment_pb2.CommentEvent): The protobuf class for the event.
|
||||
|
||||
"""
|
||||
bg_task = asyncio.create_task(self._producer.send(
|
||||
topic="comment",
|
||||
value=event.SerializeToString(),
|
||||
))
|
||||
self._pending_sends.add(bg_task)
|
||||
bg_task.add_done_callback(self._pending_sends.discard)
|
||||
|
||||
async def send_created_event(self, comment: Comment) -> None:
|
||||
"""Send a comment created event.
|
||||
|
||||
Args:
|
||||
comment (Comment): The comment to reference.
|
||||
|
||||
"""
|
||||
event = comment_pb2.CommentEvent(
|
||||
type="created",
|
||||
data=Comment.to_protobuf(comment)
|
||||
)
|
||||
await self._send_event(event)
|
||||
|
||||
async def send_updated_event(self, comment: Comment) -> None:
|
||||
"""Send a comment updated event.
|
||||
|
||||
Args:
|
||||
comment (Comment): The comment to reference.
|
||||
|
||||
"""
|
||||
event = comment_pb2.CommentEvent(
|
||||
type="updated",
|
||||
data=Comment.to_protobuf(comment)
|
||||
)
|
||||
await self._send_event(event)
|
||||
|
||||
async def send_deleted_event(self, comment: Comment) -> None:
|
||||
"""Send a comment deleted event.
|
||||
|
||||
Args:
|
||||
comment (Comment): The comment to reference.
|
||||
|
||||
"""
|
||||
event = comment_pb2.CommentEvent(
|
||||
type="deleted",
|
||||
data=Comment.to_protobuf(comment)
|
||||
)
|
||||
await self._send_event(event)
|
||||
67
services/comment-service/comment_service/events/service.py
Normal file
67
services/comment-service/comment_service/events/service.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import asyncio
|
||||
from typing import Type
|
||||
|
||||
from comment_service.models.config import Config
|
||||
from comment_service.models.service import CommentDBRepository
|
||||
from comment_service.events.producer import CommentEventProducer
|
||||
from comment_service.events.post_consumer import PostEventConsumer
|
||||
from comment_service.events.user_consumer import UserEventConsumer
|
||||
|
||||
|
||||
class EventConsumersWrapper:
|
||||
"""A wrapper class for starting the event consumers.
|
||||
|
||||
Attributes:
|
||||
_post_consumer (PostEventConsumer): A wrapped consumer.
|
||||
_user_consumer (UserEventConsumer): A wrapped consumer.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, post_consumer: PostEventConsumer, user_consumer: UserEventConsumer) -> None:
|
||||
"""Add the consumers to the wrapper
|
||||
|
||||
Args:
|
||||
post_consumer (PostEventConsumer): Initialised post consumer.
|
||||
user_consumer (UserEventConsumer): Initialised user consumer.
|
||||
|
||||
"""
|
||||
self._post_consumer = post_consumer
|
||||
self._user_consumer = user_consumer
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Begin consuming events on all the event consumers."""
|
||||
await asyncio.gather(
|
||||
self._post_consumer.start(),
|
||||
self._user_consumer.start()
|
||||
)
|
||||
|
||||
|
||||
def create_consumers(config: Config, db_repo: Type[CommentDBRepository]) -> EventConsumersWrapper:
|
||||
"""Initialse the event consumers and return them in a wrapper.
|
||||
|
||||
Args:
|
||||
config (Config): The app configuration instance.
|
||||
db_repo (Type[CommentDBRepository]): The database repo to pass to the consumers.
|
||||
|
||||
Returns:
|
||||
EventConsumerWrapper
|
||||
|
||||
"""
|
||||
post_consumer = PostEventConsumer(config, db_repo)
|
||||
user_consumer = UserEventConsumer(config, db_repo)
|
||||
return EventConsumersWrapper(post_consumer=post_consumer, user_consumer=user_consumer)
|
||||
|
||||
|
||||
async def create_producer(config: Config) -> CommentEventProducer:
|
||||
"""Create an event producer for the service.
|
||||
|
||||
Args:
|
||||
config (Config): The app configuration instance.
|
||||
|
||||
Returns:
|
||||
CommentEventProducer
|
||||
|
||||
"""
|
||||
producer = CommentEventProducer(config)
|
||||
await producer.start()
|
||||
return producer
|
||||
@@ -0,0 +1,33 @@
|
||||
import logging
|
||||
|
||||
from comment_service.models.proto import user_pb2
|
||||
from comment_service.events.base_consumer import EventConsumer
|
||||
|
||||
|
||||
class UserEventConsumer(EventConsumer):
|
||||
"""Consumer class responsible for 'user' events.
|
||||
|
||||
Attributes:
|
||||
CONSUMER_TOPIC: The topic to consume events from.
|
||||
CONSUMER_EVENT_TYPE (user_pb2.UserEvent): Kafka messages are serialised to this type.
|
||||
_db_repo (CommentDBRepository): The repository interface for modifying data.
|
||||
_consumer (aiokafka.AIOKafkaConsumer): The underlying Kafka instance.
|
||||
|
||||
"""
|
||||
CONSUMER_TOPIC = "user"
|
||||
CONSUMER_EVENT_TYPE = user_pb2.UserEvent
|
||||
|
||||
async def _process_event(self, event: user_pb2.UserEvent) -> None:
|
||||
"""Process a recieved event.
|
||||
|
||||
In response to a User deleted event, delete any comments
|
||||
created by that user.
|
||||
|
||||
Args:
|
||||
event (user_pb2.UserEvent): The decoded protobuf message.
|
||||
|
||||
"""
|
||||
if event.type == "deleted":
|
||||
assert event.data.id != ""
|
||||
await self._db_repo.delete_user_comments(event.data.id)
|
||||
logging.info("succesfully processed UserEvent (type: 'deleted')")
|
||||
32
services/comment-service/comment_service/main.py
Normal file
32
services/comment-service/comment_service/main.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from sys import stdout
|
||||
|
||||
from comment_service.models.config import Config
|
||||
from comment_service.events.service import create_producer, create_consumers
|
||||
from comment_service.postgres.service import create_db_repository
|
||||
from comment_service.redis.service import create_redis_repository
|
||||
from comment_service.rpc.service import create_rpc_server
|
||||
from comment_service.service import ServiceRepository
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
config = Config()
|
||||
|
||||
event_prod = await create_producer(config)
|
||||
db_repo = await create_db_repository(config, event_producer=event_prod)
|
||||
redis_repo = await create_redis_repository(config, downstream_repo=db_repo)
|
||||
svc_repo = ServiceRepository(downstream_repo=redis_repo)
|
||||
|
||||
rpc_server = create_rpc_server(svc_repo)
|
||||
event_consumers = create_consumers(config, db_repo=db_repo)
|
||||
|
||||
await asyncio.gather(
|
||||
rpc_server.start(),
|
||||
event_consumers.start()
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(stream=stdout, level=logging.INFO)
|
||||
asyncio.run(main())
|
||||
66
services/comment-service/comment_service/models/config.py
Normal file
66
services/comment-service/comment_service/models/config.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from typing import Any, List
|
||||
|
||||
from pydantic import computed_field
|
||||
from pydantic.fields import FieldInfo
|
||||
from pydantic_settings import BaseSettings, EnvSettingsSource
|
||||
from pydantic_settings.main import BaseSettings
|
||||
from pydantic_settings.sources import PydanticBaseSettingsSource
|
||||
|
||||
|
||||
class ConfigSource(EnvSettingsSource):
|
||||
"""Responsible for loading config options from environment variables."""
|
||||
def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
|
||||
if field_name == "kafka_brokers":
|
||||
# Comma delimit the kafka brokers.
|
||||
if value == None:
|
||||
return None
|
||||
return value.split(",")
|
||||
|
||||
return super().prepare_field_value(field_name, field, value, value_is_complex)
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
"""The service configuration loaded from environment
|
||||
variables.
|
||||
|
||||
Attributes:
|
||||
postgres_user (str): Loaded from the 'POSTGRES_USER' envvar.
|
||||
postgres_pass (str): Loaded from the 'POSTGRES_PASS' envvar.
|
||||
postgres_host (str): Loaded from the 'POSTGRES_HOST' envvar.
|
||||
postgres_database (str): Loaded from the 'POSTGRES_DATABASE' envvar.
|
||||
redis_host (str): Loaded from the 'REDIS_HOST' envvar.
|
||||
redis_pass (str): Loaded from the 'REDIS_PASS' envvar.
|
||||
kafka_brokers (list[str]): Loaded and comma delmited from the 'KAFKA_BROKERS' envvar.
|
||||
postgres_dsn (str): Computed when accessed the first time. (@property)
|
||||
|
||||
"""
|
||||
postgres_user: str
|
||||
postgres_pass: str
|
||||
postgres_host: str
|
||||
postgres_database: str
|
||||
|
||||
redis_host: str
|
||||
redis_pass: str
|
||||
|
||||
kafka_brokers: List[str]
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def postgres_dsn(self) -> str:
|
||||
"""Uses the postgres_user, postgres_pass, postgres_host,
|
||||
and postgres_database options to assemble a DSN.
|
||||
|
||||
Returns:
|
||||
str: DSN for connecting to the database.
|
||||
|
||||
"""
|
||||
return "postgresql+asyncpg://{user}:{password}@{host}/{db}".format(
|
||||
user=self.postgres_user,
|
||||
password=self.postgres_pass,
|
||||
host=self.postgres_host,
|
||||
db=self.postgres_database
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def settings_customise_sources(cls, settings_cls: type[BaseSettings], *args, **kwargs) -> tuple[PydanticBaseSettingsSource, ...]:
|
||||
return (ConfigSource(settings_cls), )
|
||||
@@ -0,0 +1,59 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
from grpc import RpcContext, StatusCode
|
||||
|
||||
|
||||
class ServiceErrorCode(Enum):
|
||||
"""Error codes used for classifying ServiceExceptions."""
|
||||
INVALID_ARGUMENT = auto()
|
||||
CONFLICT = auto()
|
||||
NOT_FOUND = auto()
|
||||
INVALID_CREDENTIALS = auto()
|
||||
SERVICE_ERROR = auto()
|
||||
|
||||
__RPC_CODE_MAP__ = {
|
||||
INVALID_ARGUMENT: StatusCode.INVALID_ARGUMENT,
|
||||
CONFLICT: StatusCode.ALREADY_EXISTS,
|
||||
NOT_FOUND: StatusCode.NOT_FOUND,
|
||||
INVALID_CREDENTIALS: StatusCode.UNAUTHENTICATED,
|
||||
SERVICE_ERROR: StatusCode.INTERNAL
|
||||
}
|
||||
|
||||
def to_rpc_code(self) -> StatusCode:
|
||||
"""Convert a service error code to a gRPC status code.
|
||||
|
||||
Returns:
|
||||
The mapped RPC status code, if found, otherwise gRPC Unknown status code.
|
||||
|
||||
"""
|
||||
return self.__class__.__RPC_CODE_MAP__.get(self.value, StatusCode.UNKNOWN)
|
||||
|
||||
|
||||
class ServiceException(Exception):
|
||||
"""This exception provides an interface to convert service errors
|
||||
into gRPC errors, which can then be returned to the caller.
|
||||
|
||||
Args:
|
||||
msg (str): Error message.
|
||||
error_code (ServiceErrorCode): Categorisation code for the error.
|
||||
|
||||
Attributes:
|
||||
msg (str): The error message.
|
||||
error_code (ServiceErrorCode): Categorisation code for the error.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str, error_code: ServiceErrorCode) -> None:
|
||||
super().__init__(msg)
|
||||
self.msg = msg
|
||||
self.error_code = error_code
|
||||
|
||||
def apply_to_rpc(self, context: RpcContext) -> None:
|
||||
"""Apply the exception to an RPC context.
|
||||
|
||||
Args:
|
||||
context (grpc.RpcContext): The context to apply to.
|
||||
|
||||
"""
|
||||
context.set_code(self.error_code.to_rpc_code())
|
||||
context.set_details(self.msg)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
|
||||
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rcomment.proto\x12\x11panels.comment.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto"\xaa\x01\n\x07Comment\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0f\n\x07post_id\x18\x02 \x01(\t\x12\x11\n\tauthor_id\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\t\x12.\n\ncreated_at\x18\x05 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x06 \x01(\x0b2\x1a.google.protobuf.Timestamp"!\n\x0eCommentMutable\x12\x0f\n\x07message\x18\x01 \x01(\t"k\n\x14CreateCommentRequest\x12\x0f\n\x07post_id\x18\x01 \x01(\t\x12\x11\n\tauthor_id\x18\x02 \x01(\t\x12/\n\x04data\x18\x03 \x01(\x0b2!.panels.comment.v1.CommentMutable"S\n\x14UpdateCommentRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12/\n\x04data\x18\x02 \x01(\x0b2!.panels.comment.v1.CommentMutable""\n\x14DeleteCommentRequest\x12\n\n\x02id\x18\x01 \x01(\t"\x1f\n\x11GetCommentRequest\x12\n\n\x02id\x18\x01 \x01(\t")\n\x16GetPostCommentsRequest\x12\x0f\n\x07post_id\x18\x01 \x01(\t"<\n\x0cPostComments\x12,\n\x08comments\x18\x01 \x03(\x0b2\x1a.panels.comment.v1.Comment"F\n\x0cCommentEvent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12(\n\x04data\x18\x02 \x01(\x0b2\x1a.panels.comment.v1.Comment2\xc7\x03\n\x0eCommentService\x12V\n\rCreateComment\x12\'.panels.comment.v1.CreateCommentRequest\x1a\x1a.panels.comment.v1.Comment"\x00\x12V\n\rUpdateComment\x12\'.panels.comment.v1.UpdateCommentRequest\x1a\x1a.panels.comment.v1.Comment"\x00\x12R\n\rDeleteComment\x12\'.panels.comment.v1.DeleteCommentRequest\x1a\x16.google.protobuf.Empty"\x00\x12P\n\nGetComment\x12$.panels.comment.v1.GetCommentRequest\x1a\x1a.panels.comment.v1.Comment"\x00\x12_\n\x0fGetPostComments\x12).panels.comment.v1.GetPostCommentsRequest\x1a\x1f.panels.comment.v1.PostComments"\x00b\x06proto3')
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'comment_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
_globals['_COMMENT']._serialized_start = 99
|
||||
_globals['_COMMENT']._serialized_end = 269
|
||||
_globals['_COMMENTMUTABLE']._serialized_start = 271
|
||||
_globals['_COMMENTMUTABLE']._serialized_end = 304
|
||||
_globals['_CREATECOMMENTREQUEST']._serialized_start = 306
|
||||
_globals['_CREATECOMMENTREQUEST']._serialized_end = 413
|
||||
_globals['_UPDATECOMMENTREQUEST']._serialized_start = 415
|
||||
_globals['_UPDATECOMMENTREQUEST']._serialized_end = 498
|
||||
_globals['_DELETECOMMENTREQUEST']._serialized_start = 500
|
||||
_globals['_DELETECOMMENTREQUEST']._serialized_end = 534
|
||||
_globals['_GETCOMMENTREQUEST']._serialized_start = 536
|
||||
_globals['_GETCOMMENTREQUEST']._serialized_end = 567
|
||||
_globals['_GETPOSTCOMMENTSREQUEST']._serialized_start = 569
|
||||
_globals['_GETPOSTCOMMENTSREQUEST']._serialized_end = 610
|
||||
_globals['_POSTCOMMENTS']._serialized_start = 612
|
||||
_globals['_POSTCOMMENTS']._serialized_end = 672
|
||||
_globals['_COMMENTEVENT']._serialized_start = 674
|
||||
_globals['_COMMENTEVENT']._serialized_end = 744
|
||||
_globals['_COMMENTSERVICE']._serialized_start = 747
|
||||
_globals['_COMMENTSERVICE']._serialized_end = 1202
|
||||
@@ -0,0 +1,97 @@
|
||||
from google.protobuf import empty_pb2 as _empty_pb2
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class Comment(_message.Message):
|
||||
__slots__ = ['id', 'post_id', 'author_id', 'message', 'created_at', 'updated_at']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
POST_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
AUTHOR_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
MESSAGE_FIELD_NUMBER: _ClassVar[int]
|
||||
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
UPDATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
post_id: str
|
||||
author_id: str
|
||||
message: str
|
||||
created_at: _timestamp_pb2.Timestamp
|
||||
updated_at: _timestamp_pb2.Timestamp
|
||||
|
||||
def __init__(self, id: _Optional[str]=..., post_id: _Optional[str]=..., author_id: _Optional[str]=..., message: _Optional[str]=..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]]=..., updated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class CommentMutable(_message.Message):
|
||||
__slots__ = ['message']
|
||||
MESSAGE_FIELD_NUMBER: _ClassVar[int]
|
||||
message: str
|
||||
|
||||
def __init__(self, message: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class CreateCommentRequest(_message.Message):
|
||||
__slots__ = ['post_id', 'author_id', 'data']
|
||||
POST_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
AUTHOR_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
post_id: str
|
||||
author_id: str
|
||||
data: CommentMutable
|
||||
|
||||
def __init__(self, post_id: _Optional[str]=..., author_id: _Optional[str]=..., data: _Optional[_Union[CommentMutable, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class UpdateCommentRequest(_message.Message):
|
||||
__slots__ = ['id', 'data']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
data: CommentMutable
|
||||
|
||||
def __init__(self, id: _Optional[str]=..., data: _Optional[_Union[CommentMutable, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class DeleteCommentRequest(_message.Message):
|
||||
__slots__ = ['id']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
|
||||
def __init__(self, id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class GetCommentRequest(_message.Message):
|
||||
__slots__ = ['id']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
|
||||
def __init__(self, id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class GetPostCommentsRequest(_message.Message):
|
||||
__slots__ = ['post_id']
|
||||
POST_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
post_id: str
|
||||
|
||||
def __init__(self, post_id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class PostComments(_message.Message):
|
||||
__slots__ = ['comments']
|
||||
COMMENTS_FIELD_NUMBER: _ClassVar[int]
|
||||
comments: _containers.RepeatedCompositeFieldContainer[Comment]
|
||||
|
||||
def __init__(self, comments: _Optional[_Iterable[_Union[Comment, _Mapping]]]=...) -> None:
|
||||
...
|
||||
|
||||
class CommentEvent(_message.Message):
|
||||
__slots__ = ['type', 'data']
|
||||
TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
type: str
|
||||
data: Comment
|
||||
|
||||
def __init__(self, type: _Optional[str]=..., data: _Optional[_Union[Comment, _Mapping]]=...) -> None:
|
||||
...
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
from . import comment_pb2 as comment__pb2
|
||||
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
|
||||
|
||||
class CommentServiceStub(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.CreateComment = channel.unary_unary('/panels.comment.v1.CommentService/CreateComment', request_serializer=comment__pb2.CreateCommentRequest.SerializeToString, response_deserializer=comment__pb2.Comment.FromString)
|
||||
self.UpdateComment = channel.unary_unary('/panels.comment.v1.CommentService/UpdateComment', request_serializer=comment__pb2.UpdateCommentRequest.SerializeToString, response_deserializer=comment__pb2.Comment.FromString)
|
||||
self.DeleteComment = channel.unary_unary('/panels.comment.v1.CommentService/DeleteComment', request_serializer=comment__pb2.DeleteCommentRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString)
|
||||
self.GetComment = channel.unary_unary('/panels.comment.v1.CommentService/GetComment', request_serializer=comment__pb2.GetCommentRequest.SerializeToString, response_deserializer=comment__pb2.Comment.FromString)
|
||||
self.GetPostComments = channel.unary_unary('/panels.comment.v1.CommentService/GetPostComments', request_serializer=comment__pb2.GetPostCommentsRequest.SerializeToString, response_deserializer=comment__pb2.PostComments.FromString)
|
||||
|
||||
class CommentServiceServicer(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def CreateComment(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def UpdateComment(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def DeleteComment(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetComment(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetPostComments(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def add_CommentServiceServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {'CreateComment': grpc.unary_unary_rpc_method_handler(servicer.CreateComment, request_deserializer=comment__pb2.CreateCommentRequest.FromString, response_serializer=comment__pb2.Comment.SerializeToString), 'UpdateComment': grpc.unary_unary_rpc_method_handler(servicer.UpdateComment, request_deserializer=comment__pb2.UpdateCommentRequest.FromString, response_serializer=comment__pb2.Comment.SerializeToString), 'DeleteComment': grpc.unary_unary_rpc_method_handler(servicer.DeleteComment, request_deserializer=comment__pb2.DeleteCommentRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString), 'GetComment': grpc.unary_unary_rpc_method_handler(servicer.GetComment, request_deserializer=comment__pb2.GetCommentRequest.FromString, response_serializer=comment__pb2.Comment.SerializeToString), 'GetPostComments': grpc.unary_unary_rpc_method_handler(servicer.GetPostComments, request_deserializer=comment__pb2.GetPostCommentsRequest.FromString, response_serializer=comment__pb2.PostComments.SerializeToString)}
|
||||
generic_handler = grpc.method_handlers_generic_handler('panels.comment.v1.CommentService', rpc_method_handlers)
|
||||
server.add_generic_rpc_handlers((generic_handler,))
|
||||
|
||||
class CommentService(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
@staticmethod
|
||||
def CreateComment(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.comment.v1.CommentService/CreateComment', comment__pb2.CreateCommentRequest.SerializeToString, comment__pb2.Comment.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def UpdateComment(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.comment.v1.CommentService/UpdateComment', comment__pb2.UpdateCommentRequest.SerializeToString, comment__pb2.Comment.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def DeleteComment(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.comment.v1.CommentService/DeleteComment', comment__pb2.DeleteCommentRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetComment(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.comment.v1.CommentService/GetComment', comment__pb2.GetCommentRequest.SerializeToString, comment__pb2.Comment.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetPostComments(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.comment.v1.CommentService/GetPostComments', comment__pb2.GetPostCommentsRequest.SerializeToString, comment__pb2.PostComments.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
|
||||
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\npost.proto\x12\x0epanels.post.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto"\xb7\x01\n\x04Post\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08panel_id\x18\x02 \x01(\t\x12\x11\n\tauthor_id\x18\x03 \x01(\t\x12\r\n\x05title\x18\x04 \x01(\t\x12\x0f\n\x07content\x18\x05 \x01(\t\x12.\n\ncreated_at\x18\x06 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x07 \x01(\x0b2\x1a.google.protobuf.Timestamp"M\n\x0bPostMutable\x12\x12\n\x05title\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07content\x18\x02 \x01(\tH\x01\x88\x01\x01B\x08\n\x06_titleB\n\n\x08_content"a\n\x11CreatePostRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12)\n\x04data\x18\x03 \x01(\x0b2\x1b.panels.post.v1.PostMutable"\x1c\n\x0eGetPostRequest\x12\n\n\x02id\x18\x01 \x01(\t"3\n\x13GetPanelPostRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t"J\n\x11UpdatePostRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12)\n\x04data\x18\x02 \x01(\x0b2\x1b.panels.post.v1.PostMutable"\x1f\n\x11DeletePostRequest\x12\n\n\x02id\x18\x01 \x01(\t"\x15\n\x13GetFeedPostsRequest"0\n\tFeedPosts\x12#\n\x05posts\x18\x01 \x03(\x0b2\x14.panels.post.v1.Post"&\n\x13GetUserPostsRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t"0\n\tUserPosts\x12#\n\x05posts\x18\x01 \x03(\x0b2\x14.panels.post.v1.Post"(\n\x14GetPanelPostsRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t"1\n\nPanelPosts\x12#\n\x05posts\x18\x01 \x03(\x0b2\x14.panels.post.v1.Post"=\n\tPostEvent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12"\n\x04data\x18\x02 \x01(\x0b2\x14.panels.post.v1.Post2\xf3\x04\n\x0bPostService\x12G\n\nCreatePost\x12!.panels.post.v1.CreatePostRequest\x1a\x14.panels.post.v1.Post"\x00\x12A\n\x07GetPost\x12\x1e.panels.post.v1.GetPostRequest\x1a\x14.panels.post.v1.Post"\x00\x12K\n\x0cGetPanelPost\x12#.panels.post.v1.GetPanelPostRequest\x1a\x14.panels.post.v1.Post"\x00\x12G\n\nUpdatePost\x12!.panels.post.v1.UpdatePostRequest\x1a\x14.panels.post.v1.Post"\x00\x12I\n\nDeletePost\x12!.panels.post.v1.DeletePostRequest\x1a\x16.google.protobuf.Empty"\x00\x12P\n\x0cGetFeedPosts\x12#.panels.post.v1.GetFeedPostsRequest\x1a\x19.panels.post.v1.FeedPosts"\x00\x12P\n\x0cGetUserPosts\x12#.panels.post.v1.GetUserPostsRequest\x1a\x19.panels.post.v1.UserPosts"\x00\x12S\n\rGetPanelPosts\x12$.panels.post.v1.GetPanelPostsRequest\x1a\x1a.panels.post.v1.PanelPosts"\x00b\x06proto3')
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'post_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
_globals['_POST']._serialized_start = 93
|
||||
_globals['_POST']._serialized_end = 276
|
||||
_globals['_POSTMUTABLE']._serialized_start = 278
|
||||
_globals['_POSTMUTABLE']._serialized_end = 355
|
||||
_globals['_CREATEPOSTREQUEST']._serialized_start = 357
|
||||
_globals['_CREATEPOSTREQUEST']._serialized_end = 454
|
||||
_globals['_GETPOSTREQUEST']._serialized_start = 456
|
||||
_globals['_GETPOSTREQUEST']._serialized_end = 484
|
||||
_globals['_GETPANELPOSTREQUEST']._serialized_start = 486
|
||||
_globals['_GETPANELPOSTREQUEST']._serialized_end = 537
|
||||
_globals['_UPDATEPOSTREQUEST']._serialized_start = 539
|
||||
_globals['_UPDATEPOSTREQUEST']._serialized_end = 613
|
||||
_globals['_DELETEPOSTREQUEST']._serialized_start = 615
|
||||
_globals['_DELETEPOSTREQUEST']._serialized_end = 646
|
||||
_globals['_GETFEEDPOSTSREQUEST']._serialized_start = 648
|
||||
_globals['_GETFEEDPOSTSREQUEST']._serialized_end = 669
|
||||
_globals['_FEEDPOSTS']._serialized_start = 671
|
||||
_globals['_FEEDPOSTS']._serialized_end = 719
|
||||
_globals['_GETUSERPOSTSREQUEST']._serialized_start = 721
|
||||
_globals['_GETUSERPOSTSREQUEST']._serialized_end = 759
|
||||
_globals['_USERPOSTS']._serialized_start = 761
|
||||
_globals['_USERPOSTS']._serialized_end = 809
|
||||
_globals['_GETPANELPOSTSREQUEST']._serialized_start = 811
|
||||
_globals['_GETPANELPOSTSREQUEST']._serialized_end = 851
|
||||
_globals['_PANELPOSTS']._serialized_start = 853
|
||||
_globals['_PANELPOSTS']._serialized_end = 902
|
||||
_globals['_POSTEVENT']._serialized_start = 904
|
||||
_globals['_POSTEVENT']._serialized_end = 965
|
||||
_globals['_POSTSERVICE']._serialized_start = 968
|
||||
_globals['_POSTSERVICE']._serialized_end = 1595
|
||||
@@ -0,0 +1,141 @@
|
||||
from google.protobuf import empty_pb2 as _empty_pb2
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class Post(_message.Message):
|
||||
__slots__ = ['id', 'panel_id', 'author_id', 'title', 'content', 'created_at', 'updated_at']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
PANEL_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
AUTHOR_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
TITLE_FIELD_NUMBER: _ClassVar[int]
|
||||
CONTENT_FIELD_NUMBER: _ClassVar[int]
|
||||
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
UPDATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
panel_id: str
|
||||
author_id: str
|
||||
title: str
|
||||
content: str
|
||||
created_at: _timestamp_pb2.Timestamp
|
||||
updated_at: _timestamp_pb2.Timestamp
|
||||
|
||||
def __init__(self, id: _Optional[str]=..., panel_id: _Optional[str]=..., author_id: _Optional[str]=..., title: _Optional[str]=..., content: _Optional[str]=..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]]=..., updated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class PostMutable(_message.Message):
|
||||
__slots__ = ['title', 'content']
|
||||
TITLE_FIELD_NUMBER: _ClassVar[int]
|
||||
CONTENT_FIELD_NUMBER: _ClassVar[int]
|
||||
title: str
|
||||
content: str
|
||||
|
||||
def __init__(self, title: _Optional[str]=..., content: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class CreatePostRequest(_message.Message):
|
||||
__slots__ = ['panel_id', 'user_id', 'data']
|
||||
PANEL_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
USER_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
panel_id: str
|
||||
user_id: str
|
||||
data: PostMutable
|
||||
|
||||
def __init__(self, panel_id: _Optional[str]=..., user_id: _Optional[str]=..., data: _Optional[_Union[PostMutable, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class GetPostRequest(_message.Message):
|
||||
__slots__ = ['id']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
|
||||
def __init__(self, id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class GetPanelPostRequest(_message.Message):
|
||||
__slots__ = ['panel_id', 'id']
|
||||
PANEL_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
panel_id: str
|
||||
id: str
|
||||
|
||||
def __init__(self, panel_id: _Optional[str]=..., id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class UpdatePostRequest(_message.Message):
|
||||
__slots__ = ['id', 'data']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
data: PostMutable
|
||||
|
||||
def __init__(self, id: _Optional[str]=..., data: _Optional[_Union[PostMutable, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class DeletePostRequest(_message.Message):
|
||||
__slots__ = ['id']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
|
||||
def __init__(self, id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class GetFeedPostsRequest(_message.Message):
|
||||
__slots__ = []
|
||||
|
||||
def __init__(self) -> None:
|
||||
...
|
||||
|
||||
class FeedPosts(_message.Message):
|
||||
__slots__ = ['posts']
|
||||
POSTS_FIELD_NUMBER: _ClassVar[int]
|
||||
posts: _containers.RepeatedCompositeFieldContainer[Post]
|
||||
|
||||
def __init__(self, posts: _Optional[_Iterable[_Union[Post, _Mapping]]]=...) -> None:
|
||||
...
|
||||
|
||||
class GetUserPostsRequest(_message.Message):
|
||||
__slots__ = ['user_id']
|
||||
USER_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
user_id: str
|
||||
|
||||
def __init__(self, user_id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class UserPosts(_message.Message):
|
||||
__slots__ = ['posts']
|
||||
POSTS_FIELD_NUMBER: _ClassVar[int]
|
||||
posts: _containers.RepeatedCompositeFieldContainer[Post]
|
||||
|
||||
def __init__(self, posts: _Optional[_Iterable[_Union[Post, _Mapping]]]=...) -> None:
|
||||
...
|
||||
|
||||
class GetPanelPostsRequest(_message.Message):
|
||||
__slots__ = ['panel_id']
|
||||
PANEL_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
panel_id: str
|
||||
|
||||
def __init__(self, panel_id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class PanelPosts(_message.Message):
|
||||
__slots__ = ['posts']
|
||||
POSTS_FIELD_NUMBER: _ClassVar[int]
|
||||
posts: _containers.RepeatedCompositeFieldContainer[Post]
|
||||
|
||||
def __init__(self, posts: _Optional[_Iterable[_Union[Post, _Mapping]]]=...) -> None:
|
||||
...
|
||||
|
||||
class PostEvent(_message.Message):
|
||||
__slots__ = ['type', 'data']
|
||||
TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
type: str
|
||||
data: Post
|
||||
|
||||
def __init__(self, type: _Optional[str]=..., data: _Optional[_Union[Post, _Mapping]]=...) -> None:
|
||||
...
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
|
||||
from . import post_pb2 as post__pb2
|
||||
|
||||
class PostServiceStub(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.CreatePost = channel.unary_unary('/panels.post.v1.PostService/CreatePost', request_serializer=post__pb2.CreatePostRequest.SerializeToString, response_deserializer=post__pb2.Post.FromString)
|
||||
self.GetPost = channel.unary_unary('/panels.post.v1.PostService/GetPost', request_serializer=post__pb2.GetPostRequest.SerializeToString, response_deserializer=post__pb2.Post.FromString)
|
||||
self.GetPanelPost = channel.unary_unary('/panels.post.v1.PostService/GetPanelPost', request_serializer=post__pb2.GetPanelPostRequest.SerializeToString, response_deserializer=post__pb2.Post.FromString)
|
||||
self.UpdatePost = channel.unary_unary('/panels.post.v1.PostService/UpdatePost', request_serializer=post__pb2.UpdatePostRequest.SerializeToString, response_deserializer=post__pb2.Post.FromString)
|
||||
self.DeletePost = channel.unary_unary('/panels.post.v1.PostService/DeletePost', request_serializer=post__pb2.DeletePostRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString)
|
||||
self.GetFeedPosts = channel.unary_unary('/panels.post.v1.PostService/GetFeedPosts', request_serializer=post__pb2.GetFeedPostsRequest.SerializeToString, response_deserializer=post__pb2.FeedPosts.FromString)
|
||||
self.GetUserPosts = channel.unary_unary('/panels.post.v1.PostService/GetUserPosts', request_serializer=post__pb2.GetUserPostsRequest.SerializeToString, response_deserializer=post__pb2.UserPosts.FromString)
|
||||
self.GetPanelPosts = channel.unary_unary('/panels.post.v1.PostService/GetPanelPosts', request_serializer=post__pb2.GetPanelPostsRequest.SerializeToString, response_deserializer=post__pb2.PanelPosts.FromString)
|
||||
|
||||
class PostServiceServicer(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def CreatePost(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetPost(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetPanelPost(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def UpdatePost(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def DeletePost(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetFeedPosts(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetUserPosts(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetPanelPosts(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def add_PostServiceServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {'CreatePost': grpc.unary_unary_rpc_method_handler(servicer.CreatePost, request_deserializer=post__pb2.CreatePostRequest.FromString, response_serializer=post__pb2.Post.SerializeToString), 'GetPost': grpc.unary_unary_rpc_method_handler(servicer.GetPost, request_deserializer=post__pb2.GetPostRequest.FromString, response_serializer=post__pb2.Post.SerializeToString), 'GetPanelPost': grpc.unary_unary_rpc_method_handler(servicer.GetPanelPost, request_deserializer=post__pb2.GetPanelPostRequest.FromString, response_serializer=post__pb2.Post.SerializeToString), 'UpdatePost': grpc.unary_unary_rpc_method_handler(servicer.UpdatePost, request_deserializer=post__pb2.UpdatePostRequest.FromString, response_serializer=post__pb2.Post.SerializeToString), 'DeletePost': grpc.unary_unary_rpc_method_handler(servicer.DeletePost, request_deserializer=post__pb2.DeletePostRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString), 'GetFeedPosts': grpc.unary_unary_rpc_method_handler(servicer.GetFeedPosts, request_deserializer=post__pb2.GetFeedPostsRequest.FromString, response_serializer=post__pb2.FeedPosts.SerializeToString), 'GetUserPosts': grpc.unary_unary_rpc_method_handler(servicer.GetUserPosts, request_deserializer=post__pb2.GetUserPostsRequest.FromString, response_serializer=post__pb2.UserPosts.SerializeToString), 'GetPanelPosts': grpc.unary_unary_rpc_method_handler(servicer.GetPanelPosts, request_deserializer=post__pb2.GetPanelPostsRequest.FromString, response_serializer=post__pb2.PanelPosts.SerializeToString)}
|
||||
generic_handler = grpc.method_handlers_generic_handler('panels.post.v1.PostService', rpc_method_handlers)
|
||||
server.add_generic_rpc_handlers((generic_handler,))
|
||||
|
||||
class PostService(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
@staticmethod
|
||||
def CreatePost(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/CreatePost', post__pb2.CreatePostRequest.SerializeToString, post__pb2.Post.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetPost(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/GetPost', post__pb2.GetPostRequest.SerializeToString, post__pb2.Post.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetPanelPost(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/GetPanelPost', post__pb2.GetPanelPostRequest.SerializeToString, post__pb2.Post.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def UpdatePost(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/UpdatePost', post__pb2.UpdatePostRequest.SerializeToString, post__pb2.Post.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def DeletePost(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/DeletePost', post__pb2.DeletePostRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetFeedPosts(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/GetFeedPosts', post__pb2.GetFeedPostsRequest.SerializeToString, post__pb2.FeedPosts.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetUserPosts(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/GetUserPosts', post__pb2.GetUserPostsRequest.SerializeToString, post__pb2.UserPosts.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetPanelPosts(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.post.v1.PostService/GetPanelPosts', post__pb2.GetPanelPostsRequest.SerializeToString, post__pb2.PanelPosts.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
|
||||
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nuser.proto\x12\x0epanels.user.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto"\x96\x01\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x10\n\x08is_admin\x18\x03 \x01(\x08\x12.\n\ncreated_at\x18\x04 \x01(\x0b2\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x05 \x01(\x0b2\x1a.google.protobuf.Timestamp"1\n\x0bUserMutable\x12\x15\n\x08username\x18\x01 \x01(\tH\x00\x88\x01\x01B\x0b\n\t_username">\n\x11CreateUserRequest\x12)\n\x04data\x18\x01 \x01(\x0b2\x1b.panels.user.v1.UserMutable" \n\x12GetUserByIdRequest\x12\n\n\x02id\x18\x01 \x01(\t"(\n\x14GetUserByNameRequest\x12\x10\n\x08username\x18\x01 \x01(\t"N\n\x15UpdateUserByIdRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12)\n\x04data\x18\x02 \x01(\x0b2\x1b.panels.user.v1.UserMutable"V\n\x17UpdateUserByNameRequest\x12\x10\n\x08username\x18\x01 \x01(\t\x12)\n\x04data\x18\x02 \x01(\x0b2\x1b.panels.user.v1.UserMutable"#\n\x15DeleteUserByIdRequest\x12\n\n\x02id\x18\x01 \x01(\t"+\n\x17DeleteUserByNameRequest\x12\x10\n\x08username\x18\x01 \x01(\t"=\n\tUserEvent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12"\n\x04data\x18\x02 \x01(\x0b2\x14.panels.user.v1.User2\xb4\x04\n\x0bUserService\x12G\n\nCreateUser\x12!.panels.user.v1.CreateUserRequest\x1a\x14.panels.user.v1.User"\x00\x12E\n\x07GetUser\x12".panels.user.v1.GetUserByIdRequest\x1a\x14.panels.user.v1.User"\x00\x12M\n\rGetUserByName\x12$.panels.user.v1.GetUserByNameRequest\x1a\x14.panels.user.v1.User"\x00\x12K\n\nUpdateUser\x12%.panels.user.v1.UpdateUserByIdRequest\x1a\x14.panels.user.v1.User"\x00\x12S\n\x10UpdateUserByName\x12\'.panels.user.v1.UpdateUserByNameRequest\x1a\x14.panels.user.v1.User"\x00\x12M\n\nDeleteUser\x12%.panels.user.v1.DeleteUserByIdRequest\x1a\x16.google.protobuf.Empty"\x00\x12U\n\x10DeleteUserByName\x12\'.panels.user.v1.DeleteUserByNameRequest\x1a\x16.google.protobuf.Empty"\x00b\x06proto3')
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'user_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
_globals['_USER']._serialized_start = 93
|
||||
_globals['_USER']._serialized_end = 243
|
||||
_globals['_USERMUTABLE']._serialized_start = 245
|
||||
_globals['_USERMUTABLE']._serialized_end = 294
|
||||
_globals['_CREATEUSERREQUEST']._serialized_start = 296
|
||||
_globals['_CREATEUSERREQUEST']._serialized_end = 358
|
||||
_globals['_GETUSERBYIDREQUEST']._serialized_start = 360
|
||||
_globals['_GETUSERBYIDREQUEST']._serialized_end = 392
|
||||
_globals['_GETUSERBYNAMEREQUEST']._serialized_start = 394
|
||||
_globals['_GETUSERBYNAMEREQUEST']._serialized_end = 434
|
||||
_globals['_UPDATEUSERBYIDREQUEST']._serialized_start = 436
|
||||
_globals['_UPDATEUSERBYIDREQUEST']._serialized_end = 514
|
||||
_globals['_UPDATEUSERBYNAMEREQUEST']._serialized_start = 516
|
||||
_globals['_UPDATEUSERBYNAMEREQUEST']._serialized_end = 602
|
||||
_globals['_DELETEUSERBYIDREQUEST']._serialized_start = 604
|
||||
_globals['_DELETEUSERBYIDREQUEST']._serialized_end = 639
|
||||
_globals['_DELETEUSERBYNAMEREQUEST']._serialized_start = 641
|
||||
_globals['_DELETEUSERBYNAMEREQUEST']._serialized_end = 684
|
||||
_globals['_USEREVENT']._serialized_start = 686
|
||||
_globals['_USEREVENT']._serialized_end = 747
|
||||
_globals['_USERSERVICE']._serialized_start = 750
|
||||
_globals['_USERSERVICE']._serialized_end = 1314
|
||||
@@ -0,0 +1,100 @@
|
||||
from google.protobuf import empty_pb2 as _empty_pb2
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class User(_message.Message):
|
||||
__slots__ = ['id', 'username', 'is_admin', 'created_at', 'updated_at']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
USERNAME_FIELD_NUMBER: _ClassVar[int]
|
||||
IS_ADMIN_FIELD_NUMBER: _ClassVar[int]
|
||||
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
UPDATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
username: str
|
||||
is_admin: bool
|
||||
created_at: _timestamp_pb2.Timestamp
|
||||
updated_at: _timestamp_pb2.Timestamp
|
||||
|
||||
def __init__(self, id: _Optional[str]=..., username: _Optional[str]=..., is_admin: bool=..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]]=..., updated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class UserMutable(_message.Message):
|
||||
__slots__ = ['username']
|
||||
USERNAME_FIELD_NUMBER: _ClassVar[int]
|
||||
username: str
|
||||
|
||||
def __init__(self, username: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class CreateUserRequest(_message.Message):
|
||||
__slots__ = ['data']
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
data: UserMutable
|
||||
|
||||
def __init__(self, data: _Optional[_Union[UserMutable, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class GetUserByIdRequest(_message.Message):
|
||||
__slots__ = ['id']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
|
||||
def __init__(self, id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class GetUserByNameRequest(_message.Message):
|
||||
__slots__ = ['username']
|
||||
USERNAME_FIELD_NUMBER: _ClassVar[int]
|
||||
username: str
|
||||
|
||||
def __init__(self, username: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class UpdateUserByIdRequest(_message.Message):
|
||||
__slots__ = ['id', 'data']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
data: UserMutable
|
||||
|
||||
def __init__(self, id: _Optional[str]=..., data: _Optional[_Union[UserMutable, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class UpdateUserByNameRequest(_message.Message):
|
||||
__slots__ = ['username', 'data']
|
||||
USERNAME_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
username: str
|
||||
data: UserMutable
|
||||
|
||||
def __init__(self, username: _Optional[str]=..., data: _Optional[_Union[UserMutable, _Mapping]]=...) -> None:
|
||||
...
|
||||
|
||||
class DeleteUserByIdRequest(_message.Message):
|
||||
__slots__ = ['id']
|
||||
ID_FIELD_NUMBER: _ClassVar[int]
|
||||
id: str
|
||||
|
||||
def __init__(self, id: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class DeleteUserByNameRequest(_message.Message):
|
||||
__slots__ = ['username']
|
||||
USERNAME_FIELD_NUMBER: _ClassVar[int]
|
||||
username: str
|
||||
|
||||
def __init__(self, username: _Optional[str]=...) -> None:
|
||||
...
|
||||
|
||||
class UserEvent(_message.Message):
|
||||
__slots__ = ['type', 'data']
|
||||
TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
DATA_FIELD_NUMBER: _ClassVar[int]
|
||||
type: str
|
||||
data: User
|
||||
|
||||
def __init__(self, type: _Optional[str]=..., data: _Optional[_Union[User, _Mapping]]=...) -> None:
|
||||
...
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
|
||||
from . import user_pb2 as user__pb2
|
||||
|
||||
class UserServiceStub(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.CreateUser = channel.unary_unary('/panels.user.v1.UserService/CreateUser', request_serializer=user__pb2.CreateUserRequest.SerializeToString, response_deserializer=user__pb2.User.FromString)
|
||||
self.GetUser = channel.unary_unary('/panels.user.v1.UserService/GetUser', request_serializer=user__pb2.GetUserByIdRequest.SerializeToString, response_deserializer=user__pb2.User.FromString)
|
||||
self.GetUserByName = channel.unary_unary('/panels.user.v1.UserService/GetUserByName', request_serializer=user__pb2.GetUserByNameRequest.SerializeToString, response_deserializer=user__pb2.User.FromString)
|
||||
self.UpdateUser = channel.unary_unary('/panels.user.v1.UserService/UpdateUser', request_serializer=user__pb2.UpdateUserByIdRequest.SerializeToString, response_deserializer=user__pb2.User.FromString)
|
||||
self.UpdateUserByName = channel.unary_unary('/panels.user.v1.UserService/UpdateUserByName', request_serializer=user__pb2.UpdateUserByNameRequest.SerializeToString, response_deserializer=user__pb2.User.FromString)
|
||||
self.DeleteUser = channel.unary_unary('/panels.user.v1.UserService/DeleteUser', request_serializer=user__pb2.DeleteUserByIdRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString)
|
||||
self.DeleteUserByName = channel.unary_unary('/panels.user.v1.UserService/DeleteUserByName', request_serializer=user__pb2.DeleteUserByNameRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString)
|
||||
|
||||
class UserServiceServicer(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def CreateUser(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetUser(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetUserByName(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def UpdateUser(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def UpdateUserByName(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def DeleteUser(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def DeleteUserByName(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def add_UserServiceServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {'CreateUser': grpc.unary_unary_rpc_method_handler(servicer.CreateUser, request_deserializer=user__pb2.CreateUserRequest.FromString, response_serializer=user__pb2.User.SerializeToString), 'GetUser': grpc.unary_unary_rpc_method_handler(servicer.GetUser, request_deserializer=user__pb2.GetUserByIdRequest.FromString, response_serializer=user__pb2.User.SerializeToString), 'GetUserByName': grpc.unary_unary_rpc_method_handler(servicer.GetUserByName, request_deserializer=user__pb2.GetUserByNameRequest.FromString, response_serializer=user__pb2.User.SerializeToString), 'UpdateUser': grpc.unary_unary_rpc_method_handler(servicer.UpdateUser, request_deserializer=user__pb2.UpdateUserByIdRequest.FromString, response_serializer=user__pb2.User.SerializeToString), 'UpdateUserByName': grpc.unary_unary_rpc_method_handler(servicer.UpdateUserByName, request_deserializer=user__pb2.UpdateUserByNameRequest.FromString, response_serializer=user__pb2.User.SerializeToString), 'DeleteUser': grpc.unary_unary_rpc_method_handler(servicer.DeleteUser, request_deserializer=user__pb2.DeleteUserByIdRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString), 'DeleteUserByName': grpc.unary_unary_rpc_method_handler(servicer.DeleteUserByName, request_deserializer=user__pb2.DeleteUserByNameRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString)}
|
||||
generic_handler = grpc.method_handlers_generic_handler('panels.user.v1.UserService', rpc_method_handlers)
|
||||
server.add_generic_rpc_handlers((generic_handler,))
|
||||
|
||||
class UserService(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
@staticmethod
|
||||
def CreateUser(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.user.v1.UserService/CreateUser', user__pb2.CreateUserRequest.SerializeToString, user__pb2.User.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetUser(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.user.v1.UserService/GetUser', user__pb2.GetUserByIdRequest.SerializeToString, user__pb2.User.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def GetUserByName(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.user.v1.UserService/GetUserByName', user__pb2.GetUserByNameRequest.SerializeToString, user__pb2.User.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def UpdateUser(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.user.v1.UserService/UpdateUser', user__pb2.UpdateUserByIdRequest.SerializeToString, user__pb2.User.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def UpdateUserByName(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.user.v1.UserService/UpdateUserByName', user__pb2.UpdateUserByNameRequest.SerializeToString, user__pb2.User.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def DeleteUser(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.user.v1.UserService/DeleteUser', user__pb2.DeleteUserByIdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
|
||||
@staticmethod
|
||||
def DeleteUserByName(request, target, options=(), channel_credentials=None, call_credentials=None, insecure=False, compression=None, wait_for_ready=None, timeout=None, metadata=None):
|
||||
return grpc.experimental.unary_unary(request, target, '/panels.user.v1.UserService/DeleteUserByName', user__pb2.DeleteUserByNameRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||
97
services/comment-service/comment_service/models/service.py
Normal file
97
services/comment-service/comment_service/models/service.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from google.protobuf import timestamp_pb2
|
||||
|
||||
from comment_service.models.proto import comment_pb2
|
||||
from comment_service.models.exceptions import ServiceException, ServiceErrorCode
|
||||
|
||||
|
||||
# Validators
|
||||
def is_valid_comment_msg(message: str) -> bool:
|
||||
if len(message) < 3 or len(message) > 512:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# Service Models
|
||||
class Comment(BaseModel):
|
||||
id: int
|
||||
|
||||
post_id: str
|
||||
author_id: str
|
||||
message: str
|
||||
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
@classmethod
|
||||
def to_protobuf(cls, comment: "Comment") -> comment_pb2.Comment:
|
||||
created_at = timestamp_pb2.Timestamp()
|
||||
created_at.FromDatetime(comment.created_at)
|
||||
|
||||
updated_at = None
|
||||
if comment.updated_at is not None:
|
||||
updated_at = timestamp_pb2.Timestamp()
|
||||
updated_at.FromDatetime(comment.updated_at)
|
||||
|
||||
return comment_pb2.Comment(
|
||||
id=str(comment.id),
|
||||
post_id=comment.post_id,
|
||||
author_id=comment.author_id,
|
||||
message=comment.message,
|
||||
|
||||
created_at=created_at,
|
||||
updated_at=updated_at,
|
||||
)
|
||||
|
||||
|
||||
class CommentCreate(BaseModel):
|
||||
post_id: str
|
||||
author_id: str
|
||||
message: str # todo: validation on message (wrap validator for is_valid_comment_msg)
|
||||
|
||||
@classmethod
|
||||
def from_protobuf(cls, request: comment_pb2.CreateCommentRequest) -> "CommentCreate":
|
||||
return cls(
|
||||
post_id=request.post_id,
|
||||
author_id=request.author_id,
|
||||
message=request.data.message,
|
||||
)
|
||||
|
||||
|
||||
class CommentUpdate(BaseModel):
|
||||
message: Optional[str] = None # todo: validation on message (if set use validator is_valid_comment_msg)
|
||||
|
||||
@classmethod
|
||||
def from_protobuf(cls, request: comment_pb2.UpdateCommentRequest) -> "CommentUpdate":
|
||||
return cls(
|
||||
message=request.data.message
|
||||
)
|
||||
|
||||
|
||||
# Repository Interfaces
|
||||
class CommentRepository:
|
||||
async def get_comment(self, comment_id: int) -> Comment:
|
||||
raise ServiceException("unimplemented internal repository method", ServiceErrorCode.SERVICE_ERROR)
|
||||
|
||||
async def get_post_comments(self, post_id: str) -> List[Comment]:
|
||||
raise ServiceException("unimplemented internal repository method", ServiceErrorCode.SERVICE_ERROR)
|
||||
|
||||
async def create_comment(self, data: CommentCreate) -> Comment:
|
||||
raise ServiceException("unimplemented internal repository method", ServiceErrorCode.SERVICE_ERROR)
|
||||
|
||||
async def update_comment(self, comment_id: int, data: CommentUpdate) -> Comment:
|
||||
raise ServiceException("unimplemented internal repository method", ServiceErrorCode.SERVICE_ERROR)
|
||||
|
||||
async def delete_comment(self, comment_id: int) -> None:
|
||||
raise ServiceException("unimplemented internal repository method", ServiceErrorCode.SERVICE_ERROR)
|
||||
|
||||
|
||||
class CommentDBRepository(CommentRepository):
|
||||
async def delete_post_comments(self, post_id: str) -> None:
|
||||
raise ServiceException("unimplemented internal repository method", ServiceErrorCode.SERVICE_ERROR)
|
||||
|
||||
async def delete_user_comments(self, user_id: str) -> None:
|
||||
raise ServiceException("unimplemented internal repository method", ServiceErrorCode.SERVICE_ERROR)
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS comments CASCADE;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE comments (
|
||||
"id" serial PRIMARY KEY,
|
||||
|
||||
"post_id" varchar(64) NOT NULL,
|
||||
"author_id" varchar(64) NOT NULL,
|
||||
|
||||
"message" varchar(512) NOT NULL,
|
||||
|
||||
"created_at" timestamp NOT NULL DEFAULT timezone('utc', now()),
|
||||
"updated_at" timestamp
|
||||
);
|
||||
@@ -0,0 +1,94 @@
|
||||
from typing import List
|
||||
|
||||
from databases import Database
|
||||
|
||||
from comment_service.events.producer import CommentEventProducer
|
||||
from comment_service.models.exceptions import ServiceException, ServiceErrorCode
|
||||
from comment_service.models.service import CommentDBRepository, Comment, CommentCreate, CommentUpdate
|
||||
|
||||
|
||||
class ServiceDBRepository(CommentDBRepository):
|
||||
"""Database repository responsible for actions
|
||||
relating to the postgres database.
|
||||
|
||||
This repository will be utilised by other upstream repositories
|
||||
or the Kafka event consumers.
|
||||
|
||||
Attributes:
|
||||
_db (Database): The postgres database connection handler.
|
||||
_event_prod (CommentEventProducer): Used to dispatch events upon execution of a CRUD action.
|
||||
|
||||
"""
|
||||
def __init__(self, db: Database, event_producer: CommentEventProducer) -> None:
|
||||
self._db = db
|
||||
self._event_prod = event_producer
|
||||
|
||||
def _result_to_comment(self, result) -> Comment:
|
||||
return Comment(
|
||||
id=result.id,
|
||||
post_id=result.post_id,
|
||||
author_id=result.author_id,
|
||||
message=result.message,
|
||||
created_at=result.created_at,
|
||||
updated_at=result._mapping.get("updated_at", None)
|
||||
)
|
||||
|
||||
async def get_post_comments(self, post_id: str) -> List[Comment]:
|
||||
query = "SELECT id, post_id, author_id, message, created_at, updated_at FROM comments WHERE post_id = :post_id"
|
||||
rows = await self._db.fetch_all(query=query, values={"post_id": post_id})
|
||||
return [self._result_to_comment(result) for result in rows]
|
||||
|
||||
async def create_comment(self, data: CommentCreate) -> Comment:
|
||||
query = "INSERT INTO comments (post_id, author_id, message) VALUES (:post_id, :author_id, :message) RETURNING id"
|
||||
result = await self._db.execute(query=query, values={"post_id": data.post_id, "author_id": data.author_id, "message": data.message})
|
||||
|
||||
comment = await self.get_comment(result)
|
||||
await self._event_prod.send_created_event(comment)
|
||||
return comment
|
||||
|
||||
async def update_comment(self, comment_id: int, data: CommentUpdate) -> Comment:
|
||||
query = "UPDATE comments SET message = :message, updated_at = now() WHERE id = :comment_id"
|
||||
await self._db.execute(query=query, values={"message": data.message, "comment_id": comment_id})
|
||||
|
||||
comment = await self.get_comment(comment_id)
|
||||
await self._event_prod.send_updated_event(comment)
|
||||
return comment
|
||||
|
||||
async def delete_comment(self, comment_id: int) -> None:
|
||||
comment = await self.get_comment(comment_id)
|
||||
|
||||
query = "DELETE FROM comments WHERE id = :comment_id"
|
||||
await self._db.execute(query=query, values={"comment_id": comment_id})
|
||||
|
||||
await self._event_prod.send_deleted_event(comment)
|
||||
|
||||
async def get_comment(self, comment_id: int) -> Comment:
|
||||
query = "SELECT id, post_id, author_id, message, created_at, updated_at FROM comments WHERE id = :comment_id"
|
||||
result = await self._db.fetch_one(query=query, values={"comment_id": comment_id})
|
||||
if result is None:
|
||||
raise ServiceException(message="no comment found", error_code=ServiceErrorCode.NOT_FOUND)
|
||||
|
||||
return self._result_to_comment(result)
|
||||
|
||||
async def delete_post_comments(self, post_id: str) -> None:
|
||||
comments = await self.get_post_comments(post_id)
|
||||
|
||||
query = "DELETE FROM comments WHERE post_id = :post_id"
|
||||
await self._db.execute(query=query, values={"post_id": post_id})
|
||||
|
||||
for comment in comments:
|
||||
await self._event_prod.send_deleted_event(comment)
|
||||
|
||||
async def delete_user_comments(self, user_id: str) -> None:
|
||||
comments = await self.get_post_comments(user_id)
|
||||
|
||||
query = "DELETE FROM comments WHERE author_id = :author_id"
|
||||
await self._db.execute(query=query, values={"author_id": user_id})
|
||||
|
||||
for comment in comments:
|
||||
await self._event_prod.send_deleted_event(comment)
|
||||
|
||||
async def _get_user_comments(self, user_id: str) -> List[Comment]:
|
||||
query = "SELECT id, post_id, author_id, message, created_at, updated_at FROM comments WHERE author_id = :user_id"
|
||||
rows = await self._db.fetch_all(query=query, values={"user_id": user_id})
|
||||
return [self._result_to_comment(result) for result in rows]
|
||||
41
services/comment-service/comment_service/postgres/service.py
Normal file
41
services/comment-service/comment_service/postgres/service.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import logging
|
||||
|
||||
from databases import Database
|
||||
|
||||
from comment_service.models.config import Config
|
||||
from comment_service.events.producer import CommentEventProducer
|
||||
from comment_service.postgres.repository import ServiceDBRepository
|
||||
|
||||
|
||||
async def connect_database(config: Config) -> Database:
|
||||
"""Opens a connection to the database.
|
||||
|
||||
Args:
|
||||
config (Config): The app configuration.
|
||||
|
||||
Returns:
|
||||
A connected databases.Database instance
|
||||
|
||||
"""
|
||||
db = Database(config.postgres_dsn)
|
||||
try:
|
||||
await db.connect()
|
||||
except Exception:
|
||||
logging.error("failed to connect to postgresql database")
|
||||
raise
|
||||
|
||||
return db
|
||||
|
||||
|
||||
async def create_db_repository(config: Config, event_producer: CommentEventProducer) -> ServiceDBRepository:
|
||||
"""Create the database repository.
|
||||
|
||||
Open a database connection and instantialise the
|
||||
database repository.
|
||||
|
||||
Returns:
|
||||
ServiceDBRepository
|
||||
|
||||
"""
|
||||
db = await connect_database(config)
|
||||
return ServiceDBRepository(db, event_producer)
|
||||
76
services/comment-service/comment_service/redis/repository.py
Normal file
76
services/comment-service/comment_service/redis/repository.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import pickle
|
||||
import logging
|
||||
from typing import Type, List
|
||||
|
||||
import redis.asyncio as redis
|
||||
|
||||
from comment_service.models.service import CommentRepository, Comment, CommentCreate, CommentUpdate
|
||||
|
||||
|
||||
class ServiceRedisRepository(CommentRepository):
|
||||
"""The Redis repository is responsible for caching
|
||||
requests to help reduce the amount of database calls,
|
||||
allowing for faster data access.
|
||||
|
||||
If the Redis repository does not have the data cached,
|
||||
or does not cache data for that request, then the call is
|
||||
passed downstream to the database repository.
|
||||
|
||||
Attributes:
|
||||
_conn (redis.asyncio.redis.Redis): The Redis connection.
|
||||
_repo (CommentRepository): The next downstream repository (the DB repo).
|
||||
|
||||
"""
|
||||
def __init__(self, redis_conn: redis.Redis, downstream_repo: Type[CommentRepository]) -> None:
|
||||
self._conn = redis_conn
|
||||
self._repo = downstream_repo
|
||||
|
||||
async def _purge_cached_comments(self, post_id: str) -> None:
|
||||
try:
|
||||
self._conn.delete(post_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def get_comment(self, comment_id: int) -> Comment:
|
||||
return await self._repo.get_comment(comment_id)
|
||||
|
||||
async def get_post_comments(self, post_id: str) -> List[Comment]:
|
||||
post_id = post_id.lower()
|
||||
|
||||
failure = False
|
||||
try:
|
||||
# check for a cached version of the post comments
|
||||
response = await self._conn.get(post_id)
|
||||
if response is not None:
|
||||
comments = pickle.loads(response)
|
||||
assert type(comments) == list
|
||||
return comments
|
||||
except Exception as e:
|
||||
failure = True
|
||||
logging.error("Redis Repo: error whilst getting post comments", e)
|
||||
pass
|
||||
|
||||
comments = await self._repo.get_post_comments(post_id)
|
||||
if failure is False:
|
||||
# cache the retrieved comments
|
||||
try:
|
||||
await self._conn.set(post_id, pickle.dumps(comments), ex=120) # TTL: 2 minutes
|
||||
except Exception:
|
||||
logging.error("Redis Repo: error whilst caching post comments", e)
|
||||
pass
|
||||
|
||||
return comments
|
||||
|
||||
async def create_comment(self, data: CommentCreate) -> Comment:
|
||||
comment = await self._repo.create_comment(data)
|
||||
await self._purge_cached_comments(comment.post_id)
|
||||
return comment
|
||||
|
||||
async def update_comment(self, comment_id: int, data: CommentUpdate) -> Comment:
|
||||
comment = await self._repo.update_comment(comment_id, data)
|
||||
await self._purge_cached_comments(comment.post_id)
|
||||
return comment
|
||||
|
||||
async def delete_comment(self, comment_id: int) -> None:
|
||||
# todo: purge cache of comments on post (instead of waiting for TTL expiry)
|
||||
await self._repo.delete_comment(comment_id)
|
||||
48
services/comment-service/comment_service/redis/service.py
Normal file
48
services/comment-service/comment_service/redis/service.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from typing import Type
|
||||
|
||||
import redis.asyncio as redis
|
||||
|
||||
from comment_service.models.config import Config
|
||||
from comment_service.models.service import CommentRepository
|
||||
from comment_service.redis.repository import ServiceRedisRepository
|
||||
|
||||
|
||||
async def connect_redis(config: Config) -> redis.Redis:
|
||||
"""Opens a connection to Redis.
|
||||
|
||||
Args:
|
||||
config (Config): The app configuration.
|
||||
|
||||
Returns:
|
||||
A connected redis.Redis instance
|
||||
|
||||
"""
|
||||
host, port = config.redis_host, 5432
|
||||
try:
|
||||
host, port = config.redis_host.split(":")
|
||||
port = int(port)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
conn = redis.Redis(host=host, port=port, password=config.redis_pass)
|
||||
await conn.ping()
|
||||
return conn
|
||||
|
||||
|
||||
async def create_redis_repository(config: Config, downstream_repo: Type[CommentRepository]) -> ServiceRedisRepository:
|
||||
"""Create the Redis repository.
|
||||
|
||||
Open a Redis connection and instantialise the
|
||||
Redis repository.
|
||||
|
||||
Args:
|
||||
downstream_repo (Type[CommentRepository]): The next downstream repository
|
||||
that will be called by the Redis repository. (in this case the database
|
||||
repository)
|
||||
|
||||
Returns:
|
||||
ServiceRedisRepository
|
||||
|
||||
"""
|
||||
redis_conn = await connect_redis(config)
|
||||
return ServiceRedisRepository(redis_conn=redis_conn, downstream_repo=downstream_repo)
|
||||
289
services/comment-service/comment_service/rpc/comment.py
Normal file
289
services/comment-service/comment_service/rpc/comment.py
Normal file
@@ -0,0 +1,289 @@
|
||||
import logging
|
||||
import traceback
|
||||
from typing import Type
|
||||
|
||||
from google.protobuf import empty_pb2
|
||||
from grpc import RpcContext, StatusCode
|
||||
|
||||
from comment_service.models.exceptions import ServiceException
|
||||
from comment_service.models.service import CommentRepository, Comment, CommentCreate, CommentUpdate
|
||||
from comment_service.models.proto import comment_pb2, comment_pb2_grpc
|
||||
|
||||
|
||||
class CommentServicer(comment_pb2_grpc.CommentServiceServicer):
|
||||
"""Contains definitions for the service's RPC methods.
|
||||
|
||||
Requests are converted from protobuf to business model
|
||||
form, then directed to the service repository, where the
|
||||
response is then translated back to protobuf.
|
||||
|
||||
Attributes:
|
||||
_svc_repo (Type[CommentRepository]): The highest level service repository.
|
||||
|
||||
"""
|
||||
def __init__(self, svc_repo: Type[CommentRepository]) -> None:
|
||||
self._svc_repo = svc_repo
|
||||
|
||||
def _apply_error(self, context: RpcContext, code: StatusCode, msg: str) -> None:
|
||||
"""Apply an error to a given RPC context.
|
||||
|
||||
Args:
|
||||
context (grpc.RpcContext): The context to apply the error to.
|
||||
code (grpc.StatusCode): The gRPC status code.
|
||||
msg (str): The error details.
|
||||
|
||||
"""
|
||||
context.set_code(code)
|
||||
context.set_details(msg)
|
||||
|
||||
def _apply_unknown_error(self, context: RpcContext) -> None:
|
||||
"""Apply a de facto error fallback message.
|
||||
|
||||
Args:
|
||||
context (grpc.RpcContext): The context to apply the error to.
|
||||
|
||||
"""
|
||||
self._apply_error(context, StatusCode.UNKNOWN, "unknown error occured")
|
||||
|
||||
async def CreateComment(self, request: comment_pb2.CreateCommentRequest, context: RpcContext) -> comment_pb2.Comment:
|
||||
"""CreateComment RPC Call
|
||||
|
||||
Args:
|
||||
request (comment_pb2.CreateCommentRequest): The request parameters.
|
||||
context (grpc.RpcContext): The context of the RPC call.
|
||||
|
||||
Returns:
|
||||
comment_pb2.Comment: With a succesful comment creation.
|
||||
|
||||
"""
|
||||
# vaLidate the request inputs
|
||||
if request.post_id == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="post not provided"
|
||||
)
|
||||
return
|
||||
|
||||
if request.author_id == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="author not provided"
|
||||
)
|
||||
return
|
||||
|
||||
if request.data == None:
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="malformed request"
|
||||
)
|
||||
return
|
||||
|
||||
if request.data.message == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="comment message not provided"
|
||||
)
|
||||
return
|
||||
|
||||
# convert to service model from protobuf
|
||||
try:
|
||||
data = CommentCreate.from_protobuf(request)
|
||||
comment = await self._svc_repo.create_comment(data)
|
||||
except ServiceException as err:
|
||||
err.apply_to_rpc(context)
|
||||
return
|
||||
except Exception:
|
||||
logging.error(traceback.format_exc())
|
||||
self._apply_unknown_error(context)
|
||||
return
|
||||
|
||||
# convert comment to protobuf form
|
||||
return Comment.to_protobuf(comment)
|
||||
|
||||
async def UpdateComment(self, request: comment_pb2.UpdateCommentRequest, context: RpcContext) -> comment_pb2.Comment:
|
||||
"""UpdateComment RPC Call
|
||||
|
||||
Args:
|
||||
request (comment_pb2.UpdateCommentRequest): The request parameters.
|
||||
context (grpc.RpcContext): The context of the RPC call.
|
||||
|
||||
Returns:
|
||||
comment_pb2.Comment: The updated comment details (if succesfully updated).
|
||||
|
||||
"""
|
||||
# vaLidate the request inputs
|
||||
if request.id == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="comment not provided"
|
||||
)
|
||||
return
|
||||
elif not request.id.isnumeric():
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="invalid comment id provided"
|
||||
)
|
||||
return
|
||||
|
||||
if request.data == None:
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="malformed request"
|
||||
)
|
||||
return
|
||||
|
||||
if request.data.message == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="comment message not provided"
|
||||
)
|
||||
return
|
||||
|
||||
# convert to service model from protobuf
|
||||
try:
|
||||
comment_id = int(request.id)
|
||||
data = CommentUpdate.from_protobuf(request)
|
||||
comment = await self._svc_repo.update_comment(comment_id, data)
|
||||
except ServiceException as err:
|
||||
err.apply_to_rpc(context)
|
||||
return
|
||||
except Exception:
|
||||
logging.error(traceback.format_exc())
|
||||
self._apply_unknown_error(context)
|
||||
return
|
||||
|
||||
# convert comment to protobuf form
|
||||
return Comment.to_protobuf(comment)
|
||||
|
||||
async def DeleteComment(self, request: comment_pb2.DeleteCommentRequest, context: RpcContext) -> empty_pb2.Empty:
|
||||
"""DeleteComment RPC Call
|
||||
|
||||
Args:
|
||||
request (comment_pb2.DeleteCommentRequest): The request parameters.
|
||||
context (grpc.RpcContext): The context of the RPC call.
|
||||
|
||||
Returns:
|
||||
empty_pb2.Empty: Empty protobuf response (in effect returns None).
|
||||
|
||||
"""
|
||||
# vaLidate the request inputs
|
||||
if request.id == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="comment not provided"
|
||||
)
|
||||
return
|
||||
|
||||
if not request.id.isnumeric():
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="invalid comment id provided"
|
||||
)
|
||||
return
|
||||
|
||||
# attempt to delete the comment
|
||||
try:
|
||||
comment_id = int(request.id)
|
||||
await self._svc_repo.delete_comment(comment_id)
|
||||
except ServiceException as err:
|
||||
err.apply_to_rpc(context)
|
||||
return
|
||||
except Exception:
|
||||
logging.error(traceback.format_exc())
|
||||
self._apply_unknown_error(context)
|
||||
return
|
||||
|
||||
return empty_pb2.Empty()
|
||||
|
||||
async def GetComment(self, request: comment_pb2.GetCommentRequest, context: RpcContext) -> comment_pb2.PostComments:
|
||||
"""GetComment RPC Call
|
||||
|
||||
Returns a comment by comment id.
|
||||
|
||||
Args:
|
||||
request (comment_pb2.GetCommentRequest): The request parameters.
|
||||
context (grpc.RpcContext): The context of the RPC call.
|
||||
|
||||
Returns:
|
||||
comment_pb2.Comment: The located comment
|
||||
|
||||
"""
|
||||
# vaLidate the request inputs
|
||||
if request.id == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="comment id not provided"
|
||||
)
|
||||
return
|
||||
|
||||
if not request.id.isnumeric():
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="invalid comment id provided"
|
||||
)
|
||||
return
|
||||
|
||||
# attempt to get the comment
|
||||
try:
|
||||
comment = await self._svc_repo.get_comment(int(request.id))
|
||||
except ServiceException as err:
|
||||
err.apply_to_rpc(context)
|
||||
return
|
||||
except Exception:
|
||||
logging.error(traceback.format_exc())
|
||||
self._apply_unknown_error(context)
|
||||
return
|
||||
|
||||
return Comment.to_protobuf(comment)
|
||||
|
||||
async def GetPostComments(self, request: comment_pb2.GetPostCommentsRequest, context: RpcContext) -> comment_pb2.PostComments:
|
||||
"""GetPostComments RPC Call
|
||||
|
||||
Returns a list of comments that a post has.
|
||||
|
||||
TODO:
|
||||
Implement pagination (?after=comment_id or some effect)
|
||||
to return more comments from a post.
|
||||
|
||||
Args:
|
||||
request (comment_pb2.UpdateCommentRequest): The request parameters.
|
||||
context (grpc.RpcContext): The context of the RPC call.
|
||||
|
||||
Returns:
|
||||
comment_pb2.PostComments: containing a list of the post's comments
|
||||
|
||||
"""
|
||||
# vaLidate the request inputs
|
||||
if request.post_id == "":
|
||||
self._apply_error(
|
||||
context,
|
||||
code=StatusCode.INVALID_ARGUMENT,
|
||||
msg="post id not provided"
|
||||
)
|
||||
return
|
||||
|
||||
# attempt to get the comments
|
||||
try:
|
||||
comments = await self._svc_repo.get_post_comments(request.post_id)
|
||||
except ServiceException as err:
|
||||
err.apply_to_rpc(context)
|
||||
return
|
||||
except Exception:
|
||||
logging.error(traceback.format_exc())
|
||||
self._apply_unknown_error(context)
|
||||
return
|
||||
|
||||
# convert to protobuf
|
||||
return comment_pb2.PostComments(comments=[Comment.to_protobuf(comment) for comment in comments])
|
||||
52
services/comment-service/comment_service/rpc/service.py
Normal file
52
services/comment-service/comment_service/rpc/service.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import logging
|
||||
from typing import Type
|
||||
|
||||
import grpc
|
||||
from grpc_health.v1 import health, health_pb2_grpc
|
||||
|
||||
from comment_service.rpc.comment import CommentServicer
|
||||
from comment_service.models.proto import comment_pb2_grpc
|
||||
from comment_service.models.service import CommentRepository
|
||||
|
||||
|
||||
class RPCServerWrapper:
|
||||
"""A wrapper class for the RPC server.
|
||||
|
||||
Attributes:
|
||||
_grpc_server (grpc.aio.Server): The gRPC server instance.
|
||||
|
||||
"""
|
||||
def __init__(self, svc_repo: Type[CommentRepository]) -> None:
|
||||
"""Creates the gRPC server and adds the servicers.
|
||||
|
||||
Args:
|
||||
svc_repo (Type[CommentRepository]): The service repository to pass to the servicers.
|
||||
|
||||
"""
|
||||
self._grpc_server = grpc.aio.server()
|
||||
self._grpc_server.add_insecure_port("[::]:9090")
|
||||
|
||||
comment_servicer = CommentServicer(svc_repo)
|
||||
comment_pb2_grpc.add_CommentServiceServicer_to_server(comment_servicer, self._grpc_server)
|
||||
|
||||
health_servicer = health.aio.HealthServicer()
|
||||
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, self._grpc_server)
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Begin serving RPC asynchronously."""
|
||||
logging.info("attempting to serve RPC...")
|
||||
await self._grpc_server.start()
|
||||
await self._grpc_server.wait_for_termination()
|
||||
|
||||
|
||||
def create_rpc_server(svc_repo: Type[CommentRepository]) -> RPCServerWrapper:
|
||||
"""Instantialise the RPC server wrapper.
|
||||
|
||||
Args:
|
||||
svc_repo (Type[CommentRepository]): The service repository for the RPC servicers to interface with.
|
||||
|
||||
Returns:
|
||||
RPCServerWrapper
|
||||
|
||||
"""
|
||||
return RPCServerWrapper(svc_repo)
|
||||
30
services/comment-service/comment_service/service.py
Normal file
30
services/comment-service/comment_service/service.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from typing import Type, List
|
||||
|
||||
from comment_service.models.service import CommentRepository, Comment, CommentCreate, CommentUpdate
|
||||
|
||||
|
||||
class ServiceRepository(CommentRepository):
|
||||
"""The comment service repository.
|
||||
|
||||
Attributes:
|
||||
_repo (Type[CommentRepository]): The downstream repository (Redis Repository -> DB Repository).
|
||||
|
||||
"""
|
||||
def __init__(self, downstream_repo: Type[CommentRepository]) -> None:
|
||||
self._repo = downstream_repo
|
||||
|
||||
async def get_comment(self, comment_id: int) -> Comment:
|
||||
return await self._repo.get_comment(comment_id)
|
||||
|
||||
async def get_post_comments(self, post_id: str) -> List[Comment]:
|
||||
# todo: pagination
|
||||
return await self._repo.get_post_comments(post_id)
|
||||
|
||||
async def create_comment(self, data: CommentCreate) -> Comment:
|
||||
return await self._repo.create_comment(data)
|
||||
|
||||
async def update_comment(self, comment_id: int, data: CommentUpdate) -> Comment:
|
||||
return await self._repo.update_comment(comment_id, data)
|
||||
|
||||
async def delete_comment(self, comment_id: int) -> None:
|
||||
await self._repo.delete_comment(comment_id)
|
||||
Reference in New Issue
Block a user