Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions api-gateway/main.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import os
from pprint import pprint
from typing import Annotated
from uuid import UUID

import grpc
import httpx
from fastapi import FastAPI, Request, HTTPException, Depends, APIRouter, Header
from fastapi import FastAPI, Request, HTTPException, Depends, APIRouter, Header, Path

import postservice_pb2
from postservice_pb2_grpc import PostServiceStub
from schemas import Post, PostCreate, PostUpdate, PostList, Comment, CommentList
import stats_pb2
from stats_pb2_grpc import StatsServiceStub
from schemas import Post, PostCreate, PostUpdate, PostList, Comment, CommentList, Counter, TimelineResponse, TopItem, \
TimelinePoint

app = FastAPI(title="API Gateway")

USER_SERVICE_URL = os.getenv("USER_SERVICE_ADDR")
POST_COMMENT_ADDR = os.getenv("POST_COMMENT_ADDR")
STATS_ADDR = os.getenv("STATS_SERVICE_ADDR", "stats_service:50051")
stats_channel = grpc.aio.insecure_channel(STATS_ADDR)
stats_stub = StatsServiceStub(stats_channel)

grpc_channel = grpc.aio.insecure_channel(POST_COMMENT_ADDR)
post_service_stub = PostServiceStub(grpc_channel)
Expand Down Expand Up @@ -261,6 +268,45 @@ async def view_post(
return {"status": "success"}


stats_router = APIRouter(prefix="/stats")


@stats_router.get("/posts/{post_id}", response_model=Counter)
async def post_counters(post_id: UUID):
resp = await stats_stub.GetPostCounters(stats_pb2.PostIdRequest(post_id=str(post_id)))
return Counter(
views=resp.views,
likes=resp.likes,
comments=resp.comments,
)


@stats_router.get("/posts/{post_id}/timeline/{metric}", response_model=TimelineResponse)
async def post_timeline(post_id: str, metric: str = Path(pattern="views|likes|comments")):
kind = {"views": 0, "likes": 1, "comments": 2}[metric]
resp = await stats_stub.GetPostTimeline(
stats_pb2.TimelineRequest(post_id=post_id, metric=kind)
)
return TimelineResponse(
points=[TimelinePoint(date=p.date, value=p.value) for p in resp.points]
)


@stats_router.get("/top/posts/{metric}", response_model=list[TopItem])
async def top_posts(metric: str = Path(pattern="views|likes|comments")):
kind = {"views": 0, "likes": 1, "comments": 2}[metric]
resp = await stats_stub.GetTopPosts(stats_pb2.TopPostsRequest(metric=kind))
return [TopItem(id=p.post_id, value=p.value) for p in resp.posts]


@stats_router.get("/top/users/{metric}", response_model=list[TopItem])
async def top_users(metric: str = Path(pattern="views|likes|comments")):
kind = {"views": 0, "likes": 1, "comments": 2}[metric]
resp = await stats_stub.GetTopUsers(stats_pb2.TopUsersRequest(metric=kind))
return [TopItem(id=u.user_id, value=u.value) for u in resp.users]


app.include_router(router, tags=["Posts"])
app.include_router(comments_router, tags=["Comments"])
app.include_router(actions_router, tags=["Actions"])
app.include_router(stats_router, tags=["Stats"])
19 changes: 18 additions & 1 deletion api-gateway/schemas.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, date
from typing import List

from pydantic import BaseModel, Field
Expand Down Expand Up @@ -44,3 +44,20 @@ class Comment(BaseModel):
class CommentList(BaseModel):
comments: List[Comment] = Field(..., description="Список комментариев")
total_count: int = Field(..., description="Общее количество комментариев")


class Counter(BaseModel):
views: int
likes: int
comments: int

class TimelinePoint(BaseModel):
date: date
value: int

class TimelineResponse(BaseModel):
points: list[TimelinePoint]

class TopItem(BaseModel):
id: str
value: int
60 changes: 60 additions & 0 deletions api-gateway/stats_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

226 changes: 226 additions & 0 deletions api-gateway/stats_pb2_grpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings

import stats_pb2 as stats__pb2

GRPC_GENERATED_VERSION = '1.71.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False

try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True

if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ f' but the generated code in stats_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)


class StatsServiceStub(object):
"""Missing associated documentation comment in .proto file."""

def __init__(self, channel):
"""Constructor.

Args:
channel: A grpc.Channel.
"""
self.GetPostCounters = channel.unary_unary(
'/stats.StatsService/GetPostCounters',
request_serializer=stats__pb2.PostIdRequest.SerializeToString,
response_deserializer=stats__pb2.PostCountersResponse.FromString,
_registered_method=True)
self.GetPostTimeline = channel.unary_unary(
'/stats.StatsService/GetPostTimeline',
request_serializer=stats__pb2.TimelineRequest.SerializeToString,
response_deserializer=stats__pb2.TimelineResponse.FromString,
_registered_method=True)
self.GetTopPosts = channel.unary_unary(
'/stats.StatsService/GetTopPosts',
request_serializer=stats__pb2.TopPostsRequest.SerializeToString,
response_deserializer=stats__pb2.TopPostsResponse.FromString,
_registered_method=True)
self.GetTopUsers = channel.unary_unary(
'/stats.StatsService/GetTopUsers',
request_serializer=stats__pb2.TopUsersRequest.SerializeToString,
response_deserializer=stats__pb2.TopUsersResponse.FromString,
_registered_method=True)


class StatsServiceServicer(object):
"""Missing associated documentation comment in .proto file."""

def GetPostCounters(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 GetPostTimeline(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 GetTopPosts(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 GetTopUsers(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_StatsServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
'GetPostCounters': grpc.unary_unary_rpc_method_handler(
servicer.GetPostCounters,
request_deserializer=stats__pb2.PostIdRequest.FromString,
response_serializer=stats__pb2.PostCountersResponse.SerializeToString,
),
'GetPostTimeline': grpc.unary_unary_rpc_method_handler(
servicer.GetPostTimeline,
request_deserializer=stats__pb2.TimelineRequest.FromString,
response_serializer=stats__pb2.TimelineResponse.SerializeToString,
),
'GetTopPosts': grpc.unary_unary_rpc_method_handler(
servicer.GetTopPosts,
request_deserializer=stats__pb2.TopPostsRequest.FromString,
response_serializer=stats__pb2.TopPostsResponse.SerializeToString,
),
'GetTopUsers': grpc.unary_unary_rpc_method_handler(
servicer.GetTopUsers,
request_deserializer=stats__pb2.TopUsersRequest.FromString,
response_serializer=stats__pb2.TopUsersResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'stats.StatsService', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('stats.StatsService', rpc_method_handlers)


# This class is part of an EXPERIMENTAL API.
class StatsService(object):
"""Missing associated documentation comment in .proto file."""

@staticmethod
def GetPostCounters(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,
'/stats.StatsService/GetPostCounters',
stats__pb2.PostIdRequest.SerializeToString,
stats__pb2.PostCountersResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)

@staticmethod
def GetPostTimeline(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,
'/stats.StatsService/GetPostTimeline',
stats__pb2.TimelineRequest.SerializeToString,
stats__pb2.TimelineResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)

@staticmethod
def GetTopPosts(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,
'/stats.StatsService/GetTopPosts',
stats__pb2.TopPostsRequest.SerializeToString,
stats__pb2.TopPostsResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)

@staticmethod
def GetTopUsers(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,
'/stats.StatsService/GetTopUsers',
stats__pb2.TopUsersRequest.SerializeToString,
stats__pb2.TopUsersResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
Loading