refactor: separate standalone and managed room configs
- veins → shunts rename - add cfg/standalone/ and cfg/<room>/ structure - remove old data/*.json (moved to cfg/<room>/data/) - update build.py and ctrl scripts
This commit is contained in:
28
.woodpecker.yml
Normal file
28
.woodpecker.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Generic woodpecker trigger
|
||||||
|
# Copy to any repo as .woodpecker.yml
|
||||||
|
# Runs matching pipeline from ppl/pipelines/<repo>/
|
||||||
|
|
||||||
|
when:
|
||||||
|
event: push
|
||||||
|
branch: main
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache git openssh-client
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "$SSH_KEY" | base64 -d > ~/.ssh/id_rsa
|
||||||
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
- ssh -o StrictHostKeyChecking=no mariano@mcrn.ar "cd ~/ppl && ./ctrl/run-pipeline.sh ${CI_REPO_NAME} build"
|
||||||
|
secrets: [ssh_key]
|
||||||
|
|
||||||
|
- name: deploy
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache openssh-client
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "$SSH_KEY" | base64 -d > ~/.ssh/id_rsa
|
||||||
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
- ssh -o StrictHostKeyChecking=no mariano@mcrn.ar "cd ~/ppl && ./ctrl/run-pipeline.sh ${CI_REPO_NAME} deploy"
|
||||||
|
secrets: [ssh_key]
|
||||||
93
README.md
93
README.md
@@ -7,17 +7,64 @@ Development workflow platform. Run, test, and document everything in one place.
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build
|
# Build standalone
|
||||||
|
python build.py dev
|
||||||
|
cd gen/standalone && .venv/bin/python run.py
|
||||||
|
|
||||||
|
# Build with room config
|
||||||
python build.py dev --cfg amar
|
python build.py dev --cfg amar
|
||||||
|
cd gen/amar && .venv/bin/python run.py
|
||||||
|
|
||||||
# Run standalone (Docker)
|
|
||||||
./ctrl/start.sh
|
|
||||||
|
|
||||||
# Or bare-metal
|
|
||||||
cd gen && .venv/bin/python run.py
|
|
||||||
# Visit http://localhost:12000
|
# Visit http://localhost:12000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
python build.py dev # → gen/standalone/
|
||||||
|
python build.py dev --cfg amar # → gen/amar/
|
||||||
|
python build.py dev --all # → both
|
||||||
|
|
||||||
|
# ctrl scripts
|
||||||
|
./ctrl/build.sh # Build standalone
|
||||||
|
./ctrl/build.sh amar # Build amar
|
||||||
|
./ctrl/build.sh --all # Build all
|
||||||
|
|
||||||
|
./ctrl/start.sh # Start standalone (Docker)
|
||||||
|
./ctrl/start.sh amar # Start amar
|
||||||
|
./ctrl/start.sh -d # Detached
|
||||||
|
./ctrl/stop.sh # Stop
|
||||||
|
./ctrl/logs.sh # View logs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a New Managed Room
|
||||||
|
|
||||||
|
1. Create room config directory:
|
||||||
|
```bash
|
||||||
|
mkdir -p cfg/clientx
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add required files:
|
||||||
|
```
|
||||||
|
cfg/clientx/
|
||||||
|
├── .env.example # Environment template
|
||||||
|
├── docker-compose.yml # Room services (optional)
|
||||||
|
├── databrowse/depot/ # Database schemas (optional)
|
||||||
|
├── tester/tests/ # Room-specific tests (optional)
|
||||||
|
├── monitors/ # Room-specific monitors (optional)
|
||||||
|
├── models/ # Room-specific models (optional)
|
||||||
|
└── data/ # Room-specific data files (optional)
|
||||||
|
├── depots.json # Doc depots for Atlas
|
||||||
|
└── shunts.json # Mock connectors
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build and run:
|
||||||
|
```bash
|
||||||
|
python build.py dev --cfg clientx
|
||||||
|
./ctrl/start.sh clientx
|
||||||
|
```
|
||||||
|
|
||||||
## Systems
|
## Systems
|
||||||
|
|
||||||
| | System | What it does |
|
| | System | What it does |
|
||||||
@@ -34,28 +81,30 @@ spr/
|
|||||||
├── build.py # Build tool
|
├── build.py # Build tool
|
||||||
├── cfg/ # Room configurations
|
├── cfg/ # Room configurations
|
||||||
│ ├── soleprint.config.json
|
│ ├── soleprint.config.json
|
||||||
│ └── amar/ # AMAR room (docker-compose, tests, models)
|
│ └── amar/ # AMAR room config
|
||||||
├── ctrl/ # Standalone Docker scripts
|
├── ctrl/ # Docker scripts
|
||||||
│
|
│
|
||||||
├── artery/ # Connectors
|
├── artery/ # Connectors
|
||||||
│ ├── veins/ # Jira, Slack, Google
|
│ ├── veins/ # Jira, Slack, Google
|
||||||
│ ├── shunts/ # Fake connectors for testing
|
│ ├── shunts/ # Fake connectors for testing
|
||||||
│ └── plexuses/ # Full apps (backend + frontend)
|
│ └── plexus/ # Full apps (backend + frontend)
|
||||||
│
|
│
|
||||||
├── atlas/ # Documentation
|
├── atlas/ # Documentation
|
||||||
│ └── book/ # Gherkin samples, feature docs
|
│ └── books/ # Soleprint docs only
|
||||||
│
|
│
|
||||||
├── station/ # Tools & monitors
|
├── station/ # Tools & monitors
|
||||||
│ ├── tools/ # modelgen, tester, datagen
|
│ ├── tools/ # modelgen, tester, datagen
|
||||||
│ └── monitors/ # databrowse
|
│ └── monitors/ # databrowse
|
||||||
│
|
│
|
||||||
├── soleprint/ # Core (versioned)
|
├── soleprint/ # Core (versioned)
|
||||||
├── gen/ # Built instance (gitignored)
|
│
|
||||||
|
├── gen/ # Built instances (gitignored)
|
||||||
|
│ ├── standalone/ # Base soleprint
|
||||||
|
│ └── amar/ # With amar config
|
||||||
│
|
│
|
||||||
└── mainroom/ # Orchestration with managed room
|
└── mainroom/ # Orchestration with managed room
|
||||||
├── amar -> cfg/amar
|
├── amar -> cfg/amar
|
||||||
├── soleprint/ # Soleprint Docker config
|
├── soleprint/ # Soleprint Docker config
|
||||||
├── sbwrapper/ # Sidebar wrapper UI
|
|
||||||
└── ctrl/ # start, stop, deploy scripts
|
└── ctrl/ # start, stop, deploy scripts
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,26 +120,6 @@ Vein ──► Pulse ──► Plexus
|
|||||||
Shunt ── Fake connector for testing
|
Shunt ── Fake connector for testing
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Standalone (soleprint only)
|
|
||||||
```bash
|
|
||||||
python build.py dev
|
|
||||||
./ctrl/start.sh # Docker
|
|
||||||
./ctrl/stop.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### With Managed Room (amar + soleprint)
|
|
||||||
```bash
|
|
||||||
python build.py dev --cfg amar
|
|
||||||
docker network create soleprint_network
|
|
||||||
|
|
||||||
cd mainroom/ctrl
|
|
||||||
./start.sh -d # Start detached
|
|
||||||
./stop.sh # Stop all
|
|
||||||
./deploy.sh # Deploy to AWS
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ports
|
## Ports
|
||||||
|
|
||||||
| Service | Port |
|
| Service | Port |
|
||||||
|
|||||||
@@ -428,14 +428,7 @@
|
|||||||
<div
|
<div
|
||||||
class="vein{% if vein.status == 'live' or vein.status == 'building' %} active{% else %} disabled{% endif %}{% if loop.first %} selected{% endif %}"
|
class="vein{% if vein.status == 'live' or vein.status == 'building' %} active{% else %} disabled{% endif %}{% if loop.first %} selected{% endif %}"
|
||||||
data-tab="{{ vein.slug }}"
|
data-tab="{{ vein.slug }}"
|
||||||
{%
|
{% if vein.status == "planned" %}data-disabled="true"{% endif %}
|
||||||
if
|
|
||||||
vein.status=""
|
|
||||||
="planned"
|
|
||||||
%}data-disabled="true"
|
|
||||||
{%
|
|
||||||
endif
|
|
||||||
%}
|
|
||||||
>
|
>
|
||||||
<h3>{{ vein.title }}</h3>
|
<h3>{{ vein.title }}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
13
artery/shunts/amar/.env.example
Normal file
13
artery/shunts/amar/.env.example
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Amar (MOCK) Vein Configuration
|
||||||
|
API_PORT=8005
|
||||||
|
|
||||||
|
# Mock data settings
|
||||||
|
MOCK_DATA_PATH=./mock_data
|
||||||
|
|
||||||
|
# Mock behavior
|
||||||
|
ENABLE_RANDOM_DELAYS=true
|
||||||
|
MIN_DELAY_MS=100
|
||||||
|
MAX_DELAY_MS=500
|
||||||
|
|
||||||
|
# Simulate errors
|
||||||
|
ERROR_RATE=0.0 # 0.0 to 1.0 (0% to 100%)
|
||||||
173
artery/shunts/amar/README.md
Normal file
173
artery/shunts/amar/README.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Amar (MOCK) Vein
|
||||||
|
|
||||||
|
Mock Amar API for testing - returns predictable test data for turnero flow testing.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Enables testing of the turnero flow (VET-535-540) without hitting the real Amar backend:
|
||||||
|
- Guest petowner creation (VET-536)
|
||||||
|
- Pet creation (VET-537)
|
||||||
|
- Cart creation and price calculation (VET-538)
|
||||||
|
- Service/category filtering (VET-539, VET-540)
|
||||||
|
- Service request creation
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Start the mock server
|
||||||
|
python run.py
|
||||||
|
|
||||||
|
# API docs: http://localhost:8005/docs
|
||||||
|
# Health check: http://localhost:8005/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env` and adjust:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
API_PORT=8005 # Server port
|
||||||
|
ENABLE_RANDOM_DELAYS=true # Add realistic delays
|
||||||
|
MIN_DELAY_MS=100 # Minimum delay
|
||||||
|
MAX_DELAY_MS=500 # Maximum delay
|
||||||
|
ERROR_RATE=0.0 # Error rate (0.0 to 1.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Turnero Flow (VET-536-540)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create guest petowner (VET-536)
|
||||||
|
POST /api/v1/pet-owners/
|
||||||
|
{
|
||||||
|
"address": "Av. Santa Fe 1234, Palermo"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create pet (VET-537)
|
||||||
|
POST /api/v1/pets/
|
||||||
|
{
|
||||||
|
"owner_id": 1234,
|
||||||
|
"name": "Luna",
|
||||||
|
"species": "DOG",
|
||||||
|
"age": 3,
|
||||||
|
"age_unit": "years"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create cart (VET-538)
|
||||||
|
POST /api/v1/cart/
|
||||||
|
{
|
||||||
|
"owner_id": 1234
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add item to cart
|
||||||
|
POST /api/v1/cart/{cart_id}/items/
|
||||||
|
{
|
||||||
|
"service_id": 1,
|
||||||
|
"pet_id": 5678,
|
||||||
|
"quantity": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# List services (VET-540)
|
||||||
|
GET /api/v1/services/?species=DOG&neighborhood_id=1
|
||||||
|
|
||||||
|
# List categories (VET-539)
|
||||||
|
GET /api/v1/categories/?species=DOG&neighborhood_id=1
|
||||||
|
|
||||||
|
# Create service request
|
||||||
|
POST /solicitudes/service-requests/?cart_id=12345
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mock Control
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get mock database stats
|
||||||
|
GET /mock/stats
|
||||||
|
|
||||||
|
# Reset mock database
|
||||||
|
GET /mock/reset
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
|
||||||
|
All responses include `_mock: true` to identify mock data:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1234,
|
||||||
|
"address": "Av. Santa Fe 1234, Palermo",
|
||||||
|
"is_guest": true,
|
||||||
|
"_mock": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Scenarios
|
||||||
|
|
||||||
|
### Complete Turnero Flow
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
BASE_URL = "http://localhost:8005"
|
||||||
|
|
||||||
|
# Step 1: Create guest petowner
|
||||||
|
owner_resp = requests.post(f"{BASE_URL}/api/v1/pet-owners/", json={
|
||||||
|
"address": "Av. Santa Fe 1234, Palermo"
|
||||||
|
})
|
||||||
|
owner = owner_resp.json()
|
||||||
|
|
||||||
|
# Step 2: Create pet
|
||||||
|
pet_resp = requests.post(f"{BASE_URL}/api/v1/pets/", json={
|
||||||
|
"owner_id": owner["id"],
|
||||||
|
"name": "Luna",
|
||||||
|
"species": "DOG",
|
||||||
|
"age": 3,
|
||||||
|
"age_unit": "years"
|
||||||
|
})
|
||||||
|
pet = pet_resp.json()
|
||||||
|
|
||||||
|
# Step 3: Create cart
|
||||||
|
cart_resp = requests.post(f"{BASE_URL}/api/v1/cart/", json={
|
||||||
|
"owner_id": owner["id"]
|
||||||
|
})
|
||||||
|
cart = cart_resp.json()
|
||||||
|
|
||||||
|
# Step 4: Get available services
|
||||||
|
services_resp = requests.get(
|
||||||
|
f"{BASE_URL}/api/v1/services/",
|
||||||
|
params={"species": "DOG", "neighborhood_id": owner["neighborhood"]["id"]}
|
||||||
|
)
|
||||||
|
services = services_resp.json()
|
||||||
|
|
||||||
|
# Step 5: Add service to cart
|
||||||
|
cart_resp = requests.post(f"{BASE_URL}/api/v1/cart/{cart['id']}/items/", json={
|
||||||
|
"service_id": services[0]["id"],
|
||||||
|
"pet_id": pet["id"],
|
||||||
|
"quantity": 1
|
||||||
|
})
|
||||||
|
cart = cart_resp.json()
|
||||||
|
print(f"Cart total: ${cart['resume']['total']}")
|
||||||
|
|
||||||
|
# Step 6: Create service request
|
||||||
|
request_resp = requests.post(
|
||||||
|
f"{BASE_URL}/solicitudes/service-requests/",
|
||||||
|
params={"cart_id": cart["id"]}
|
||||||
|
)
|
||||||
|
service_request = request_resp.json()
|
||||||
|
print(f"Service request created: {service_request['id']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Generator
|
||||||
|
|
||||||
|
This vein uses the independent `datagen` tool from `ward/tools/datagen/amar.py`.
|
||||||
|
See `ward/tools/datagen/README.md` for data generation details.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Mock database is in-memory (resets on server restart)
|
||||||
|
- Use `/mock/reset` to clear data during testing
|
||||||
|
- All IDs are randomly generated on creation
|
||||||
|
- Multi-pet discounts are automatically calculated
|
||||||
1
artery/shunts/amar/__init__.py
Normal file
1
artery/shunts/amar/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Amar (MOCK) vein - Mock Amar API for testing."""
|
||||||
1
artery/shunts/amar/api/__init__.py
Normal file
1
artery/shunts/amar/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""API routes for Amar mock vein."""
|
||||||
302
artery/shunts/amar/api/routes.py
Normal file
302
artery/shunts/amar/api/routes.py
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
"""API routes for Amar (MOCK) vein - Mock Amar API for testing."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
from fastapi import APIRouter, HTTPException, Query
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
# Import datagen from ward/tools
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
ward_tools_path = Path(__file__).parent.parent.parent.parent.parent / "ward" / "tools"
|
||||||
|
sys.path.insert(0, str(ward_tools_path))
|
||||||
|
|
||||||
|
from datagen.amar import AmarDataGenerator
|
||||||
|
|
||||||
|
from ..core.config import settings
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# In-memory storage for mock data (reset on restart)
|
||||||
|
MOCK_DB = {
|
||||||
|
"petowners": {},
|
||||||
|
"pets": {},
|
||||||
|
"carts": {},
|
||||||
|
"service_requests": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Request/Response Models
|
||||||
|
class CreatePetOwnerRequest(BaseModel):
|
||||||
|
address: str
|
||||||
|
email: Optional[str] = None
|
||||||
|
phone: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePetRequest(BaseModel):
|
||||||
|
owner_id: int
|
||||||
|
name: str
|
||||||
|
species: str # DOG, CAT
|
||||||
|
age: Optional[int] = None
|
||||||
|
age_unit: str = "years" # years, months
|
||||||
|
|
||||||
|
|
||||||
|
class CreateCartRequest(BaseModel):
|
||||||
|
owner_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class AddCartItemRequest(BaseModel):
|
||||||
|
service_id: int
|
||||||
|
pet_id: int
|
||||||
|
quantity: int = 1
|
||||||
|
|
||||||
|
|
||||||
|
async def _mock_delay():
|
||||||
|
"""Add realistic delay if enabled."""
|
||||||
|
if settings.enable_random_delays:
|
||||||
|
delay_ms = random.randint(settings.min_delay_ms, settings.max_delay_ms)
|
||||||
|
await asyncio.sleep(delay_ms / 1000)
|
||||||
|
|
||||||
|
|
||||||
|
def _maybe_error():
|
||||||
|
"""Randomly raise an error based on error_rate."""
|
||||||
|
if random.random() < settings.error_rate:
|
||||||
|
raise HTTPException(500, "Mock error: Simulated failure")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/health")
|
||||||
|
async def health():
|
||||||
|
"""Health check endpoint."""
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"vein": "Amar\n(MOCK)",
|
||||||
|
"message": "Mock Amar API for testing",
|
||||||
|
"_mock": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/v1/pet-owners/")
|
||||||
|
async def create_petowner(request: CreatePetOwnerRequest):
|
||||||
|
"""Create a guest petowner (VET-536)."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
owner = AmarDataGenerator.petowner(
|
||||||
|
address=request.address,
|
||||||
|
is_guest=True,
|
||||||
|
email=request.email,
|
||||||
|
phone=request.phone,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Store in mock DB
|
||||||
|
MOCK_DB["petowners"][owner["id"]] = owner
|
||||||
|
|
||||||
|
owner["_mock"] = True
|
||||||
|
return owner
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/v1/pet-owners/{owner_id}")
|
||||||
|
async def get_petowner(owner_id: int):
|
||||||
|
"""Get petowner by ID."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
owner = MOCK_DB["petowners"].get(owner_id)
|
||||||
|
if not owner:
|
||||||
|
raise HTTPException(404, f"PetOwner {owner_id} not found")
|
||||||
|
|
||||||
|
return owner
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/v1/pets/")
|
||||||
|
async def create_pet(request: CreatePetRequest):
|
||||||
|
"""Create a pet (VET-537)."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
# Verify owner exists
|
||||||
|
if request.owner_id not in MOCK_DB["petowners"]:
|
||||||
|
raise HTTPException(404, f"Owner {request.owner_id} not found")
|
||||||
|
|
||||||
|
pet = AmarDataGenerator.pet(
|
||||||
|
owner_id=request.owner_id,
|
||||||
|
name=request.name,
|
||||||
|
species=request.species,
|
||||||
|
age_value=request.age,
|
||||||
|
age_unit=request.age_unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Store in mock DB
|
||||||
|
MOCK_DB["pets"][pet["id"]] = pet
|
||||||
|
|
||||||
|
pet["_mock"] = True
|
||||||
|
return pet
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/v1/pets/{pet_id}")
|
||||||
|
async def get_pet(pet_id: int):
|
||||||
|
"""Get pet by ID."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
pet = MOCK_DB["pets"].get(pet_id)
|
||||||
|
if not pet:
|
||||||
|
raise HTTPException(404, f"Pet {pet_id} not found")
|
||||||
|
|
||||||
|
return pet
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/v1/cart/")
|
||||||
|
async def create_cart(request: CreateCartRequest):
|
||||||
|
"""Create a cart (VET-538)."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
# Verify owner exists
|
||||||
|
if request.owner_id not in MOCK_DB["petowners"]:
|
||||||
|
raise HTTPException(404, f"Owner {request.owner_id} not found")
|
||||||
|
|
||||||
|
cart = AmarDataGenerator.cart(owner_id=request.owner_id)
|
||||||
|
|
||||||
|
# Store in mock DB
|
||||||
|
MOCK_DB["carts"][cart["id"]] = cart
|
||||||
|
|
||||||
|
cart["_mock"] = True
|
||||||
|
return cart
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/v1/cart/{cart_id}/items/")
|
||||||
|
async def add_cart_item(cart_id: int, request: AddCartItemRequest):
|
||||||
|
"""Add item to cart and recalculate summary."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
cart = MOCK_DB["carts"].get(cart_id)
|
||||||
|
if not cart:
|
||||||
|
raise HTTPException(404, f"Cart {cart_id} not found")
|
||||||
|
|
||||||
|
# Get service price
|
||||||
|
services = AmarDataGenerator.SERVICES
|
||||||
|
service = next((s for s in services if s["id"] == request.service_id), None)
|
||||||
|
if not service:
|
||||||
|
raise HTTPException(404, f"Service {request.service_id} not found")
|
||||||
|
|
||||||
|
# Add item
|
||||||
|
item = {
|
||||||
|
"service_id": request.service_id,
|
||||||
|
"service_name": service["name"],
|
||||||
|
"pet_id": request.pet_id,
|
||||||
|
"quantity": request.quantity,
|
||||||
|
"price": service["price"],
|
||||||
|
}
|
||||||
|
|
||||||
|
cart["items"].append(item)
|
||||||
|
|
||||||
|
# Recalculate summary
|
||||||
|
cart = AmarDataGenerator.calculate_cart_summary(cart, cart["items"])
|
||||||
|
MOCK_DB["carts"][cart_id] = cart
|
||||||
|
|
||||||
|
cart["_mock"] = True
|
||||||
|
return cart
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/v1/cart/{cart_id}")
|
||||||
|
async def get_cart(cart_id: int):
|
||||||
|
"""Get cart by ID."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
cart = MOCK_DB["carts"].get(cart_id)
|
||||||
|
if not cart:
|
||||||
|
raise HTTPException(404, f"Cart {cart_id} not found")
|
||||||
|
|
||||||
|
return cart
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/v1/services/")
|
||||||
|
async def list_services(
|
||||||
|
species: Optional[str] = Query(None),
|
||||||
|
neighborhood_id: Optional[int] = Query(None),
|
||||||
|
):
|
||||||
|
"""List available services filtered by species and neighborhood (VET-540)."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
services = AmarDataGenerator.filter_services(
|
||||||
|
species=species,
|
||||||
|
neighborhood_id=neighborhood_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
for service in services:
|
||||||
|
service["_mock"] = True
|
||||||
|
|
||||||
|
return services
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/v1/categories/")
|
||||||
|
async def list_categories(
|
||||||
|
species: Optional[str] = Query(None),
|
||||||
|
neighborhood_id: Optional[int] = Query(None),
|
||||||
|
):
|
||||||
|
"""List categories with available services (VET-539)."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
categories = AmarDataGenerator.filter_categories(
|
||||||
|
species=species,
|
||||||
|
neighborhood_id=neighborhood_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
for category in categories:
|
||||||
|
category["_mock"] = True
|
||||||
|
|
||||||
|
return categories
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/solicitudes/service-requests/")
|
||||||
|
async def create_service_request(cart_id: int, requested_date: Optional[str] = None):
|
||||||
|
"""Create a service request."""
|
||||||
|
await _mock_delay()
|
||||||
|
_maybe_error()
|
||||||
|
|
||||||
|
cart = MOCK_DB["carts"].get(cart_id)
|
||||||
|
if not cart:
|
||||||
|
raise HTTPException(404, f"Cart {cart_id} not found")
|
||||||
|
|
||||||
|
request = AmarDataGenerator.service_request(
|
||||||
|
cart_id=cart_id,
|
||||||
|
requested_date=requested_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
MOCK_DB["service_requests"][request["id"]] = request
|
||||||
|
|
||||||
|
request["_mock"] = True
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/mock/reset")
|
||||||
|
async def reset_mock_db():
|
||||||
|
"""Reset the mock database (useful for testing)."""
|
||||||
|
MOCK_DB["petowners"].clear()
|
||||||
|
MOCK_DB["pets"].clear()
|
||||||
|
MOCK_DB["carts"].clear()
|
||||||
|
MOCK_DB["service_requests"].clear()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "Mock database reset",
|
||||||
|
"_mock": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/mock/stats")
|
||||||
|
async def mock_stats():
|
||||||
|
"""Get mock database statistics."""
|
||||||
|
return {
|
||||||
|
"petowners": len(MOCK_DB["petowners"]),
|
||||||
|
"pets": len(MOCK_DB["pets"]),
|
||||||
|
"carts": len(MOCK_DB["carts"]),
|
||||||
|
"service_requests": len(MOCK_DB["service_requests"]),
|
||||||
|
"_mock": True,
|
||||||
|
}
|
||||||
1
artery/shunts/amar/core/__init__.py
Normal file
1
artery/shunts/amar/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Core logic for Amar mock API."""
|
||||||
27
artery/shunts/amar/core/config.py
Normal file
27
artery/shunts/amar/core/config.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"""Configuration for Amar mock vein."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
ENV_FILE = Path(__file__).parent.parent / ".env"
|
||||||
|
|
||||||
|
|
||||||
|
class AmarMockConfig(BaseSettings):
|
||||||
|
"""Configuration for Amar (MOCK) vein."""
|
||||||
|
|
||||||
|
api_port: int = 8005
|
||||||
|
mock_data_path: str = "./mock_data"
|
||||||
|
|
||||||
|
# Mock behavior
|
||||||
|
enable_random_delays: bool = True
|
||||||
|
min_delay_ms: int = 100
|
||||||
|
max_delay_ms: int = 500
|
||||||
|
error_rate: float = 0.0 # 0.0 to 1.0
|
||||||
|
|
||||||
|
model_config = {
|
||||||
|
"env_file": ENV_FILE if ENV_FILE.exists() else None,
|
||||||
|
"env_file_encoding": "utf-8",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
settings = AmarMockConfig()
|
||||||
40
artery/shunts/amar/main.py
Normal file
40
artery/shunts/amar/main.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""Amar (MOCK) Vein - FastAPI app."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from .api.routes import router
|
||||||
|
from .core.config import settings
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Amar API (MOCK)",
|
||||||
|
description="Mock Amar API for testing - returns predictable test data",
|
||||||
|
version="0.1.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable CORS for testing from frontend
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # In production, specify exact origins
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Templates for configuration UI
|
||||||
|
templates = Jinja2Templates(directory=Path(__file__).parent / "templates")
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
def index(request: Request):
|
||||||
|
"""Mock configuration UI."""
|
||||||
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
# Include router at root (matches real Amar API structure)
|
||||||
|
app.include_router(router)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=settings.api_port)
|
||||||
18
artery/shunts/amar/run.py
Normal file
18
artery/shunts/amar/run.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""Standalone runner for Amar mock vein."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import uvicorn
|
||||||
|
from main import app
|
||||||
|
from core.config import settings
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logger.info(f"Starting Amar (MOCK) vein on port {settings.api_port}")
|
||||||
|
logger.info(f"API docs: http://localhost:{settings.api_port}/docs")
|
||||||
|
logger.info(f"Health check: http://localhost:{settings.api_port}/health")
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=settings.api_port, reload=True)
|
||||||
298
artery/shunts/amar/templates/index.html
Normal file
298
artery/shunts/amar/templates/index.html
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Amar API (MOCK) - Configuration</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #111827;
|
||||||
|
color: #e5e7eb;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
header {
|
||||||
|
background: #f59e0b;
|
||||||
|
color: #111827;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 8px; }
|
||||||
|
.subtitle { opacity: 0.8; font-size: 0.875rem; }
|
||||||
|
.mock-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: #111827;
|
||||||
|
color: #f59e0b;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
background: #1f2937;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.section-header {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #f9fafb;
|
||||||
|
}
|
||||||
|
.endpoint-list { display: flex; flex-direction: column; gap: 12px; }
|
||||||
|
.endpoint-card {
|
||||||
|
background: #374151;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.endpoint-card:hover { border-color: #f59e0b; background: #4b5563; }
|
||||||
|
.endpoint-card.active { border-color: #f59e0b; background: #4b5563; }
|
||||||
|
.endpoint-method {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.method-post { background: #10b981; color: white; }
|
||||||
|
.method-get { background: #3b82f6; color: white; }
|
||||||
|
.endpoint-path { font-family: monospace; font-size: 0.875rem; }
|
||||||
|
.endpoint-desc { font-size: 0.75rem; color: #9ca3af; margin-top: 6px; }
|
||||||
|
.form-group { margin-bottom: 16px; }
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #f9fafb;
|
||||||
|
}
|
||||||
|
.form-input, .form-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #374151;
|
||||||
|
border: 1px solid #4b5563;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e5e7eb;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.form-textarea { min-height: 200px; font-family: monospace; }
|
||||||
|
.form-input:focus, .form-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #f59e0b;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background: #f59e0b;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
.btn-primary:hover { background: #d97706; }
|
||||||
|
.btn-secondary {
|
||||||
|
background: #4b5563;
|
||||||
|
color: #e5e7eb;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover { background: #6b7280; }
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.stat-card {
|
||||||
|
background: #374151;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.stat-value { font-size: 1.5rem; font-weight: 600; color: #f59e0b; }
|
||||||
|
.stat-label { font-size: 0.75rem; color: #9ca3af; margin-top: 4px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Amar API <span class="mock-badge">MOCK</span></h1>
|
||||||
|
<div class="subtitle">Configure mock responses for Amar backend endpoints</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-header">Mock Database Stats</div>
|
||||||
|
<div class="stats" id="stats">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">-</div>
|
||||||
|
<div class="stat-label">Pet Owners</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">-</div>
|
||||||
|
<div class="stat-label">Pets</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">-</div>
|
||||||
|
<div class="stat-label">Carts</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">-</div>
|
||||||
|
<div class="stat-label">Requests</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 16px;">
|
||||||
|
<button class="btn btn-secondary" onclick="resetMock()">Reset Database</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Endpoint Configuration -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-header">Configure Endpoint Responses</div>
|
||||||
|
<div class="endpoint-list">
|
||||||
|
<div class="endpoint-card" onclick="selectEndpoint('POST', '/api/v1/pet-owners/', 'petowner')">
|
||||||
|
<div>
|
||||||
|
<span class="endpoint-method method-post">POST</span>
|
||||||
|
<span class="endpoint-path">/api/v1/pet-owners/</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-desc">Create guest pet owner (VET-536)</div>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-card" onclick="selectEndpoint('POST', '/api/v1/pets/', 'pet')">
|
||||||
|
<div>
|
||||||
|
<span class="endpoint-method method-post">POST</span>
|
||||||
|
<span class="endpoint-path">/api/v1/pets/</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-desc">Create pet (VET-537)</div>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-card" onclick="selectEndpoint('POST', '/api/v1/cart/', 'cart')">
|
||||||
|
<div>
|
||||||
|
<span class="endpoint-method method-post">POST</span>
|
||||||
|
<span class="endpoint-path">/api/v1/cart/</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-desc">Create cart (VET-538)</div>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-card" onclick="selectEndpoint('GET', '/api/v1/services/', 'services')">
|
||||||
|
<div>
|
||||||
|
<span class="endpoint-method method-get">GET</span>
|
||||||
|
<span class="endpoint-path">/api/v1/services/</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-desc">List services (VET-540)</div>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-card" onclick="selectEndpoint('GET', '/api/v1/categories/', 'categories')">
|
||||||
|
<div>
|
||||||
|
<span class="endpoint-method method-get">GET</span>
|
||||||
|
<span class="endpoint-path">/api/v1/categories/</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-desc">List categories (VET-539)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Response Editor -->
|
||||||
|
<div class="section" id="responseEditor" style="display: none;">
|
||||||
|
<div class="section-header">Edit Response</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Endpoint</label>
|
||||||
|
<input class="form-input" id="endpointDisplay" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Mock Response (JSON)</label>
|
||||||
|
<textarea class="form-textarea" id="responseJson" placeholder='{"id": 123, "name": "Luna", "_mock": true}'></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">HTTP Status Code</label>
|
||||||
|
<input type="number" class="form-input" id="statusCode" value="200">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Delay (ms)</label>
|
||||||
|
<input type="number" class="form-input" id="delay" value="0">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" onclick="saveResponse()">Save Response</button>
|
||||||
|
<button class="btn btn-secondary" onclick="closeEditor()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Test -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-header">Quick Test</div>
|
||||||
|
<p style="color: #9ca3af; margin-bottom: 12px;">Test endpoint URL to hit for configured responses:</p>
|
||||||
|
<div class="form-input" style="background: #374151; user-select: all;">
|
||||||
|
http://localhost:8005/api/v1/pet-owners/
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let selectedEndpoint = null;
|
||||||
|
|
||||||
|
async function loadStats() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/mock/stats');
|
||||||
|
const data = await resp.json();
|
||||||
|
document.getElementById('stats').innerHTML = `
|
||||||
|
<div class="stat-card"><div class="stat-value">${data.pet_owners || 0}</div><div class="stat-label">Pet Owners</div></div>
|
||||||
|
<div class="stat-card"><div class="stat-value">${data.pets || 0}</div><div class="stat-label">Pets</div></div>
|
||||||
|
<div class="stat-card"><div class="stat-value">${data.carts || 0}</div><div class="stat-label">Carts</div></div>
|
||||||
|
<div class="stat-card"><div class="stat-value">${data.service_requests || 0}</div><div class="stat-label">Requests</div></div>
|
||||||
|
`;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load stats:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetMock() {
|
||||||
|
if (confirm('Reset all mock data?')) {
|
||||||
|
await fetch('/mock/reset');
|
||||||
|
loadStats();
|
||||||
|
alert('Mock database reset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectEndpoint(method, path, type) {
|
||||||
|
selectedEndpoint = {method, path, type};
|
||||||
|
document.querySelectorAll('.endpoint-card').forEach(c => c.classList.remove('active'));
|
||||||
|
event.currentTarget.classList.add('active');
|
||||||
|
document.getElementById('responseEditor').style.display = 'block';
|
||||||
|
document.getElementById('endpointDisplay').value = `${method} ${path}`;
|
||||||
|
document.getElementById('responseJson').value = getDefaultResponse(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultResponse(type) {
|
||||||
|
const defaults = {
|
||||||
|
petowner: JSON.stringify({"id": 1234, "address": "Av Santa Fe 1234", "is_guest": true, "_mock": true}, null, 2),
|
||||||
|
pet: JSON.stringify({"id": 5678, "name": "Luna", "species": "DOG", "age": 3, "_mock": true}, null, 2),
|
||||||
|
cart: JSON.stringify({"id": 9012, "items": [], "total": 0, "_mock": true}, null, 2),
|
||||||
|
services: JSON.stringify([{"id": 1, "name": "Vacunación", "price": 1500, "_mock": true}], null, 2),
|
||||||
|
categories: JSON.stringify([{"id": 1, "name": "Salud", "services": 5, "_mock": true}], null, 2)
|
||||||
|
};
|
||||||
|
return defaults[type] || '{}';
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveResponse() {
|
||||||
|
alert('Mock response saved (feature pending implementation)');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditor() {
|
||||||
|
document.getElementById('responseEditor').style.display = 'none';
|
||||||
|
selectedEndpoint = null;
|
||||||
|
document.querySelectorAll('.endpoint-card').forEach(c => c.classList.remove('active'));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStats();
|
||||||
|
setInterval(loadStats, 5000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
artery/shunts/mercadopago/requirements.txt
Normal file
4
artery/shunts/mercadopago/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi>=0.104.0
|
||||||
|
uvicorn>=0.24.0
|
||||||
|
pydantic>=2.0.0
|
||||||
|
pydantic-settings>=2.0.0
|
||||||
128
build.py
128
build.py
@@ -8,18 +8,23 @@ Both dev and deploy modes copy files (no symlinks) for Docker compatibility.
|
|||||||
After editing source files, re-run `python build.py dev` to update gen/.
|
After editing source files, re-run `python build.py dev` to update gen/.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python build.py dev # Build gen/ from source
|
python build.py dev # Build gen/standalone/
|
||||||
python build.py dev --cfg amar # Include amar room config
|
python build.py dev --cfg amar # Build gen/amar/
|
||||||
|
python build.py dev --all # Build all (standalone + rooms)
|
||||||
python build.py deploy --output /path/ # Build for production
|
python build.py deploy --output /path/ # Build for production
|
||||||
python build.py models # Only regenerate models
|
python build.py models # Only regenerate models
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
# Set up dev environment
|
# Set up dev environment (standalone)
|
||||||
python build.py dev
|
python build.py dev
|
||||||
cd gen && .venv/bin/python run.py
|
cd gen/standalone && .venv/bin/python run.py
|
||||||
|
|
||||||
# With room config
|
# With room config
|
||||||
python build.py dev --cfg amar
|
python build.py dev --cfg amar
|
||||||
|
cd gen/amar && .venv/bin/python run.py
|
||||||
|
|
||||||
|
# Build all targets
|
||||||
|
python build.py dev --all
|
||||||
|
|
||||||
# Build for deployment
|
# Build for deployment
|
||||||
python build.py deploy --output ../deploy/soleprint/
|
python build.py deploy --output ../deploy/soleprint/
|
||||||
@@ -72,13 +77,15 @@ def count_files(path: Path) -> int:
|
|||||||
return sum(1 for _ in path.rglob("*") if _.is_file())
|
return sum(1 for _ in path.rglob("*") if _.is_file())
|
||||||
|
|
||||||
|
|
||||||
def generate_models(output_dir: Path):
|
def generate_models(output_dir: Path, cfg_name: str | None = None):
|
||||||
"""Generate models using modelgen tool.
|
"""Generate models using modelgen tool.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_dir: Directory where models/pydantic/__init__.py will be created
|
output_dir: Directory where models/pydantic/__init__.py will be created
|
||||||
|
cfg_name: Room config name (e.g., 'amar'), or None for standalone
|
||||||
"""
|
"""
|
||||||
config_path = SPR_ROOT / "cfg" / "soleprint.config.json"
|
room = cfg_name or "standalone"
|
||||||
|
config_path = SPR_ROOT / "cfg" / room / "config.json"
|
||||||
|
|
||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
log.warning(f"Config not found at {config_path}")
|
log.warning(f"Config not found at {config_path}")
|
||||||
@@ -111,49 +118,53 @@ def copy_cfg(output_dir: Path, cfg_name: str | None):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_dir: Target directory
|
output_dir: Target directory
|
||||||
cfg_name: Name of room config (e.g., 'amar'), or None for base only
|
cfg_name: Name of room config (e.g., 'amar'), or None for standalone
|
||||||
"""
|
"""
|
||||||
|
room = cfg_name or "standalone"
|
||||||
|
room_cfg = SPR_ROOT / "cfg" / room
|
||||||
|
|
||||||
|
if not room_cfg.exists():
|
||||||
|
log.warning(f"Room config '{room}' not found at {room_cfg}")
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info(f"\nCopying {room} room config...")
|
||||||
|
|
||||||
|
# Copy config.json to cfg/
|
||||||
cfg_dir = output_dir / "cfg"
|
cfg_dir = output_dir / "cfg"
|
||||||
ensure_dir(cfg_dir)
|
ensure_dir(cfg_dir)
|
||||||
|
room_config = room_cfg / "config.json"
|
||||||
|
if room_config.exists():
|
||||||
|
copy_path(room_config, cfg_dir / "config.json")
|
||||||
|
|
||||||
# Always copy base config
|
# Copy data/ to output data/
|
||||||
base_config = SPR_ROOT / "cfg" / "soleprint.config.json"
|
room_data = room_cfg / "data"
|
||||||
if base_config.exists():
|
if room_data.exists():
|
||||||
copy_path(base_config, cfg_dir / "soleprint.config.json")
|
log.info(f" Copying {room} data files...")
|
||||||
|
copy_path(room_data, output_dir / "data")
|
||||||
|
|
||||||
# Copy room-specific config if specified
|
# Copy .env.example to output root
|
||||||
if cfg_name:
|
env_example = room_cfg / ".env.example"
|
||||||
room_cfg = SPR_ROOT / "cfg" / cfg_name
|
if env_example.exists():
|
||||||
if room_cfg.exists() and room_cfg.is_dir():
|
copy_path(env_example, output_dir / ".env.example")
|
||||||
log.info(f"\nCopying {cfg_name} room config...")
|
|
||||||
for item in room_cfg.iterdir():
|
|
||||||
if item.name == ".env.example":
|
|
||||||
# Copy .env.example to output root as template
|
|
||||||
copy_path(item, output_dir / ".env.example")
|
|
||||||
elif item.is_dir():
|
|
||||||
copy_path(item, cfg_dir / cfg_name / item.name)
|
|
||||||
else:
|
|
||||||
ensure_dir(cfg_dir / cfg_name)
|
|
||||||
copy_path(item, cfg_dir / cfg_name / item.name)
|
|
||||||
|
|
||||||
# Copy room-specific databrowse depot if exists
|
# Copy room-specific databrowse depot if exists
|
||||||
room_databrowse = room_cfg / "databrowse" / "depot"
|
room_databrowse = room_cfg / "databrowse" / "depot"
|
||||||
if room_databrowse.exists():
|
if room_databrowse.exists():
|
||||||
log.info(f" Copying {cfg_name} databrowse depot...")
|
log.info(f" Copying {room} databrowse depot...")
|
||||||
target = output_dir / "station" / "monitors" / "databrowse" / "depot"
|
target = output_dir / "station" / "monitors" / "databrowse" / "depot"
|
||||||
copy_path(room_databrowse, target)
|
copy_path(room_databrowse, target)
|
||||||
|
|
||||||
# Copy room-specific tester tests if exists
|
# Copy room-specific tester tests if exists
|
||||||
room_tests = room_cfg / "tester" / "tests"
|
room_tests = room_cfg / "tester" / "tests"
|
||||||
if room_tests.exists():
|
if room_tests.exists():
|
||||||
log.info(f" Copying {cfg_name} tester tests...")
|
log.info(f" Copying {room} tester tests...")
|
||||||
target = output_dir / "station" / "tools" / "tester" / "tests"
|
target = output_dir / "station" / "tools" / "tester" / "tests"
|
||||||
copy_path(room_tests, target)
|
copy_path(room_tests, target)
|
||||||
|
|
||||||
# Copy room-specific monitors if exists
|
# Copy room-specific monitors if exists
|
||||||
room_monitors = room_cfg / "monitors"
|
room_monitors = room_cfg / "monitors"
|
||||||
if room_monitors.exists():
|
if room_monitors.exists():
|
||||||
log.info(f" Copying {cfg_name} monitors...")
|
log.info(f" Copying {room} monitors...")
|
||||||
for monitor in room_monitors.iterdir():
|
for monitor in room_monitors.iterdir():
|
||||||
if monitor.is_dir():
|
if monitor.is_dir():
|
||||||
target = output_dir / "station" / "monitors" / monitor.name
|
target = output_dir / "station" / "monitors" / monitor.name
|
||||||
@@ -162,11 +173,9 @@ def copy_cfg(output_dir: Path, cfg_name: str | None):
|
|||||||
# Copy room-specific models if exists
|
# Copy room-specific models if exists
|
||||||
room_models = room_cfg / "models"
|
room_models = room_cfg / "models"
|
||||||
if room_models.exists():
|
if room_models.exists():
|
||||||
log.info(f" Copying {cfg_name} models...")
|
log.info(f" Copying {room} models...")
|
||||||
target = output_dir / "models" / cfg_name
|
target = output_dir / "models" / room
|
||||||
copy_path(room_models, target)
|
copy_path(room_models, target)
|
||||||
else:
|
|
||||||
log.warning(f"Room config '{cfg_name}' not found at {room_cfg}")
|
|
||||||
|
|
||||||
|
|
||||||
def build_dev(output_dir: Path, cfg_name: str | None = None):
|
def build_dev(output_dir: Path, cfg_name: str | None = None):
|
||||||
@@ -174,7 +183,7 @@ def build_dev(output_dir: Path, cfg_name: str | None = None):
|
|||||||
Build for development using copies (Docker-compatible).
|
Build for development using copies (Docker-compatible).
|
||||||
|
|
||||||
Structure:
|
Structure:
|
||||||
gen/
|
gen/standalone/ or gen/<room>/
|
||||||
├── main.py
|
├── main.py
|
||||||
├── run.py
|
├── run.py
|
||||||
├── index.html
|
├── index.html
|
||||||
@@ -189,7 +198,7 @@ def build_dev(output_dir: Path, cfg_name: str | None = None):
|
|||||||
├── .env.example # From cfg/<room>/.env.example
|
├── .env.example # From cfg/<room>/.env.example
|
||||||
└── models/ # Generated
|
└── models/ # Generated
|
||||||
|
|
||||||
After editing source files, re-run `python build.py dev` to update gen/.
|
After editing source files, re-run `python build.py dev` to update.
|
||||||
"""
|
"""
|
||||||
log.info("\n=== Building DEV environment ===")
|
log.info("\n=== Building DEV environment ===")
|
||||||
log.info(f"SPR root: {SPR_ROOT}")
|
log.info(f"SPR root: {SPR_ROOT}")
|
||||||
@@ -217,17 +226,13 @@ def build_dev(output_dir: Path, cfg_name: str | None = None):
|
|||||||
if source.exists():
|
if source.exists():
|
||||||
copy_path(source, output_dir / system)
|
copy_path(source, output_dir / system)
|
||||||
|
|
||||||
# Data directory
|
# Config (includes data/ from room)
|
||||||
log.info("\nCopying data...")
|
|
||||||
copy_path(SPR_ROOT / "data", output_dir / "data")
|
|
||||||
|
|
||||||
# Config
|
|
||||||
log.info("\nCopying config...")
|
log.info("\nCopying config...")
|
||||||
copy_cfg(output_dir, cfg_name)
|
copy_cfg(output_dir, cfg_name)
|
||||||
|
|
||||||
# Models (generated)
|
# Models (generated)
|
||||||
log.info("\nGenerating models...")
|
log.info("\nGenerating models...")
|
||||||
if not generate_models(output_dir):
|
if not generate_models(output_dir, cfg_name):
|
||||||
log.warning("Model generation failed, you may need to run it manually")
|
log.warning("Model generation failed, you may need to run it manually")
|
||||||
|
|
||||||
log.info("\n✓ Dev build complete!")
|
log.info("\n✓ Dev build complete!")
|
||||||
@@ -236,6 +241,11 @@ def build_dev(output_dir: Path, cfg_name: str | None = None):
|
|||||||
log.info(f" python3 -m venv .venv")
|
log.info(f" python3 -m venv .venv")
|
||||||
log.info(f" .venv/bin/pip install -r requirements.txt")
|
log.info(f" .venv/bin/pip install -r requirements.txt")
|
||||||
log.info(f" .venv/bin/python run.py # Single-port bare-metal dev")
|
log.info(f" .venv/bin/python run.py # Single-port bare-metal dev")
|
||||||
|
if cfg_name:
|
||||||
|
log.info(
|
||||||
|
f"\nAfter editing source, rebuild with: python build.py dev --cfg {cfg_name}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
log.info(f"\nAfter editing source, rebuild with: python build.py dev")
|
log.info(f"\nAfter editing source, rebuild with: python build.py dev")
|
||||||
|
|
||||||
|
|
||||||
@@ -276,19 +286,16 @@ def build_deploy(output_dir: Path, cfg_name: str | None = None):
|
|||||||
if source.exists():
|
if source.exists():
|
||||||
copy_path(source, output_dir / system)
|
copy_path(source, output_dir / system)
|
||||||
|
|
||||||
# Data directory (copy)
|
# Config (includes data/ from room)
|
||||||
log.info("\nCopying data...")
|
|
||||||
copy_path(SPR_ROOT / "data", output_dir / "data")
|
|
||||||
|
|
||||||
# Config (copy)
|
|
||||||
log.info("\nCopying config...")
|
log.info("\nCopying config...")
|
||||||
copy_cfg(output_dir, cfg_name)
|
copy_cfg(output_dir, cfg_name)
|
||||||
|
|
||||||
# Models (generate fresh) - pass output_dir, modelgen adds models/pydantic
|
# Models (generate fresh) - pass output_dir, modelgen adds models/pydantic
|
||||||
log.info("\nGenerating models...")
|
log.info("\nGenerating models...")
|
||||||
if not generate_models(output_dir):
|
if not generate_models(output_dir, cfg_name):
|
||||||
# Fallback: copy from gen if exists
|
# Fallback: copy from gen if exists
|
||||||
existing = SPR_ROOT / "gen" / "models"
|
room = cfg_name or "standalone"
|
||||||
|
existing = SPR_ROOT / "gen" / room / "models"
|
||||||
if existing.exists():
|
if existing.exists():
|
||||||
log.info(" Using existing models from gen/")
|
log.info(" Using existing models from gen/")
|
||||||
copy_path(existing, output_dir / "models")
|
copy_path(existing, output_dir / "models")
|
||||||
@@ -354,8 +361,8 @@ def main():
|
|||||||
"--output",
|
"--output",
|
||||||
"-o",
|
"-o",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=SPR_ROOT / "gen",
|
default=None,
|
||||||
help="Output directory (default: gen/)",
|
help="Output directory (default: gen/standalone/ or gen/<cfg>/)",
|
||||||
)
|
)
|
||||||
dev_parser.add_argument(
|
dev_parser.add_argument(
|
||||||
"--cfg",
|
"--cfg",
|
||||||
@@ -364,6 +371,11 @@ def main():
|
|||||||
default=None,
|
default=None,
|
||||||
help="Room config to include (e.g., 'amar')",
|
help="Room config to include (e.g., 'amar')",
|
||||||
)
|
)
|
||||||
|
dev_parser.add_argument(
|
||||||
|
"--all",
|
||||||
|
action="store_true",
|
||||||
|
help="Build all configs (standalone + all rooms in cfg/)",
|
||||||
|
)
|
||||||
|
|
||||||
# deploy command
|
# deploy command
|
||||||
deploy_parser = subparsers.add_parser(
|
deploy_parser = subparsers.add_parser(
|
||||||
@@ -390,7 +402,23 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.command == "dev":
|
if args.command == "dev":
|
||||||
build_dev(args.output.resolve(), args.cfg)
|
if getattr(args, "all", False):
|
||||||
|
# Build standalone
|
||||||
|
build_dev(SPR_ROOT / "gen" / "standalone", None)
|
||||||
|
# Build all room configs
|
||||||
|
cfg_dir = SPR_ROOT / "cfg"
|
||||||
|
for room in cfg_dir.iterdir():
|
||||||
|
if room.is_dir() and room.name not in ("__pycache__",):
|
||||||
|
build_dev(SPR_ROOT / "gen" / room.name, room.name)
|
||||||
|
else:
|
||||||
|
# Determine output directory
|
||||||
|
if args.output:
|
||||||
|
output_dir = args.output.resolve()
|
||||||
|
elif args.cfg:
|
||||||
|
output_dir = SPR_ROOT / "gen" / args.cfg
|
||||||
|
else:
|
||||||
|
output_dir = SPR_ROOT / "gen" / "standalone"
|
||||||
|
build_dev(output_dir, args.cfg)
|
||||||
elif args.command == "deploy":
|
elif args.command == "deploy":
|
||||||
build_deploy(args.output.resolve(), args.cfg)
|
build_deploy(args.output.resolve(), args.cfg)
|
||||||
elif args.command == "models":
|
elif args.command == "models":
|
||||||
|
|||||||
@@ -56,15 +56,27 @@
|
|||||||
"connector": {
|
"connector": {
|
||||||
"name": "vein",
|
"name": "vein",
|
||||||
"title": "Vein",
|
"title": "Vein",
|
||||||
"description": "Single responsibility connector",
|
"description": "Stateless API connector",
|
||||||
"plural": "veins"
|
"plural": "veins"
|
||||||
},
|
},
|
||||||
|
"mock": {
|
||||||
|
"name": "shunt",
|
||||||
|
"title": "Shunt",
|
||||||
|
"description": "Fake connector for testing",
|
||||||
|
"plural": "shunts"
|
||||||
|
},
|
||||||
"composed": {
|
"composed": {
|
||||||
"name": "pulse",
|
"name": "pulse",
|
||||||
"title": "Pulse",
|
"title": "Pulse",
|
||||||
"description": "Composed data flow",
|
"description": "Composed data flow",
|
||||||
"plural": "pulses",
|
"plural": "pulses",
|
||||||
"formula": "Vein + Room + Depot"
|
"formula": "Vein + Room + Depot"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"name": "plexus",
|
||||||
|
"title": "Plexus",
|
||||||
|
"description": "Full app with backend, frontend and DB",
|
||||||
|
"plural": "plexus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"documentation": {
|
"documentation": {
|
||||||
60
cfg/amar/data/veins.json
Normal file
60
cfg/amar/data/veins.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "jira",
|
||||||
|
"slug": "jira",
|
||||||
|
"title": "Jira",
|
||||||
|
"status": "live",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slack",
|
||||||
|
"slug": "slack",
|
||||||
|
"title": "Slack",
|
||||||
|
"status": "building",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "google",
|
||||||
|
"slug": "google",
|
||||||
|
"title": "Google",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maps",
|
||||||
|
"slug": "maps",
|
||||||
|
"title": "Maps",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "whatsapp",
|
||||||
|
"slug": "whatsapp",
|
||||||
|
"title": "WhatsApp",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gnucash",
|
||||||
|
"slug": "gnucash",
|
||||||
|
"title": "GNUCash",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vnc",
|
||||||
|
"slug": "vnc",
|
||||||
|
"title": "VNC",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ia",
|
||||||
|
"slug": "ia",
|
||||||
|
"title": "IA",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
140
cfg/standalone/config.json
Normal file
140
cfg/standalone/config.json
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"framework": {
|
||||||
|
"name": "soleprint",
|
||||||
|
"slug": "soleprint",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Development workflow and documentation system",
|
||||||
|
"tagline": "Mapping development footprints",
|
||||||
|
"icon": "👣",
|
||||||
|
"hub_port": 12000
|
||||||
|
},
|
||||||
|
"systems": [
|
||||||
|
{
|
||||||
|
"key": "data_flow",
|
||||||
|
"name": "artery",
|
||||||
|
"slug": "artery",
|
||||||
|
"title": "Artery",
|
||||||
|
"tagline": "Todo lo vital",
|
||||||
|
"port": 12001,
|
||||||
|
"icon": "💉"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "documentation",
|
||||||
|
"name": "atlas",
|
||||||
|
"slug": "atlas",
|
||||||
|
"title": "Atlas",
|
||||||
|
"tagline": "Documentación accionable",
|
||||||
|
"port": 12002,
|
||||||
|
"icon": "🗺️"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "execution",
|
||||||
|
"name": "station",
|
||||||
|
"slug": "station",
|
||||||
|
"title": "Station",
|
||||||
|
"tagline": "Monitores, Entornos y Herramientas",
|
||||||
|
"port": 12003,
|
||||||
|
"icon": "🎛️"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"shared": {
|
||||||
|
"config": {
|
||||||
|
"name": "room",
|
||||||
|
"title": "Room",
|
||||||
|
"description": "Runtime environment configuration",
|
||||||
|
"plural": "rooms"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"name": "depot",
|
||||||
|
"title": "Depot",
|
||||||
|
"description": "Data storage / provisions",
|
||||||
|
"plural": "depots"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data_flow": {
|
||||||
|
"connector": {
|
||||||
|
"name": "vein",
|
||||||
|
"title": "Vein",
|
||||||
|
"description": "Stateless API connector",
|
||||||
|
"plural": "veins"
|
||||||
|
},
|
||||||
|
"mock": {
|
||||||
|
"name": "shunt",
|
||||||
|
"title": "Shunt",
|
||||||
|
"description": "Fake connector for testing",
|
||||||
|
"plural": "shunts"
|
||||||
|
},
|
||||||
|
"composed": {
|
||||||
|
"name": "pulse",
|
||||||
|
"title": "Pulse",
|
||||||
|
"description": "Composed data flow",
|
||||||
|
"plural": "pulses",
|
||||||
|
"formula": "Vein + Room + Depot"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"name": "plexus",
|
||||||
|
"title": "Plexus",
|
||||||
|
"description": "Full app with backend, frontend and DB",
|
||||||
|
"plural": "plexus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"documentation": {
|
||||||
|
"pattern": {
|
||||||
|
"name": "template",
|
||||||
|
"title": "Template",
|
||||||
|
"description": "Documentation pattern",
|
||||||
|
"plural": "templates"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"name": "book",
|
||||||
|
"title": "Book",
|
||||||
|
"description": "Documentation library"
|
||||||
|
},
|
||||||
|
"composed": {
|
||||||
|
"name": "book",
|
||||||
|
"title": "Book",
|
||||||
|
"description": "Composed documentation",
|
||||||
|
"plural": "books",
|
||||||
|
"formula": "Template + Depot"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"execution": {
|
||||||
|
"utility": {
|
||||||
|
"name": "tool",
|
||||||
|
"title": "Tool",
|
||||||
|
"description": "Execution utility",
|
||||||
|
"plural": "tools"
|
||||||
|
},
|
||||||
|
"watcher": {
|
||||||
|
"name": "monitor",
|
||||||
|
"title": "Monitor",
|
||||||
|
"description": "Service monitor",
|
||||||
|
"plural": "monitors"
|
||||||
|
},
|
||||||
|
"container": {
|
||||||
|
"name": "cabinet",
|
||||||
|
"title": "Cabinet",
|
||||||
|
"description": "Tool container",
|
||||||
|
"plural": "cabinets"
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"name": "desk",
|
||||||
|
"title": "Desk",
|
||||||
|
"description": "Execution workspace"
|
||||||
|
},
|
||||||
|
"workbench": {
|
||||||
|
"name": "desk",
|
||||||
|
"title": "Desk",
|
||||||
|
"description": "Work surface"
|
||||||
|
},
|
||||||
|
"composed": {
|
||||||
|
"name": "desk",
|
||||||
|
"title": "Desk",
|
||||||
|
"description": "Composed execution bundle",
|
||||||
|
"plural": "desks",
|
||||||
|
"formula": "Cabinet + Room + Depots"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
156
cfg/standalone/data/__init__.py
Normal file
156
cfg/standalone/data/__init__.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
"""
|
||||||
|
Pawprint Data Layer
|
||||||
|
|
||||||
|
JSON file storage (future: MongoDB)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
# Add parent to path for models import
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
from models.pydantic import (
|
||||||
|
Vein, Nest, Larder, Template, Tool,
|
||||||
|
Pulse, Book, Table,
|
||||||
|
VeinCollection, NestCollection, LarderCollection,
|
||||||
|
TemplateCollection, ToolCollection,
|
||||||
|
PulseCollection, BookCollection, TableCollection,
|
||||||
|
Status
|
||||||
|
)
|
||||||
|
|
||||||
|
DATA_DIR = Path(__file__).parent.resolve()
|
||||||
|
|
||||||
|
# Debug: print data dir on import
|
||||||
|
print(f"[data] DATA_DIR: {DATA_DIR}")
|
||||||
|
print(f"[data] DATA_DIR exists: {DATA_DIR.exists()}")
|
||||||
|
print(f"[data] veins.json exists: {(DATA_DIR / 'veins.json').exists()}")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_json(filename: str) -> dict:
|
||||||
|
filepath = DATA_DIR / filename
|
||||||
|
if filepath.exists():
|
||||||
|
with open(filepath) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
print(f"[data] Loaded {filename}: {len(data.get('items', []))} items")
|
||||||
|
return data
|
||||||
|
print(f"[data] File not found: {filepath}")
|
||||||
|
return {"items": []}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_json(filename: str, data: dict):
|
||||||
|
filepath = DATA_DIR / filename
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
# === Loaders ===
|
||||||
|
|
||||||
|
def get_veins() -> List[Vein]:
|
||||||
|
data = _load_json("veins.json")
|
||||||
|
return VeinCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
def get_nests() -> List[Nest]:
|
||||||
|
data = _load_json("nests.json")
|
||||||
|
return NestCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
def get_larders() -> List[Larder]:
|
||||||
|
data = _load_json("larders.json")
|
||||||
|
return LarderCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
def get_templates() -> List[Template]:
|
||||||
|
data = _load_json("templates.json")
|
||||||
|
return TemplateCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
def get_tools() -> List[Tool]:
|
||||||
|
data = _load_json("tools.json")
|
||||||
|
return ToolCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
def get_cabinets() -> list:
|
||||||
|
"""Load cabinets (simple dict, no pydantic yet)."""
|
||||||
|
data = _load_json("cabinets.json")
|
||||||
|
return data.get("items", [])
|
||||||
|
|
||||||
|
|
||||||
|
def get_monitors() -> list:
|
||||||
|
"""Load monitors (simple dict, no pydantic yet)."""
|
||||||
|
data = _load_json("monitors.json")
|
||||||
|
return data.get("items", [])
|
||||||
|
|
||||||
|
|
||||||
|
def get_pulses() -> List[Pulse]:
|
||||||
|
data = _load_json("pulses.json")
|
||||||
|
return PulseCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
def get_books() -> List[Book]:
|
||||||
|
data = _load_json("books.json")
|
||||||
|
return BookCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
def get_tables() -> List[Table]:
|
||||||
|
data = _load_json("tables.json")
|
||||||
|
return TableCollection(**data).items
|
||||||
|
|
||||||
|
|
||||||
|
# === Helpers ===
|
||||||
|
|
||||||
|
def get_vein(name: str) -> Optional[Vein]:
|
||||||
|
for v in get_veins():
|
||||||
|
if v.name == name:
|
||||||
|
return v
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_nest(name: str) -> Optional[Nest]:
|
||||||
|
for n in get_nests():
|
||||||
|
if n.name == name:
|
||||||
|
return n
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_larder(name: str) -> Optional[Larder]:
|
||||||
|
for l in get_larders():
|
||||||
|
if l.name == name:
|
||||||
|
return l
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# === For frontend rendering ===
|
||||||
|
|
||||||
|
def get_artery_data() -> dict:
|
||||||
|
"""Data for artery frontend."""
|
||||||
|
return {
|
||||||
|
"veins": [v.model_dump() for v in get_veins()],
|
||||||
|
"nests": [n.model_dump() for n in get_nests()],
|
||||||
|
"larders": [l.model_dump() for l in get_larders()],
|
||||||
|
"pulses": [p.model_dump() for p in get_pulses()],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_album_data() -> dict:
|
||||||
|
"""Data for album frontend."""
|
||||||
|
return {
|
||||||
|
"templates": [t.model_dump() for t in get_templates()],
|
||||||
|
"larders": [l.model_dump() for l in get_larders()],
|
||||||
|
"books": [b.model_dump() for b in get_books()],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_ward_data() -> dict:
|
||||||
|
"""Data for ward frontend."""
|
||||||
|
return {
|
||||||
|
"tools": [t.model_dump() for t in get_tools()],
|
||||||
|
"monitors": get_monitors(),
|
||||||
|
"cabinets": get_cabinets(),
|
||||||
|
"nests": [n.model_dump() for n in get_nests()],
|
||||||
|
"larders": [l.model_dump() for l in get_larders()],
|
||||||
|
"tables": [t.model_dump() for t in get_tables()],
|
||||||
|
}
|
||||||
79
cfg/standalone/data/books.json
Normal file
79
cfg/standalone/data/books.json
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "arch-model",
|
||||||
|
"slug": "arch-model",
|
||||||
|
"title": "Architecture Model",
|
||||||
|
"status": "ready",
|
||||||
|
"template": null,
|
||||||
|
"larder": {
|
||||||
|
"name": "arch-model",
|
||||||
|
"slug": "arch-model",
|
||||||
|
"title": "Architecture Model",
|
||||||
|
"status": "ready",
|
||||||
|
"source_template": null,
|
||||||
|
"data_path": "album/book/arch-model"
|
||||||
|
},
|
||||||
|
"output_larder": null,
|
||||||
|
"system": "album"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "feature-flow",
|
||||||
|
"slug": "feature-flow",
|
||||||
|
"title": "Feature Flow Pipeline",
|
||||||
|
"status": "ready",
|
||||||
|
"template": null,
|
||||||
|
"larder": {
|
||||||
|
"name": "feature-flow",
|
||||||
|
"slug": "feature-flow",
|
||||||
|
"title": "Feature Flow Pipeline",
|
||||||
|
"status": "ready",
|
||||||
|
"source_template": null,
|
||||||
|
"data_path": "album/book/feature-flow"
|
||||||
|
},
|
||||||
|
"output_larder": null,
|
||||||
|
"system": "album"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gherkin-samples",
|
||||||
|
"slug": "gherkin-samples",
|
||||||
|
"title": "Gherkin Samples",
|
||||||
|
"status": "ready",
|
||||||
|
"template": null,
|
||||||
|
"larder": {
|
||||||
|
"name": "gherkin-samples",
|
||||||
|
"slug": "gherkin-samples",
|
||||||
|
"title": "Gherkin Samples",
|
||||||
|
"status": "ready",
|
||||||
|
"source_template": null,
|
||||||
|
"data_path": "album/book/gherkin-samples"
|
||||||
|
},
|
||||||
|
"output_larder": null,
|
||||||
|
"system": "album"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "feature-form-samples",
|
||||||
|
"slug": "feature-form-samples",
|
||||||
|
"title": "Feature Form Samples",
|
||||||
|
"status": "ready",
|
||||||
|
"template": {
|
||||||
|
"name": "feature-form",
|
||||||
|
"slug": "feature-form",
|
||||||
|
"title": "Feature Form Template",
|
||||||
|
"status": "ready",
|
||||||
|
"template_path": "album/template/feature-form",
|
||||||
|
"system": "album"
|
||||||
|
},
|
||||||
|
"larder": {
|
||||||
|
"name": "feature-form",
|
||||||
|
"slug": "feature-form",
|
||||||
|
"title": "Feature Forms",
|
||||||
|
"status": "ready",
|
||||||
|
"source_template": "feature-form",
|
||||||
|
"data_path": "album/book/feature-form-samples/feature-form"
|
||||||
|
},
|
||||||
|
"output_larder": null,
|
||||||
|
"system": "album"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
cfg/standalone/data/depots.json
Normal file
12
cfg/standalone/data/depots.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "feature-form",
|
||||||
|
"slug": "feature-form",
|
||||||
|
"title": "Feature Forms",
|
||||||
|
"status": "ready",
|
||||||
|
"source_template": "feature-form",
|
||||||
|
"data_path": "album/book/feature-form-samples/feature-form"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
cfg/standalone/data/desks.json
Normal file
3
cfg/standalone/data/desks.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
22
cfg/standalone/data/monitors.json
Normal file
22
cfg/standalone/data/monitors.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "turnos",
|
||||||
|
"slug": "turnos",
|
||||||
|
"title": "Turnos Monitor",
|
||||||
|
"status": "dev",
|
||||||
|
"system": "ward",
|
||||||
|
"description": "Pipeline view of requests → turnos. Shows vet-petowner at a glance.",
|
||||||
|
"path": "ward/monitor/turnos"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data_browse",
|
||||||
|
"slug": "data-browse",
|
||||||
|
"title": "Data Browse",
|
||||||
|
"status": "ready",
|
||||||
|
"system": "ward",
|
||||||
|
"description": "Quick navigation to test users and data states. Book/larder pattern with SQL mode for manual testing workflows.",
|
||||||
|
"path": "ward/monitor/data_browse"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
cfg/standalone/data/pulses.json
Normal file
3
cfg/standalone/data/pulses.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
5
cfg/standalone/data/rooms.json
Normal file
5
cfg/standalone/data/rooms.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{"name": "pawprint-local", "slug": "pawprint-local", "title": "Pawprint Local", "status": "dev", "config_path": "deploy/pawprint-local"}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
cfg/standalone/data/tables.json
Normal file
3
cfg/standalone/data/tables.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
38
cfg/standalone/data/template/feature-form/template.md
Normal file
38
cfg/standalone/data/template/feature-form/template.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# {{nombre_flujo}}
|
||||||
|
|
||||||
|
## Tipo de usuario
|
||||||
|
{{tipo_usuario}}
|
||||||
|
|
||||||
|
## Donde empieza
|
||||||
|
{{punto_entrada}}
|
||||||
|
|
||||||
|
## Que quiere hacer el usuario
|
||||||
|
{{objetivo}}
|
||||||
|
|
||||||
|
## Pasos
|
||||||
|
|
||||||
|
1. {{paso_1}}
|
||||||
|
2. {{paso_2}}
|
||||||
|
3. {{paso_3}}
|
||||||
|
|
||||||
|
## Que deberia pasar
|
||||||
|
|
||||||
|
- {{resultado_1}}
|
||||||
|
- {{resultado_2}}
|
||||||
|
|
||||||
|
## Problemas comunes
|
||||||
|
|
||||||
|
- {{problema_1}}
|
||||||
|
- {{problema_2}}
|
||||||
|
|
||||||
|
## Casos especiales
|
||||||
|
|
||||||
|
- {{caso_especial_1}}
|
||||||
|
|
||||||
|
## Flujos relacionados
|
||||||
|
|
||||||
|
- {{flujo_relacionado_1}}
|
||||||
|
|
||||||
|
## Notas tecnicas
|
||||||
|
|
||||||
|
- {{nota_tecnica_1}}
|
||||||
12
cfg/standalone/data/templates.json
Normal file
12
cfg/standalone/data/templates.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "feature-form",
|
||||||
|
"slug": "feature-form",
|
||||||
|
"title": "Feature Form Template",
|
||||||
|
"status": "ready",
|
||||||
|
"template_path": "data/template/feature-form",
|
||||||
|
"system": "album"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
48
cfg/standalone/data/tools.json
Normal file
48
cfg/standalone/data/tools.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "tester",
|
||||||
|
"slug": "tester",
|
||||||
|
"title": "Contract Tests",
|
||||||
|
"status": "live",
|
||||||
|
"system": "ward",
|
||||||
|
"type": "app",
|
||||||
|
"description": "HTTP contract test runner with multi-environment support. Filter, run, and track tests against dev/stage/prod.",
|
||||||
|
"path": "ward/tools/tester",
|
||||||
|
"url": "/tools/tester/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "datagen",
|
||||||
|
"slug": "datagen",
|
||||||
|
"title": "Test Data Generator",
|
||||||
|
"status": "live",
|
||||||
|
"system": "ward",
|
||||||
|
"type": "cli",
|
||||||
|
"description": "Generate realistic test data for Amar domain (users, pets, services) and MercadoPago API responses. Used by mock veins and test seeders.",
|
||||||
|
"path": "ward/tools/datagen",
|
||||||
|
"cli": "python -m datagen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "generate_test_data",
|
||||||
|
"slug": "generate-test-data",
|
||||||
|
"title": "DB Test Data Extractor",
|
||||||
|
"status": "dev",
|
||||||
|
"system": "ward",
|
||||||
|
"type": "cli",
|
||||||
|
"description": "Extract representative subsets from PostgreSQL dumps for testing/development.",
|
||||||
|
"path": "ward/tools/generate_test_data",
|
||||||
|
"cli": "python -m generate_test_data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "modelgen",
|
||||||
|
"slug": "modelgen",
|
||||||
|
"title": "Model Generator",
|
||||||
|
"status": "dev",
|
||||||
|
"system": "ward",
|
||||||
|
"type": "cli",
|
||||||
|
"description": "Generate platform-specific models (Pydantic, Django, Prisma) from JSON Schema.",
|
||||||
|
"path": "ward/tools/modelgen",
|
||||||
|
"cli": "python -m modelgen"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
60
cfg/standalone/data/veins.json
Normal file
60
cfg/standalone/data/veins.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "jira",
|
||||||
|
"slug": "jira",
|
||||||
|
"title": "Jira",
|
||||||
|
"status": "live",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slack",
|
||||||
|
"slug": "slack",
|
||||||
|
"title": "Slack",
|
||||||
|
"status": "building",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "google",
|
||||||
|
"slug": "google",
|
||||||
|
"title": "Google",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maps",
|
||||||
|
"slug": "maps",
|
||||||
|
"title": "Maps",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "whatsapp",
|
||||||
|
"slug": "whatsapp",
|
||||||
|
"title": "WhatsApp",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gnucash",
|
||||||
|
"slug": "gnucash",
|
||||||
|
"title": "GNUCash",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vnc",
|
||||||
|
"slug": "vnc",
|
||||||
|
"title": "VNC",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ia",
|
||||||
|
"slug": "ia",
|
||||||
|
"title": "IA",
|
||||||
|
"status": "planned",
|
||||||
|
"system": "artery"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,21 +2,25 @@
|
|||||||
# Build soleprint for local development
|
# Build soleprint for local development
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./build.sh # Build gen/ with default config
|
# ./build.sh # Build gen/standalone/
|
||||||
# ./build.sh amar # Build gen/ with amar room config
|
# ./build.sh amar # Build gen/amar/
|
||||||
|
# ./build.sh --all # Build all targets
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
CFG="${1:-}"
|
TARGET="${1:-}"
|
||||||
|
|
||||||
if [ -n "$CFG" ]; then
|
if [ "$TARGET" = "--all" ]; then
|
||||||
echo "Building with --cfg $CFG..."
|
echo "Building all targets..."
|
||||||
python build.py dev --cfg "$CFG"
|
python build.py dev --all
|
||||||
|
elif [ -n "$TARGET" ]; then
|
||||||
|
echo "Building gen/$TARGET/..."
|
||||||
|
python build.py dev --cfg "$TARGET"
|
||||||
else
|
else
|
||||||
echo "Building soleprint..."
|
echo "Building gen/standalone/..."
|
||||||
python build.py dev
|
python build.py dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Done. Run with: ./ctrl/start.sh"
|
echo "Done. Run with: ./ctrl/start.sh [target]"
|
||||||
|
|||||||
26
ctrl/logs.sh
26
ctrl/logs.sh
@@ -1,7 +1,29 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# View soleprint logs
|
# View soleprint logs
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./logs.sh # Logs for standalone
|
||||||
|
# ./logs.sh amar # Logs for amar
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")/../gen"
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
docker compose logs -f "$@"
|
TARGET="standalone"
|
||||||
|
ARGS=""
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
-*) ARGS="$ARGS $arg" ;;
|
||||||
|
*) TARGET="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
GEN_DIR="gen/$TARGET"
|
||||||
|
|
||||||
|
if [ ! -d "$GEN_DIR" ]; then
|
||||||
|
echo "$GEN_DIR not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$GEN_DIR"
|
||||||
|
docker compose logs -f $ARGS
|
||||||
|
|||||||
@@ -1,28 +1,32 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Start soleprint with Docker (for network access to other services)
|
# Start soleprint with Docker
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./start.sh # Start in foreground
|
# ./start.sh # Start standalone
|
||||||
# ./start.sh -d # Start detached
|
# ./start.sh amar # Start amar
|
||||||
# ./start.sh --build # Rebuild image first
|
# ./start.sh -d # Detached
|
||||||
|
# ./start.sh amar -d # Start amar detached
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
# Ensure gen/ exists
|
TARGET="standalone"
|
||||||
if [ ! -d "gen" ]; then
|
|
||||||
echo "gen/ not found. Run ./ctrl/build.sh first"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd gen
|
|
||||||
|
|
||||||
ARGS=""
|
ARGS=""
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case $arg in
|
case $arg in
|
||||||
-d|--detach) ARGS="$ARGS -d" ;;
|
-d|--detach) ARGS="$ARGS -d" ;;
|
||||||
--build) ARGS="$ARGS --build" ;;
|
--build) ARGS="$ARGS --build" ;;
|
||||||
|
*) TARGET="$arg" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
GEN_DIR="gen/$TARGET"
|
||||||
|
|
||||||
|
if [ ! -d "$GEN_DIR" ]; then
|
||||||
|
echo "$GEN_DIR not found. Run ./ctrl/build.sh $TARGET first"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$GEN_DIR"
|
||||||
docker compose up $ARGS
|
docker compose up $ARGS
|
||||||
|
|||||||
15
ctrl/stop.sh
15
ctrl/stop.sh
@@ -1,7 +1,20 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Stop soleprint Docker container
|
# Stop soleprint Docker container
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./stop.sh # Stop standalone
|
||||||
|
# ./stop.sh amar # Stop amar
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")/../gen"
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
TARGET="${1:-standalone}"
|
||||||
|
GEN_DIR="gen/$TARGET"
|
||||||
|
|
||||||
|
if [ ! -d "$GEN_DIR" ]; then
|
||||||
|
echo "$GEN_DIR not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$GEN_DIR"
|
||||||
docker compose down
|
docker compose down
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"items": [
|
|
||||||
{"name": "jira", "slug": "jira", "title": "Jira", "status": "live", "system": "artery"},
|
|
||||||
{"name": "slack", "slug": "slack", "title": "Slack", "status": "building", "system": "artery"},
|
|
||||||
{"name": "amar", "slug": "amar", "title": "Amar API", "status": "ready", "system": "artery", "mock": true, "description": "Mock Amar backend - configure endpoint responses"},
|
|
||||||
{"name": "mercadopago", "slug": "mercadopago", "title": "MercadoPago", "status": "ready", "system": "artery", "mock": true, "description": "Mock MercadoPago API - configure payment responses"},
|
|
||||||
{"name": "google", "slug": "google", "title": "Google", "status": "planned", "system": "artery"},
|
|
||||||
{"name": "maps", "slug": "maps", "title": "Maps", "status": "planned", "system": "artery"},
|
|
||||||
{"name": "whatsapp", "slug": "whatsapp", "title": "WhatsApp", "status": "planned", "system": "artery"},
|
|
||||||
{"name": "gnucash", "slug": "gnucash", "title": "GNUCash", "status": "planned", "system": "artery"},
|
|
||||||
{"name": "vnc", "slug": "vnc", "title": "VPN", "status": "planned", "system": "artery"},
|
|
||||||
{"name": "ia", "slug": "ia", "title": "IA", "status": "planned", "system": "artery"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user