Define Server API scheme
API contains optional endpoints defined by list of Intents
This commit is contained in:
@@ -11,10 +11,21 @@ python3 -m pip install "uvicorn[standard]"
|
|||||||
After importing an app such as `main.py`:
|
After importing an app such as `main.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from typing import List
|
||||||
from src.vue_apps_py.security import SecurityHelper
|
from src.vue_apps_py.security import SecurityHelper
|
||||||
from src.vue_apps_py.server import VueApp
|
from src.vue_apps_py.server import VueApp, name_intents
|
||||||
from src.vue_apps_py.client import ClientAPI
|
from src.vue_apps_py.client import ClientAPI
|
||||||
from src.vue_apps_py.server_utils.models import Intent
|
from src.vue_apps_py.server_utils.intents import Intent, InlineQuery
|
||||||
|
from src.vue_apps_py.server_utils.models import AutoModel, SuggestionsModel
|
||||||
|
|
||||||
|
|
||||||
|
def autocomplete_handler(query_model: AutoModel) -> SuggestionsModel:
|
||||||
|
# Just log the query
|
||||||
|
print(query_model.json())
|
||||||
|
|
||||||
|
# Send back empty model
|
||||||
|
return SuggestionsModel()
|
||||||
|
|
||||||
|
|
||||||
# Initialize API
|
# Initialize API
|
||||||
api = ClientAPI(client_id='vue-app-0', client_secret='abccf12389efab222')
|
api = ClientAPI(client_id='vue-app-0', client_secret='abccf12389efab222')
|
||||||
@@ -22,8 +33,12 @@ api = ClientAPI(client_id='vue-app-0', client_secret='abccf12389efab222')
|
|||||||
# Prepare Security
|
# Prepare Security
|
||||||
key_holder = SecurityHelper(public_key='abbcbbbcaksjdhf/skdjhfnnsn/sjdkfjj21234=')
|
key_holder = SecurityHelper(public_key='abbcbbbcaksjdhf/skdjhfnnsn/sjdkfjj21234=')
|
||||||
|
|
||||||
|
# Define intents
|
||||||
|
intents: List[Intent] = [InlineQuery(autocomplete_handler)]
|
||||||
|
named_intents = name_intents(intents)
|
||||||
|
|
||||||
# Get Vue Application Server
|
# Get Vue Application Server
|
||||||
vue_app = VueApp(client_api=api, security_helper=key_holder, intents=[Intent.INLINE_QUERY], debug=True)
|
vue_app = VueApp(client_api=api, security_helper=key_holder, named_intents=named_intents, debug=True)
|
||||||
```
|
```
|
||||||
|
|
||||||
You can launch web server:
|
You can launch web server:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class SecurityHelper:
|
|||||||
def __init__(self, public_key: str):
|
def __init__(self, public_key: str):
|
||||||
self.public_key = VerifyingKey(public_key.encode(), encoding='base64')
|
self.public_key = VerifyingKey(public_key.encode(), encoding='base64')
|
||||||
|
|
||||||
def check(self, http: bytes, signature: bytes) -> bool:
|
def is_valid(self, http: bytes, signature: bytes) -> bool:
|
||||||
try:
|
try:
|
||||||
self.public_key.verify(signature, http, encoding='base64')
|
self.public_key.verify(signature, http, encoding='base64')
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,38 +1,89 @@
|
|||||||
from typing import List
|
from typing import Dict, List
|
||||||
|
|
||||||
from fastapi import FastAPI, Request, HTTPException
|
from fastapi import FastAPI, Request, HTTPException, status
|
||||||
|
|
||||||
from client import ClientAPI
|
from .client import ClientAPI
|
||||||
from security import SecurityHelper
|
from .security import SecurityHelper
|
||||||
from server_utils.models import PongModel, Intent
|
from .server_utils.intents import Intent, InlineQuery, PageMentions, Interactions, PageApp
|
||||||
|
from .server_utils.models import PongModel, SuggestionsModel, AutoModel, MentionModel, InteractionModel, PageModel
|
||||||
|
|
||||||
|
|
||||||
def VueApp(client_api: ClientAPI, security_helper: SecurityHelper, intents: List[Intent], debug: bool = False):
|
def name_intents(intents: List[Intent]) -> Dict[str, Intent]:
|
||||||
|
dictionary: Dict[str, Intent] = dict()
|
||||||
|
for intent in intents:
|
||||||
|
if issubclass(type(intent), Intent):
|
||||||
|
dictionary[str(intent)] = intent
|
||||||
|
return dictionary
|
||||||
|
|
||||||
|
|
||||||
|
def VueApp(client_api: ClientAPI,
|
||||||
|
security_helper: SecurityHelper,
|
||||||
|
named_intents: Dict[str, Intent],
|
||||||
|
debug: bool = False) -> FastAPI:
|
||||||
app = FastAPI(debug=debug, title='VueApp', description='Vue Application Server', version='0.0.1')
|
app = FastAPI(debug=debug, title='VueApp', description='Vue Application Server', version='0.0.1')
|
||||||
|
|
||||||
async def check_request(request: Request) -> bool:
|
async def check_request(request: Request):
|
||||||
if 'X-Signature-Ed25519' not in request.headers or 'X-Signature-Timestamp' not in request.headers:
|
if 'X-Signature-Ed25519' not in request.headers or 'X-Signature-Timestamp' not in request.headers:
|
||||||
return False
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Verification of signature failed')
|
||||||
body_to_verify = await request.body()
|
body_to_verify = await request.body()
|
||||||
timestamp = request.headers['X-Signature-Timestamp'].encode('utf-8')
|
timestamp = request.headers['X-Signature-Timestamp'].encode('utf-8')
|
||||||
signature = request.headers['X-Signature-Ed25519'].encode('utf-8')
|
signature = request.headers['X-Signature-Ed25519'].encode('utf-8')
|
||||||
return security_helper.check(timestamp + body_to_verify, signature)
|
if not security_helper.is_valid(timestamp + body_to_verify, signature):
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Verification of signature failed')
|
||||||
|
|
||||||
@app.post(path='/ping', response_model=PongModel)
|
@app.post(path='/ping', response_model=PongModel)
|
||||||
async def handle_ping(request: Request) -> PongModel:
|
async def handle_ping(request: Request) -> PongModel:
|
||||||
ok = await check_request(request)
|
await check_request(request)
|
||||||
if not ok:
|
|
||||||
raise HTTPException(status_code=401, detail='Verification of signature failed')
|
|
||||||
|
|
||||||
return PongModel(message='pong', token=client_api.get_token())
|
return PongModel(message='pong', token=client_api.get_token())
|
||||||
|
|
||||||
if Intent.INLINE_QUERY in intents:
|
if PageApp.intent_name in named_intents and isinstance(named_intents[PageApp.intent_name], PageApp):
|
||||||
@app.post(path='/auto', response_model=PongModel)
|
page_app_intent: PageApp = named_intents[PageApp.intent_name]
|
||||||
async def handle_autocomplete(request: Request) -> PongModel:
|
start_handler = page_app_intent.added_page_handler
|
||||||
ok = await check_request(request)
|
stop_handler = page_app_intent.removed_page_handler
|
||||||
if not ok:
|
|
||||||
raise HTTPException(status_code=401, detail='Verification of signature failed')
|
|
||||||
|
|
||||||
return PongModel(message='pong', token=client_api.get_token())
|
@app.post(path='/start', status_code=status.HTTP_201_CREATED)
|
||||||
|
async def handle_integration(request: Request, model: PageModel):
|
||||||
|
await check_request(request)
|
||||||
|
acknowledged = start_handler(model)
|
||||||
|
if not acknowledged:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@app.post(path='/stop', status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
async def handle_disconnect(request: Request, model: PageModel):
|
||||||
|
await check_request(request)
|
||||||
|
acknowledged = stop_handler(model)
|
||||||
|
if not acknowledged:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if InlineQuery.intent_name in named_intents and isinstance(named_intents[InlineQuery.intent_name], InlineQuery):
|
||||||
|
inline_query_intent: InlineQuery = named_intents[InlineQuery.intent_name]
|
||||||
|
auto_handler = inline_query_intent.query_handler
|
||||||
|
|
||||||
|
@app.post(path='/auto', response_model=SuggestionsModel)
|
||||||
|
async def handle_autocomplete(request: Request, model: AutoModel) -> SuggestionsModel:
|
||||||
|
await check_request(request)
|
||||||
|
return auto_handler(model)
|
||||||
|
|
||||||
|
if PageMentions.intent_name in named_intents and isinstance(named_intents[PageMentions.intent_name], PageMentions):
|
||||||
|
page_mentions_intent: PageMentions = named_intents[PageMentions.intent_name]
|
||||||
|
mentions_handler = page_mentions_intent.query_handler
|
||||||
|
|
||||||
|
@app.post(path='/mention', status_code=status.HTTP_202_ACCEPTED)
|
||||||
|
async def handle_mentions(request: Request, model: MentionModel):
|
||||||
|
await check_request(request)
|
||||||
|
acknowledged = mentions_handler(model)
|
||||||
|
if not acknowledged:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if Interactions.intent_name in named_intents and isinstance(named_intents[Interactions.intent_name], Interactions):
|
||||||
|
interactions_intent: Interactions = named_intents[Interactions.intent_name]
|
||||||
|
interactions_handler = interactions_intent.query_handler
|
||||||
|
|
||||||
|
@app.post(path='/interact', status_code=status.HTTP_202_ACCEPTED)
|
||||||
|
async def handle_interactions(request: Request, model: InteractionModel):
|
||||||
|
await check_request(request)
|
||||||
|
acknowledged = interactions_handler(model)
|
||||||
|
if not acknowledged:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
46
vue-apps-py/src/vue_apps_py/server_utils/intents.py
Normal file
46
vue-apps-py/src/vue_apps_py/server_utils/intents.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from .models import AutoModel, SuggestionsModel, MentionModel, InteractionModel, PageModel
|
||||||
|
|
||||||
|
|
||||||
|
class Intent:
|
||||||
|
intent_name: str
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.intent_name
|
||||||
|
|
||||||
|
|
||||||
|
class PageApp(Intent):
|
||||||
|
intent_name = 'PAGE_APP'
|
||||||
|
added_page_handler: Callable[[PageModel], bool]
|
||||||
|
removed_page_handler: Callable[[PageModel], bool]
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
added_page_handler: Callable[[PageModel], bool],
|
||||||
|
removed_page_handler: Callable[[PageModel], bool]):
|
||||||
|
self.added_page_handler = added_page_handler
|
||||||
|
self.removed_page_handler = removed_page_handler
|
||||||
|
|
||||||
|
|
||||||
|
class InlineQuery(Intent):
|
||||||
|
intent_name = 'INLINE_QUERY'
|
||||||
|
query_handler: Callable[[AutoModel], SuggestionsModel]
|
||||||
|
|
||||||
|
def __init__(self, query_handler: Callable[[AutoModel], SuggestionsModel]):
|
||||||
|
self.query_handler = query_handler
|
||||||
|
|
||||||
|
|
||||||
|
class PageMentions(Intent):
|
||||||
|
intent_name = 'PAGE_MENTIONS'
|
||||||
|
query_handler: Callable[[MentionModel], bool]
|
||||||
|
|
||||||
|
def __init__(self, query_handler: Callable[[MentionModel], bool]):
|
||||||
|
self.query_handler = query_handler
|
||||||
|
|
||||||
|
|
||||||
|
class Interactions(Intent):
|
||||||
|
intent_name = 'INTERACTIONS'
|
||||||
|
query_handler: Callable[[InteractionModel], bool]
|
||||||
|
|
||||||
|
def __init__(self, query_handler: Callable[[InteractionModel], bool]):
|
||||||
|
self.query_handler = query_handler
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
from enum import Enum, auto
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -13,8 +12,21 @@ class PongModel(BaseModel):
|
|||||||
token: Optional[str] = Field(default=None, title='Token', description='Token for API to check')
|
token: Optional[str] = Field(default=None, title='Token', description='Token for API to check')
|
||||||
|
|
||||||
|
|
||||||
class Intent(Enum):
|
class AutoModel(BaseModel):
|
||||||
PAGE_APP = auto()
|
pass
|
||||||
PAGE_MENTIONS = auto()
|
|
||||||
INLINE_QUERY = auto()
|
|
||||||
INTERACTIONS = auto()
|
class SuggestionsModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MentionModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InteractionModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PageModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user