Initial commit with Advoware proxy

This commit is contained in:
root
2025-10-19 14:57:07 +00:00
commit 273aa8b549
45771 changed files with 5534555 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
from services.advoware import AdvowareAPI
config = {
'type': 'api',
'name': 'Advoware Proxy',
'description': 'Universal proxy for Advoware API',
'path': '/api/advoware/proxy',
'method': 'GET',
'emits': [],
'flows': ['advoware']
}
async def handler(req, context):
try:
# Endpoint aus Query-Parametern
endpoint = req.get('queryParams', {}).get('endpoint', '')
if not endpoint:
return {'status': 400, 'body': {'error': 'Endpoint required as query param'}}
advoware = AdvowareAPI(context)
method = req.get('method', 'GET')
params = {k: v for k, v in req.get('queryParams', {}).items() if k != 'endpoint'}
json_data = req.get('body') if method in ['POST', 'PUT', 'PATCH'] else None
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
return {'status': 200, 'body': {'result': result}}
except Exception as e:
context.logger.error(f"Proxy error: {e}")
return {'status': 500, 'body': {'error': 'Internal server error', 'details': str(e)}}

View File

@@ -0,0 +1,31 @@
from services.advoware import AdvowareAPI
config = {
'type': 'api',
'name': 'Advoware Proxy DELETE',
'description': 'Universal proxy for Advoware API (DELETE)',
'path': '/api/advoware/proxy/delete',
'method': 'DELETE',
'emits': [],
'flows': ['advoware']
}
async def handler(req, context):
try:
# Endpoint aus Query-Parametern
endpoint = req.get('queryParams', {}).get('endpoint', '')
if not endpoint:
return {'status': 400, 'body': {'error': 'Endpoint required as query param'}}
advoware = AdvowareAPI(context)
method = 'DELETE' # Feste Methode für diesen Step
params = {k: v for k, v in req.get('queryParams', {}).items() if k != 'endpoint'}
json_data = req.get('body')
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
return {'status': 200, 'body': {'result': result}}
except Exception as e:
context.logger.error(f"Proxy error: {e}")
return {'status': 500, 'body': {'error': 'Internal server error', 'details': str(e)}}

View File

@@ -0,0 +1,31 @@
from services.advoware import AdvowareAPI
config = {
'type': 'api',
'name': 'Advoware Proxy GET',
'description': 'Universal proxy for Advoware API (GET)',
'path': '/api/advoware/proxy',
'method': 'GET',
'emits': [],
'flows': ['advoware']
}
async def handler(req, context):
try:
# Endpoint aus Query-Parametern
endpoint = req.get('queryParams', {}).get('endpoint', '')
if not endpoint:
return {'status': 400, 'body': {'error': 'Endpoint required as query param'}}
advoware = AdvowareAPI(context)
method = req.get('method', 'GET')
params = {k: v for k, v in req.get('queryParams', {}).items() if k != 'endpoint'}
json_data = req.get('body') if method in ['POST', 'PUT', 'PATCH'] else None
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
return {'status': 200, 'body': {'result': result}}
except Exception as e:
context.logger.error(f"Proxy error: {e}")
return {'status': 500, 'body': {'error': 'Internal server error', 'details': str(e)}}

View File

@@ -0,0 +1,31 @@
from services.advoware import AdvowareAPI
config = {
'type': 'api',
'name': 'Advoware Proxy POST',
'description': 'Universal proxy for Advoware API (POST)',
'path': '/api/advoware/proxy/post',
'method': 'POST',
'emits': [],
'flows': ['advoware']
}
async def handler(req, context):
try:
# Endpoint aus Query-Parametern
endpoint = req.get('queryParams', {}).get('endpoint', '')
if not endpoint:
return {'status': 400, 'body': {'error': 'Endpoint required as query param'}}
advoware = AdvowareAPI(context)
method = 'POST' # Feste Methode für diesen Step
params = {k: v for k, v in req.get('queryParams', {}).items() if k != 'endpoint'}
json_data = req.get('body')
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
return {'status': 200, 'body': {'result': result}}
except Exception as e:
context.logger.error(f"Proxy error: {e}")
return {'status': 500, 'body': {'error': 'Internal server error', 'details': str(e)}}

View File

@@ -0,0 +1,31 @@
from services.advoware import AdvowareAPI
config = {
'type': 'api',
'name': 'Advoware Proxy PUT',
'description': 'Universal proxy for Advoware API (PUT)',
'path': '/api/advoware/proxy/put',
'method': 'PUT',
'emits': [],
'flows': ['advoware']
}
async def handler(req, context):
try:
# Endpoint aus Query-Parametern
endpoint = req.get('queryParams', {}).get('endpoint', '')
if not endpoint:
return {'status': 400, 'body': {'error': 'Endpoint required as query param'}}
advoware = AdvowareAPI(context)
method = 'PUT' # Feste Methode für diesen Step
params = {k: v for k, v in req.get('queryParams', {}).items() if k != 'endpoint'}
json_data = req.get('body')
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
return {'status': 200, 'body': {'result': result}}
except Exception as e:
context.logger.error(f"Proxy error: {e}")
return {'status': 500, 'body': {'error': 'Internal server error', 'details': str(e)}}

View File

@@ -0,0 +1,52 @@
from pydantic import BaseModel
from typing import Optional
from src.services.pet_store import pet_store_service
from src.services.types import Pet
class PetRequest(BaseModel):
name: str
photoUrl: str
class FoodOrder(BaseModel):
id: str
quantity: int
class RequestBody(BaseModel):
pet: PetRequest
foodOrder: Optional[FoodOrder] = None
config = {
"type": "api",
"name": "ApiTrigger",
"description": "basic-tutorial api trigger",
"flows": ["basic-tutorial"],
"method": "POST",
"path": "/basic-tutorial",
"bodySchema": RequestBody.model_json_schema(),
"responseSchema": {
200: Pet.model_json_schema(),
},
"emits": ["process-food-order"],
}
async def handler(req, context):
body = req.get("body", {})
context.logger.info("Step 01 Processing API Step", {"body": body})
pet = body.get("pet", {})
food_order = body.get("foodOrder", {})
new_pet_record = await pet_store_service.create_pet(pet)
if food_order:
await context.emit({
"topic": "process-food-order",
"data": {
"id": food_order.get("id"),
"quantity": food_order.get("quantity"),
"email": "test@test.com", # sample email
"pet_id": new_pet_record.get("id"),
},
})
return {"status": 200, "body": {**new_pet_record, "traceId": context.trace_id}}

View File

@@ -0,0 +1,69 @@
[
{
"id": "step-configuration",
"title": "Step Configuration",
"description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
"lines": [
"6-30"
]
},
{
"id": "api-configuration",
"title": "API Step",
"description": "Definition of an API endpoint",
"lines": [
"23-24"
]
},
{
"id": "request-body",
"title": "Request body",
"description": "Definition of the expected request body. Motia will automatically generate types based on this schema.",
"lines": [
"6-16",
"25"
]
},
{
"id": "response-payload",
"title": "Response Payload",
"description": "Definition of the expected response payload, Motia will generate the types automatically based on this schema. This is also important to create the Open API spec later.",
"lines": [
"4",
"26-28"
]
},
{
"id": "event-driven-architecture",
"title": "Emits",
"description": "We can define the events that this step will emit, this is how we can trigger other Motia Steps.",
"lines": [
"29",
"42-50"
]
},
{
"id": "handler",
"title": "Handler",
"description": "The handler is the function that will be executed when the step is triggered. This one receives the request body and emits events.",
"lines": [
"32-52"
]
},
{
"id": "logger",
"title": "Logger",
"description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function. We encourage you to use it instead of console.log. It will automatically be tied to the trace id of the request.",
"lines": [
"34"
]
},
{
"id": "http-response",
"title": "HTTP Response",
"description": "The handler can return a response to the client. This is how we can return a response to the client. It must comply with the responseSchema defined in the step configuration.",
"lines": [
"52"
]
}
]

View File

@@ -0,0 +1,40 @@
from pydantic import BaseModel
from typing import Dict, Any
import re
class InputSchema(BaseModel):
template_id: str
email: str
template_data: Dict[str, Any]
config = {
"type": "event",
"name": "Notification",
"description": "Checks a state change",
"flows": ["basic-tutorial"],
"subscribes": ["notification"],
"emits": [],
"input": InputSchema.model_json_schema(),
}
async def handler(input_data, context):
email = input_data.get("email")
template_id = input_data.get("template_id")
template_data = input_data.get("template_data")
redacted_email = re.sub(r'(?<=.{2}).(?=.*@)', '*', email)
context.logger.info("Processing Notification", {
"template_id": template_id,
"template_data": template_data,
"email": redacted_email,
})
# This represents a call to some sort of
# notification service to indicate that a
# new order has been placed
context.logger.info("New notification sent", {
"template_id": template_id,
"email": redacted_email,
"template_data": template_data,
})

View File

@@ -0,0 +1,50 @@
from pydantic import BaseModel
from datetime import datetime
from src.services.pet_store import pet_store_service
class InputSchema(BaseModel):
id: str
email: str
quantity: int
pet_id: int
config = {
"type": "event",
"name": "ProcessFoodOrder",
"description": "basic-tutorial event step, demonstrates how to consume an event from a topic and persist data in state",
"flows": ["basic-tutorial"],
"subscribes": ["process-food-order"],
"emits": ["notification"],
"input": InputSchema.model_json_schema(),
}
async def handler(input_data, context):
context.logger.info("Step 02 Process food order", {"input": input_data})
order = await pet_store_service.create_order({
"id": input_data.get("id"),
"quantity": input_data.get("quantity"),
"pet_id": input_data.get("pet_id"),
"email": input_data.get("email"),
"ship_date": datetime.now().isoformat(),
"status": "placed",
})
context.logger.info("Order created", {"order": order})
await context.state.set("orders_python", order.get("id"), order)
await context.emit({
"topic": "notification",
"data": {
"email": input_data["email"],
"template_id": "new-order",
"template_data": {
"status": order.get("status"),
"ship_date": order.get("shipDate"),
"id": order.get("id"),
"pet_id": order.get("petId"),
"quantity": order.get("quantity"),
},
},
})

View File

@@ -0,0 +1,68 @@
[
{
"id": "step-configuration",
"title": "Step Configuration",
"description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
"lines": [
"5-19"
]
},
{
"id": "event-configuration",
"title": "Event Step",
"description": "Definition of an event step that subscribes to specific topics",
"lines": [
"12",
"15-16"
]
},
{
"id": "input-schema",
"title": "Input Schema",
"description": "Definition of the expected input data structure from the subscribed topic. Motia will automatically generate types based on this schema.",
"lines": [
"5-9",
"17"
]
},
{
"id": "event-emits",
"title": "Emits",
"description": "We can define the events that this step will emit, triggering other Motia Steps.",
"lines": [
"17"
]
},
{
"id": "handler",
"title": "Handler",
"description": "The handler is the function that will be executed when the step receives an event from its subscribed topic. It processes the input data and can emit new events.",
"lines": [
"21-50"
]
},
{
"id": "state",
"title": "State Management",
"description": "The handler demonstrates state management by storing order data that can be accessed by other steps.",
"lines": [
"35"
]
},
{
"id": "event-emission",
"title": "Event Emission",
"description": "After processing the order, the handler emits a new event to notify other steps about the new order.",
"lines": [
"37-50"
]
},
{
"id": "logger",
"title": "Logger",
"description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function and automatically ties to the trace id of the request.",
"lines": [
"22"
]
}
]

View File

@@ -0,0 +1,39 @@
from datetime import datetime, timezone
config = {
"type": "cron",
"cron": "0 0 * * 1", # run once every Monday at midnight
"name": "StateAuditJob",
"description": "Checks the state for orders that are not complete and have a ship date in the past",
"emits": ["notification"],
"flows": ["basic-tutorial"],
}
async def handler(context):
state_value = await context.state.get_group("orders_python")
for item in state_value:
# check if current date is after item.ship_date
current_date = datetime.now(timezone.utc)
ship_date = datetime.fromisoformat(item.get("shipDate", "").replace('Z', '+00:00'))
if not item.get("complete", False) and current_date > ship_date:
context.logger.warn("Order is not complete and ship date is past", {
"order_id": item.get("id"),
"ship_date": item.get("shipDate"),
"complete": item.get("complete", False),
})
await context.emit({
"topic": "notification",
"data": {
"email": "test@test.com",
"template_id": "order-audit-warning",
"template_data": {
"order_id": item.get("id"),
"status": item.get("status"),
"ship_date": item.get("shipDate"),
"message": "Order is not complete and ship date is past",
},
},
})

View File

@@ -0,0 +1,26 @@
[
{
"id": "step-configuration",
"title": "Step Configuration",
"description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
"lines": [
"3-10"
]
},
{
"id": "cron-configuration",
"title": "Cron Configuration",
"description": "Cron steps require a specific configuration structure with the 'type' field set to 'cron' and a valid cron expression.",
"lines": [
"4-5"
]
},
{
"id": "handler",
"title": "Cron Step Handler",
"description": "The Cron step handler only receives one argument.",
"lines": [
"12-39"
]
}
]