docs: add architecture and veins documentation
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- System overview, artery hierarchy, build flow, room config diagrams
- Veins docs: Jira, Slack, Google OAuth
- Shunts docs: MercadoPago mock
- DOT source files with generated SVGs
- HTML viewers with navigation and full-screen mode

Solves:
- Freelance work standardization
- Missing infrastructure replication (shunts)
- Reliable testing environment (BDD -> Gherkin -> Tests)
This commit is contained in:
buenosairesam
2026-01-02 22:09:13 -03:00
parent 05e7ead081
commit 22356fed66
22 changed files with 2887 additions and 0 deletions

61
docs/veins/google.dot Normal file
View File

@@ -0,0 +1,61 @@
digraph GoogleVein {
rankdir=LR;
compound=true;
fontname="Helvetica";
node [fontname="Helvetica", fontsize=11, shape=box, style="rounded,filled"];
edge [fontname="Helvetica", fontsize=10];
labelloc="t";
label="Google Vein - OAuth Flow";
fontsize=16;
// Client
subgraph cluster_client {
label="Soleprint";
style=filled;
color="#E8F5E9";
fillcolor="#E8F5E9";
app [label="Application", fillcolor="#C8E6C9"];
vein [label="Google Vein\n(artery/veins/google)", fillcolor="#A5D6A7"];
oauth [label="OAuth Handler\n(artery/oauth.py)", fillcolor="#81C784"];
}
// OAuth Flow
subgraph cluster_oauth {
label="OAuth 2.0";
style=filled;
color="#FFF8E1";
fillcolor="#FFF8E1";
auth_url [label="1. Authorization URL", fillcolor="#FFECB3"];
consent [label="2. User Consent", fillcolor="#FFE082"];
callback [label="3. Callback + Code", fillcolor="#FFD54F"];
tokens [label="4. Access + Refresh\nTokens", fillcolor="#FFCA28"];
}
// Google APIs
subgraph cluster_google {
label="Google APIs";
style=filled;
color="#E3F2FD";
fillcolor="#E3F2FD";
sheets [label="Sheets API", fillcolor="#BBDEFB"];
calendar [label="Calendar API", fillcolor="#BBDEFB"];
drive [label="Drive API", fillcolor="#BBDEFB"];
}
// Flow
app -> vein [label="get_sheets()"];
vein -> oauth [label="ensure_auth"];
oauth -> auth_url;
auth_url -> consent;
consent -> callback;
callback -> tokens;
tokens -> oauth [label="store"];
oauth -> sheets [label="Bearer token"];
oauth -> calendar [label="Bearer token"];
oauth -> drive [label="Bearer token"];
}

158
docs/veins/google.svg Normal file
View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.1 (0)
-->
<!-- Title: GoogleVein Pages: 1 -->
<svg width="1243pt" height="340pt"
viewBox="0.00 0.00 1243.00 340.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 335.5)">
<title>GoogleVein</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-335.5 1239.25,-335.5 1239.25,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="617.62" y="-312.3" font-family="Helvetica,sans-Serif" font-size="16.00">Google Vein &#45; OAuth Flow</text>
<g id="clust1" class="cluster">
<title>cluster_client</title>
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="8,-74 8,-154 537,-154 537,-74 8,-74"/>
<text xml:space="preserve" text-anchor="middle" x="272.5" y="-134.8" font-family="Helvetica,sans-Serif" font-size="16.00">Soleprint</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_oauth</title>
<polygon fill="#fff8e1" stroke="#fff8e1" points="619.25,-216 619.25,-296 1227.25,-296 1227.25,-216 619.25,-216"/>
<text xml:space="preserve" text-anchor="middle" x="923.25" y="-276.8" font-family="Helvetica,sans-Serif" font-size="16.00">OAuth 2.0</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_google</title>
<polygon fill="#e3f2fd" stroke="#e3f2fd" points="636.38,-20 636.38,-208 748.38,-208 748.38,-20 636.38,-20"/>
<text xml:space="preserve" text-anchor="middle" x="692.38" y="-188.8" font-family="Helvetica,sans-Serif" font-size="16.00">Google APIs</text>
</g>
<!-- app -->
<g id="node1" class="node">
<title>app</title>
<path fill="#c8e6c9" stroke="black" d="M80.75,-118C80.75,-118 28,-118 28,-118 22,-118 16,-112 16,-106 16,-106 16,-94 16,-94 16,-88 22,-82 28,-82 28,-82 80.75,-82 80.75,-82 86.75,-82 92.75,-88 92.75,-94 92.75,-94 92.75,-106 92.75,-106 92.75,-112 86.75,-118 80.75,-118"/>
<text xml:space="preserve" text-anchor="middle" x="54.38" y="-96.3" font-family="Helvetica,sans-Serif" font-size="11.00">Application</text>
</g>
<!-- vein -->
<g id="node2" class="node">
<title>vein</title>
<path fill="#a5d6a7" stroke="black" d="M309.75,-118C309.75,-118 201.5,-118 201.5,-118 195.5,-118 189.5,-112 189.5,-106 189.5,-106 189.5,-94 189.5,-94 189.5,-88 195.5,-82 201.5,-82 201.5,-82 309.75,-82 309.75,-82 315.75,-82 321.75,-88 321.75,-94 321.75,-94 321.75,-106 321.75,-106 321.75,-112 315.75,-118 309.75,-118"/>
<text xml:space="preserve" text-anchor="middle" x="255.62" y="-103.05" font-family="Helvetica,sans-Serif" font-size="11.00">Google Vein</text>
<text xml:space="preserve" text-anchor="middle" x="255.62" y="-89.55" font-family="Helvetica,sans-Serif" font-size="11.00">(artery/veins/google)</text>
</g>
<!-- app&#45;&gt;vein -->
<g id="edge1" class="edge">
<title>app&#45;&gt;vein</title>
<path fill="none" stroke="black" d="M92.85,-100C116.74,-100 148.56,-100 177.65,-100"/>
<polygon fill="black" stroke="black" points="177.56,-103.5 187.56,-100 177.56,-96.5 177.56,-103.5"/>
<text xml:space="preserve" text-anchor="middle" x="141.12" y="-103.25" font-family="Helvetica,sans-Serif" font-size="10.00">get_sheets()</text>
</g>
<!-- oauth -->
<g id="node3" class="node">
<title>oauth</title>
<path fill="#81c784" stroke="black" d="M517,-118C517,-118 429.75,-118 429.75,-118 423.75,-118 417.75,-112 417.75,-106 417.75,-106 417.75,-94 417.75,-94 417.75,-88 423.75,-82 429.75,-82 429.75,-82 517,-82 517,-82 523,-82 529,-88 529,-94 529,-94 529,-106 529,-106 529,-112 523,-118 517,-118"/>
<text xml:space="preserve" text-anchor="middle" x="473.38" y="-103.05" font-family="Helvetica,sans-Serif" font-size="11.00">OAuth Handler</text>
<text xml:space="preserve" text-anchor="middle" x="473.38" y="-89.55" font-family="Helvetica,sans-Serif" font-size="11.00">(artery/oauth.py)</text>
</g>
<!-- vein&#45;&gt;oauth -->
<g id="edge2" class="edge">
<title>vein&#45;&gt;oauth</title>
<path fill="none" stroke="black" d="M322.24,-100C348.92,-100 379.71,-100 406.43,-100"/>
<polygon fill="black" stroke="black" points="406.09,-103.5 416.09,-100 406.09,-96.5 406.09,-103.5"/>
<text xml:space="preserve" text-anchor="middle" x="369.75" y="-103.25" font-family="Helvetica,sans-Serif" font-size="10.00">ensure_auth</text>
</g>
<!-- auth_url -->
<g id="node4" class="node">
<title>auth_url</title>
<path fill="#ffecb3" stroke="black" d="M744.5,-260C744.5,-260 639.25,-260 639.25,-260 633.25,-260 627.25,-254 627.25,-248 627.25,-248 627.25,-236 627.25,-236 627.25,-230 633.25,-224 639.25,-224 639.25,-224 744.5,-224 744.5,-224 750.5,-224 756.5,-230 756.5,-236 756.5,-236 756.5,-248 756.5,-248 756.5,-254 750.5,-260 744.5,-260"/>
<text xml:space="preserve" text-anchor="middle" x="691.88" y="-238.3" font-family="Helvetica,sans-Serif" font-size="11.00">1. Authorization URL</text>
</g>
<!-- oauth&#45;&gt;auth_url -->
<g id="edge3" class="edge">
<title>oauth&#45;&gt;auth_url</title>
<path fill="none" stroke="black" d="M494.27,-118.13C521.13,-141.94 571.17,-184 619.25,-212 623.25,-214.33 627.46,-216.57 631.76,-218.71"/>
<polygon fill="black" stroke="black" points="629.98,-221.74 640.51,-222.85 632.97,-215.41 629.98,-221.74"/>
</g>
<!-- sheets -->
<g id="node8" class="node">
<title>sheets</title>
<path fill="#bbdefb" stroke="black" d="M717.12,-172C717.12,-172 666.62,-172 666.62,-172 660.62,-172 654.62,-166 654.62,-160 654.62,-160 654.62,-148 654.62,-148 654.62,-142 660.62,-136 666.62,-136 666.62,-136 717.12,-136 717.12,-136 723.12,-136 729.12,-142 729.12,-148 729.12,-148 729.12,-160 729.12,-160 729.12,-166 723.12,-172 717.12,-172"/>
<text xml:space="preserve" text-anchor="middle" x="691.88" y="-150.3" font-family="Helvetica,sans-Serif" font-size="11.00">Sheets API</text>
</g>
<!-- oauth&#45;&gt;sheets -->
<g id="edge8" class="edge">
<title>oauth&#45;&gt;sheets</title>
<path fill="none" stroke="black" d="M529.29,-113.7C564.52,-122.48 609.79,-133.78 643.45,-142.17"/>
<polygon fill="black" stroke="black" points="642.37,-145.51 652.92,-144.53 644.07,-138.72 642.37,-145.51"/>
<text xml:space="preserve" text-anchor="middle" x="578.12" y="-136.67" font-family="Helvetica,sans-Serif" font-size="10.00">Bearer token</text>
</g>
<!-- calendar -->
<g id="node9" class="node">
<title>calendar</title>
<path fill="#bbdefb" stroke="black" d="M722.75,-118C722.75,-118 661,-118 661,-118 655,-118 649,-112 649,-106 649,-106 649,-94 649,-94 649,-88 655,-82 661,-82 661,-82 722.75,-82 722.75,-82 728.75,-82 734.75,-88 734.75,-94 734.75,-94 734.75,-106 734.75,-106 734.75,-112 728.75,-118 722.75,-118"/>
<text xml:space="preserve" text-anchor="middle" x="691.88" y="-96.3" font-family="Helvetica,sans-Serif" font-size="11.00">Calendar API</text>
</g>
<!-- oauth&#45;&gt;calendar -->
<g id="edge9" class="edge">
<title>oauth&#45;&gt;calendar</title>
<path fill="none" stroke="black" d="M529.29,-100C562.46,-100 604.54,-100 637.44,-100"/>
<polygon fill="black" stroke="black" points="637.15,-103.5 647.15,-100 637.15,-96.5 637.15,-103.5"/>
<text xml:space="preserve" text-anchor="middle" x="578.12" y="-103.25" font-family="Helvetica,sans-Serif" font-size="10.00">Bearer token</text>
</g>
<!-- drive -->
<g id="node10" class="node">
<title>drive</title>
<path fill="#bbdefb" stroke="black" d="M713,-64C713,-64 670.75,-64 670.75,-64 664.75,-64 658.75,-58 658.75,-52 658.75,-52 658.75,-40 658.75,-40 658.75,-34 664.75,-28 670.75,-28 670.75,-28 713,-28 713,-28 719,-28 725,-34 725,-40 725,-40 725,-52 725,-52 725,-58 719,-64 713,-64"/>
<text xml:space="preserve" text-anchor="middle" x="691.88" y="-42.3" font-family="Helvetica,sans-Serif" font-size="11.00">Drive API</text>
</g>
<!-- oauth&#45;&gt;drive -->
<g id="edge10" class="edge">
<title>oauth&#45;&gt;drive</title>
<path fill="none" stroke="black" d="M529.29,-86.3C565.98,-77.15 613.57,-65.28 647.58,-56.8"/>
<polygon fill="black" stroke="black" points="648.24,-60.24 657.09,-54.43 646.54,-53.45 648.24,-60.24"/>
<text xml:space="preserve" text-anchor="middle" x="578.12" y="-84.24" font-family="Helvetica,sans-Serif" font-size="10.00">Bearer token</text>
</g>
<!-- consent -->
<g id="node5" class="node">
<title>consent</title>
<path fill="#ffe082" stroke="black" d="M886,-260C886,-260 805.5,-260 805.5,-260 799.5,-260 793.5,-254 793.5,-248 793.5,-248 793.5,-236 793.5,-236 793.5,-230 799.5,-224 805.5,-224 805.5,-224 886,-224 886,-224 892,-224 898,-230 898,-236 898,-236 898,-248 898,-248 898,-254 892,-260 886,-260"/>
<text xml:space="preserve" text-anchor="middle" x="845.75" y="-238.3" font-family="Helvetica,sans-Serif" font-size="11.00">2. User Consent</text>
</g>
<!-- auth_url&#45;&gt;consent -->
<g id="edge4" class="edge">
<title>auth_url&#45;&gt;consent</title>
<path fill="none" stroke="black" d="M756.84,-242C765.11,-242 773.57,-242 781.82,-242"/>
<polygon fill="black" stroke="black" points="781.78,-245.5 791.78,-242 781.78,-238.5 781.78,-245.5"/>
</g>
<!-- callback -->
<g id="node6" class="node">
<title>callback</title>
<path fill="#ffd54f" stroke="black" d="M1043.25,-260C1043.25,-260 947,-260 947,-260 941,-260 935,-254 935,-248 935,-248 935,-236 935,-236 935,-230 941,-224 947,-224 947,-224 1043.25,-224 1043.25,-224 1049.25,-224 1055.25,-230 1055.25,-236 1055.25,-236 1055.25,-248 1055.25,-248 1055.25,-254 1049.25,-260 1043.25,-260"/>
<text xml:space="preserve" text-anchor="middle" x="995.12" y="-238.3" font-family="Helvetica,sans-Serif" font-size="11.00">3. Callback + Code</text>
</g>
<!-- consent&#45;&gt;callback -->
<g id="edge5" class="edge">
<title>consent&#45;&gt;callback</title>
<path fill="none" stroke="black" d="M898.29,-242C906.34,-242 914.78,-242 923.16,-242"/>
<polygon fill="black" stroke="black" points="923.02,-245.5 933.02,-242 923.02,-238.5 923.02,-245.5"/>
</g>
<!-- tokens -->
<g id="node7" class="node">
<title>tokens</title>
<path fill="#ffca28" stroke="black" d="M1207.25,-260C1207.25,-260 1104.25,-260 1104.25,-260 1098.25,-260 1092.25,-254 1092.25,-248 1092.25,-248 1092.25,-236 1092.25,-236 1092.25,-230 1098.25,-224 1104.25,-224 1104.25,-224 1207.25,-224 1207.25,-224 1213.25,-224 1219.25,-230 1219.25,-236 1219.25,-236 1219.25,-248 1219.25,-248 1219.25,-254 1213.25,-260 1207.25,-260"/>
<text xml:space="preserve" text-anchor="middle" x="1155.75" y="-245.05" font-family="Helvetica,sans-Serif" font-size="11.00">4. Access + Refresh</text>
<text xml:space="preserve" text-anchor="middle" x="1155.75" y="-231.55" font-family="Helvetica,sans-Serif" font-size="11.00">Tokens</text>
</g>
<!-- callback&#45;&gt;tokens -->
<g id="edge6" class="edge">
<title>callback&#45;&gt;tokens</title>
<path fill="none" stroke="black" d="M1055.64,-242C1063.8,-242 1072.26,-242 1080.64,-242"/>
<polygon fill="black" stroke="black" points="1080.47,-245.5 1090.47,-242 1080.47,-238.5 1080.47,-245.5"/>
</g>
<!-- tokens&#45;&gt;oauth -->
<g id="edge7" class="edge">
<title>tokens&#45;&gt;oauth</title>
<path fill="none" stroke="black" d="M1151.88,-223.81C1141.88,-167.53 1103.13,0 996.12,0 690.88,0 690.88,0 690.88,0 619.58,0 546.24,-45.24 505.87,-74.87"/>
<polygon fill="black" stroke="black" points="503.92,-71.95 498.02,-80.75 508.12,-77.55 503.92,-71.95"/>
<text xml:space="preserve" text-anchor="middle" x="845.75" y="-3.25" font-family="Helvetica,sans-Serif" font-size="10.00">store</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

115
docs/veins/graph.html Normal file
View File

@@ -0,0 +1,115 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Graph Viewer - Veins</title>
<link rel="stylesheet" href="../architecture/styles.css" />
</head>
<body class="graph-viewer">
<header class="graph-header">
<a href="index.html" class="back-link">← Back</a>
<div class="nav-controls">
<button onclick="navigate(-1)" id="btn-prev" title="Previous"></button>
<span id="nav-position">1 / 4</span>
<button onclick="navigate(1)" id="btn-next" title="Next"></button>
</div>
<h1 id="graph-title">Loading...</h1>
<div class="graph-controls">
<button onclick="setMode('fit')">Fit</button>
<button onclick="setMode('fit-width')">Width</button>
<button onclick="setMode('fit-height')">Height</button>
<button onclick="setMode('actual-size')">100%</button>
<button onclick="downloadSvg()">↓ SVG</button>
</div>
</header>
<div class="graph-container" id="graph-container">
<img id="graph-img" src="" alt="Graph" />
</div>
<script>
const graphOrder = [
"jira",
"slack",
"google",
"mercadopago-shunt",
];
const graphs = {
"jira": {
title: "Jira Vein",
file: "jira.svg",
},
"slack": {
title: "Slack Vein",
file: "slack.svg",
},
"google": {
title: "Google Vein (OAuth)",
file: "google.svg",
},
"mercadopago-shunt": {
title: "MercadoPago Shunt",
file: "mercadopago-shunt.svg",
},
};
const params = new URLSearchParams(window.location.search);
let graphKey = params.get("g") || "jira";
let currentIndex = graphOrder.indexOf(graphKey);
if (currentIndex === -1) currentIndex = 0;
function loadGraph(key) {
const graph = graphs[key];
document.getElementById("graph-title").textContent = graph.title;
document.getElementById("graph-img").src = graph.file;
document.title = graph.title + " - Soleprint";
history.replaceState(null, "", "?g=" + key);
graphKey = key;
updateNavHints();
}
function updateNavHints() {
const idx = graphOrder.indexOf(graphKey);
const prevBtn = document.getElementById("btn-prev");
const nextBtn = document.getElementById("btn-next");
prevBtn.disabled = idx === 0;
nextBtn.disabled = idx === graphOrder.length - 1;
document.getElementById("nav-position").textContent =
idx + 1 + " / " + graphOrder.length;
}
function navigate(direction) {
const idx = graphOrder.indexOf(graphKey);
const newIdx = idx + direction;
if (newIdx >= 0 && newIdx < graphOrder.length) {
currentIndex = newIdx;
loadGraph(graphOrder[newIdx]);
}
}
function setMode(mode) {
const container = document.getElementById("graph-container");
container.className = "graph-container " + mode;
}
function downloadSvg() {
const graph = graphs[graphKey];
const link = document.createElement("a");
link.href = graph.file;
link.download = graph.file;
link.click();
}
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowLeft") navigate(-1);
else if (e.key === "ArrowRight") navigate(1);
else if (e.key === "Escape") window.location.href = "index.html";
});
loadGraph(graphOrder[currentIndex]);
setMode("fit");
</script>
</body>
</html>

172
docs/veins/index.html Normal file
View File

@@ -0,0 +1,172 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Veins & Shunts - Soleprint</title>
<link rel="stylesheet" href="../architecture/styles.css" />
</head>
<body>
<header>
<h1>Veins & Shunts</h1>
<p class="subtitle">API Connectors & Mock Services</p>
</header>
<main>
<section class="findings-section">
<p style="margin-bottom: 1rem;"><a href="../index.html" style="color: var(--accent);">← Back to Docs</a></p>
</section>
<!-- Veins -->
<section class="graph-section">
<h2 style="color: var(--accent); margin-bottom: 1rem;">Veins (Stateless API Connectors)</h2>
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
Veins are stateless connectors to external APIs. They handle authentication and provide a clean interface for the rest of the application.
</p>
</section>
<section class="graph-section" id="jira">
<div class="graph-header-row">
<h2>Jira</h2>
<a href="graph.html?g=jira" class="view-btn">View Full</a>
</div>
<a href="graph.html?g=jira" class="graph-preview">
<img src="jira.svg" alt="Jira Vein" />
</a>
<div class="graph-details">
<p>Connect to Jira Cloud for issue tracking.</p>
<h4>Capabilities</h4>
<ul>
<li>Get/create/update issues</li>
<li>JQL search</li>
<li>Transition issues between statuses</li>
<li>List projects</li>
</ul>
<h4>Auth</h4>
<p>API Token (Basic Auth with email + token)</p>
</div>
</section>
<section class="graph-section" id="slack">
<div class="graph-header-row">
<h2>Slack</h2>
<a href="graph.html?g=slack" class="view-btn">View Full</a>
</div>
<a href="graph.html?g=slack" class="graph-preview">
<img src="slack.svg" alt="Slack Vein" />
</a>
<div class="graph-details">
<p>Send messages and interact with Slack workspaces.</p>
<h4>Capabilities</h4>
<ul>
<li>Post messages to channels</li>
<li>List channels and users</li>
<li>Upload files</li>
<li>Webhook integration</li>
</ul>
<h4>Auth</h4>
<p>Bot Token (Bearer)</p>
</div>
</section>
<section class="graph-section" id="google">
<div class="graph-header-row">
<h2>Google</h2>
<a href="graph.html?g=google" class="view-btn">View Full</a>
</div>
<a href="graph.html?g=google" class="graph-preview">
<img src="google.svg" alt="Google Vein" />
</a>
<div class="graph-details">
<p>Access Google Sheets, Calendar, Drive via OAuth 2.0.</p>
<h4>Capabilities</h4>
<ul>
<li>Read/write Sheets</li>
<li>Calendar events</li>
<li>Drive file access</li>
</ul>
<h4>Auth</h4>
<p>OAuth 2.0 with refresh tokens</p>
</div>
</section>
<!-- Shunts -->
<section class="graph-section" style="margin-top: 3rem;">
<h2 style="color: var(--accent); margin-bottom: 1rem;">Shunts (Mock Connectors)</h2>
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
Shunts are fake connectors for testing. They mimic real APIs but let you control the responses.
Perfect for testing payment flows, webhook handling, and integration scenarios without needing real credentials.
</p>
</section>
<section class="graph-section" id="mercadopago">
<div class="graph-header-row">
<h2>MercadoPago Shunt</h2>
<a href="graph.html?g=mercadopago-shunt" class="view-btn">View Full</a>
</div>
<a href="graph.html?g=mercadopago-shunt" class="graph-preview">
<img src="mercadopago-shunt.svg" alt="MercadoPago Shunt" />
</a>
<div class="graph-details">
<p>Mock MercadoPago payment API for testing payment flows.</p>
<h4>Features</h4>
<ul>
<li>Fake payment creation</li>
<li>Configurable responses (approved/pending/rejected)</li>
<li>Webhook callbacks</li>
<li>Config UI to set next response</li>
</ul>
<h4>Use Case</h4>
<p>Test checkout flows without real payments. Set the shunt to return "approved" or "rejected" and verify your app handles each case.</p>
</div>
</section>
<!-- Pattern -->
<section class="tech-section">
<h2>Vein Pattern</h2>
<div class="tech-grid">
<div class="tech-column">
<h3>Structure</h3>
<ul>
<li>artery/veins/{name}/</li>
<li>__init__.py (exports)</li>
<li>client.py (API client)</li>
<li>models.py (types)</li>
<li>templates/ (test UI)</li>
</ul>
</div>
<div class="tech-column">
<h3>Base Class</h3>
<ul>
<li>Extends artery/veins/base.py</li>
<li>Common auth handling</li>
<li>Request/response logging</li>
<li>Error handling</li>
</ul>
</div>
<div class="tech-column">
<h3>OAuth</h3>
<ul>
<li>artery/oauth.py</li>
<li>Token storage</li>
<li>Refresh flow</li>
<li>Callback handling</li>
</ul>
</div>
<div class="tech-column">
<h3>Config</h3>
<ul>
<li>cfg/{room}/data/veins.json</li>
<li>Per-room credentials</li>
<li>Enable/disable veins</li>
</ul>
</div>
</div>
</section>
</main>
<footer>
<p>Soleprint - Veins & Shunts Documentation</p>
</footer>
</body>
</html>

53
docs/veins/jira.dot Normal file
View File

@@ -0,0 +1,53 @@
digraph JiraVein {
rankdir=LR;
compound=true;
fontname="Helvetica";
node [fontname="Helvetica", fontsize=11, shape=box, style="rounded,filled"];
edge [fontname="Helvetica", fontsize=10];
labelloc="t";
label="Jira Vein - API Flow";
fontsize=16;
// Client
subgraph cluster_client {
label="Soleprint";
style=filled;
color="#E8F5E9";
fillcolor="#E8F5E9";
app [label="Application", fillcolor="#C8E6C9"];
vein [label="Jira Vein\n(artery/veins/jira)", fillcolor="#A5D6A7"];
}
// Auth
subgraph cluster_auth {
label="Authentication";
style=filled;
color="#FFF8E1";
fillcolor="#FFF8E1";
token [label="API Token\n(Basic Auth)", fillcolor="#FFECB3"];
}
// Jira API
subgraph cluster_jira {
label="Jira Cloud API";
style=filled;
color="#E3F2FD";
fillcolor="#E3F2FD";
issues [label="/rest/api/3/issue", fillcolor="#BBDEFB"];
search [label="/rest/api/3/search", fillcolor="#BBDEFB"];
projects [label="/rest/api/3/project", fillcolor="#BBDEFB"];
transitions [label="/rest/api/3/issue/{id}/transitions", fillcolor="#BBDEFB"];
}
// Flow
app -> vein [label="get_issue()"];
vein -> token [label="auth"];
token -> issues [label="GET/POST"];
token -> search [label="JQL"];
token -> projects [label="list"];
token -> transitions [label="update status"];
}

115
docs/veins/jira.svg Normal file
View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.1 (0)
-->
<!-- Title: JiraVein Pages: 1 -->
<svg width="792pt" height="294pt"
viewBox="0.00 0.00 792.00 294.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 289.5)">
<title>JiraVein</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-289.5 788.25,-289.5 788.25,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="392.12" y="-266.3" font-family="Helvetica,sans-Serif" font-size="16.00">Jira Vein &#45; API Flow</text>
<g id="clust1" class="cluster">
<title>cluster_client</title>
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="8,-88 8,-168 303.5,-168 303.5,-88 8,-88"/>
<text xml:space="preserve" text-anchor="middle" x="155.75" y="-148.8" font-family="Helvetica,sans-Serif" font-size="16.00">Soleprint</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_auth</title>
<polygon fill="#fff8e1" stroke="#fff8e1" points="345.25,-88 345.25,-168 479.75,-168 479.75,-88 345.25,-88"/>
<text xml:space="preserve" text-anchor="middle" x="412.5" y="-148.8" font-family="Helvetica,sans-Serif" font-size="16.00">Authentication</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_jira</title>
<polygon fill="#e3f2fd" stroke="#e3f2fd" points="566.5,-8 566.5,-250 776.25,-250 776.25,-8 566.5,-8"/>
<text xml:space="preserve" text-anchor="middle" x="671.38" y="-230.8" font-family="Helvetica,sans-Serif" font-size="16.00">Jira Cloud API</text>
</g>
<!-- app -->
<g id="node1" class="node">
<title>app</title>
<path fill="#c8e6c9" stroke="black" d="M80.75,-132C80.75,-132 28,-132 28,-132 22,-132 16,-126 16,-120 16,-120 16,-108 16,-108 16,-102 22,-96 28,-96 28,-96 80.75,-96 80.75,-96 86.75,-96 92.75,-102 92.75,-108 92.75,-108 92.75,-120 92.75,-120 92.75,-126 86.75,-132 80.75,-132"/>
<text xml:space="preserve" text-anchor="middle" x="54.38" y="-110.3" font-family="Helvetica,sans-Serif" font-size="11.00">Application</text>
</g>
<!-- vein -->
<g id="node2" class="node">
<title>vein</title>
<path fill="#a5d6a7" stroke="black" d="M283.5,-132C283.5,-132 194.75,-132 194.75,-132 188.75,-132 182.75,-126 182.75,-120 182.75,-120 182.75,-108 182.75,-108 182.75,-102 188.75,-96 194.75,-96 194.75,-96 283.5,-96 283.5,-96 289.5,-96 295.5,-102 295.5,-108 295.5,-108 295.5,-120 295.5,-120 295.5,-126 289.5,-132 283.5,-132"/>
<text xml:space="preserve" text-anchor="middle" x="239.12" y="-117.05" font-family="Helvetica,sans-Serif" font-size="11.00">Jira Vein</text>
<text xml:space="preserve" text-anchor="middle" x="239.12" y="-103.55" font-family="Helvetica,sans-Serif" font-size="11.00">(artery/veins/jira)</text>
</g>
<!-- app&#45;&gt;vein -->
<g id="edge1" class="edge">
<title>app&#45;&gt;vein</title>
<path fill="none" stroke="black" d="M93.24,-114C115.64,-114 144.67,-114 170.86,-114"/>
<polygon fill="black" stroke="black" points="170.75,-117.5 180.75,-114 170.75,-110.5 170.75,-117.5"/>
<text xml:space="preserve" text-anchor="middle" x="137.75" y="-117.25" font-family="Helvetica,sans-Serif" font-size="10.00">get_issue()</text>
</g>
<!-- token -->
<g id="node3" class="node">
<title>token</title>
<path fill="#ffecb3" stroke="black" d="M441.75,-132C441.75,-132 382.25,-132 382.25,-132 376.25,-132 370.25,-126 370.25,-120 370.25,-120 370.25,-108 370.25,-108 370.25,-102 376.25,-96 382.25,-96 382.25,-96 441.75,-96 441.75,-96 447.75,-96 453.75,-102 453.75,-108 453.75,-108 453.75,-120 453.75,-120 453.75,-126 447.75,-132 441.75,-132"/>
<text xml:space="preserve" text-anchor="middle" x="412" y="-117.05" font-family="Helvetica,sans-Serif" font-size="11.00">API Token</text>
<text xml:space="preserve" text-anchor="middle" x="412" y="-103.55" font-family="Helvetica,sans-Serif" font-size="11.00">(Basic Auth)</text>
</g>
<!-- vein&#45;&gt;token -->
<g id="edge2" class="edge">
<title>vein&#45;&gt;token</title>
<path fill="none" stroke="black" d="M295.6,-114C315.81,-114 338.57,-114 358.5,-114"/>
<polygon fill="black" stroke="black" points="358.36,-117.5 368.36,-114 358.36,-110.5 358.36,-117.5"/>
<text xml:space="preserve" text-anchor="middle" x="324.38" y="-117.25" font-family="Helvetica,sans-Serif" font-size="10.00">auth</text>
</g>
<!-- issues -->
<g id="node4" class="node">
<title>issues</title>
<path fill="#bbdefb" stroke="black" d="M711.62,-214C711.62,-214 631.12,-214 631.12,-214 625.12,-214 619.12,-208 619.12,-202 619.12,-202 619.12,-190 619.12,-190 619.12,-184 625.12,-178 631.12,-178 631.12,-178 711.62,-178 711.62,-178 717.62,-178 723.62,-184 723.62,-190 723.62,-190 723.62,-202 723.62,-202 723.62,-208 717.62,-214 711.62,-214"/>
<text xml:space="preserve" text-anchor="middle" x="671.38" y="-192.3" font-family="Helvetica,sans-Serif" font-size="11.00">/rest/api/3/issue</text>
</g>
<!-- token&#45;&gt;issues -->
<g id="edge3" class="edge">
<title>token&#45;&gt;issues</title>
<path fill="none" stroke="black" d="M454.12,-132.07C465.56,-136.84 478.06,-141.82 489.75,-146 528.32,-159.78 572.63,-172.1 607.71,-181.06"/>
<polygon fill="black" stroke="black" points="606.74,-184.42 617.29,-183.48 608.45,-177.63 606.74,-184.42"/>
<text xml:space="preserve" text-anchor="middle" x="523.12" y="-169.6" font-family="Helvetica,sans-Serif" font-size="10.00">GET/POST</text>
</g>
<!-- search -->
<g id="node5" class="node">
<title>search</title>
<path fill="#bbdefb" stroke="black" d="M715.75,-160C715.75,-160 627,-160 627,-160 621,-160 615,-154 615,-148 615,-148 615,-136 615,-136 615,-130 621,-124 627,-124 627,-124 715.75,-124 715.75,-124 721.75,-124 727.75,-130 727.75,-136 727.75,-136 727.75,-148 727.75,-148 727.75,-154 721.75,-160 715.75,-160"/>
<text xml:space="preserve" text-anchor="middle" x="671.38" y="-138.3" font-family="Helvetica,sans-Serif" font-size="11.00">/rest/api/3/search</text>
</g>
<!-- token&#45;&gt;search -->
<g id="edge4" class="edge">
<title>token&#45;&gt;search</title>
<path fill="none" stroke="black" d="M454.07,-118.47C494.19,-122.83 555.94,-129.55 603.22,-134.69"/>
<polygon fill="black" stroke="black" points="602.77,-138.17 613.09,-135.77 603.53,-131.21 602.77,-138.17"/>
<text xml:space="preserve" text-anchor="middle" x="523.12" y="-132.09" font-family="Helvetica,sans-Serif" font-size="10.00">JQL</text>
</g>
<!-- projects -->
<g id="node6" class="node">
<title>projects</title>
<path fill="#bbdefb" stroke="black" d="M716.5,-106C716.5,-106 626.25,-106 626.25,-106 620.25,-106 614.25,-100 614.25,-94 614.25,-94 614.25,-82 614.25,-82 614.25,-76 620.25,-70 626.25,-70 626.25,-70 716.5,-70 716.5,-70 722.5,-70 728.5,-76 728.5,-82 728.5,-82 728.5,-94 728.5,-94 728.5,-100 722.5,-106 716.5,-106"/>
<text xml:space="preserve" text-anchor="middle" x="671.38" y="-84.3" font-family="Helvetica,sans-Serif" font-size="11.00">/rest/api/3/project</text>
</g>
<!-- token&#45;&gt;projects -->
<g id="edge5" class="edge">
<title>token&#45;&gt;projects</title>
<path fill="none" stroke="black" d="M453.95,-107.16C465.5,-105.39 478.1,-103.6 489.75,-102.25 527.09,-97.92 568.92,-94.53 602.92,-92.14"/>
<polygon fill="black" stroke="black" points="602.89,-95.66 612.62,-91.48 602.41,-88.67 602.89,-95.66"/>
<text xml:space="preserve" text-anchor="middle" x="523.12" y="-105.5" font-family="Helvetica,sans-Serif" font-size="10.00">list</text>
</g>
<!-- transitions -->
<g id="node7" class="node">
<title>transitions</title>
<path fill="#bbdefb" stroke="black" d="M756.25,-52C756.25,-52 586.5,-52 586.5,-52 580.5,-52 574.5,-46 574.5,-40 574.5,-40 574.5,-28 574.5,-28 574.5,-22 580.5,-16 586.5,-16 586.5,-16 756.25,-16 756.25,-16 762.25,-16 768.25,-22 768.25,-28 768.25,-28 768.25,-40 768.25,-40 768.25,-46 762.25,-52 756.25,-52"/>
<text xml:space="preserve" text-anchor="middle" x="671.38" y="-30.3" font-family="Helvetica,sans-Serif" font-size="11.00">/rest/api/3/issue/{id}/transitions</text>
</g>
<!-- token&#45;&gt;transitions -->
<g id="edge6" class="edge">
<title>token&#45;&gt;transitions</title>
<path fill="none" stroke="black" d="M449.93,-95.66C462.38,-89.91 476.49,-83.87 489.75,-79.25 516.18,-70.04 545.39,-61.89 572.48,-55.12"/>
<polygon fill="black" stroke="black" points="573.19,-58.55 582.07,-52.77 571.52,-51.75 573.19,-58.55"/>
<text xml:space="preserve" text-anchor="middle" x="523.12" y="-82.5" font-family="Helvetica,sans-Serif" font-size="10.00">update status</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,64 @@
digraph MercadoPagoShunt {
rankdir=LR;
compound=true;
fontname="Helvetica";
node [fontname="Helvetica", fontsize=11, shape=box, style="rounded,filled"];
edge [fontname="Helvetica", fontsize=10];
labelloc="t";
label="MercadoPago Shunt - Mock Payment Flow";
fontsize=16;
// Client App
subgraph cluster_client {
label="Managed Room (e.g., AMAR)";
style=filled;
color="#E8F5E9";
fillcolor="#E8F5E9";
backend [label="Backend\n(Django/FastAPI)", fillcolor="#C8E6C9"];
}
// Shunt
subgraph cluster_shunt {
label="Shunt (artery/shunts/mercadopago)";
style=filled;
color="#FFF3E0";
fillcolor="#FFF3E0";
mock_api [label="Mock API\n/payments\n/preferences", fillcolor="#FFCC80"];
config_ui [label="Config UI\n(set responses)", fillcolor="#FFB74D"];
state [label="State\n(pending payments)", fillcolor="#FFA726"];
}
// Fake responses
subgraph cluster_responses {
label="Configurable Responses";
style=dashed;
color=gray;
approved [label="approved", fillcolor="#C8E6C9"];
pending [label="pending", fillcolor="#FFF9C4"];
rejected [label="rejected", fillcolor="#FFCDD2"];
}
// Real (bypassed)
subgraph cluster_real {
label="Real MercadoPago (bypassed)";
style=dashed;
color="#BDBDBD";
real_api [label="api.mercadopago.com", fillcolor="#E0E0E0", fontcolor="#9E9E9E"];
}
// Flow
backend -> mock_api [label="POST /payments"];
mock_api -> state [label="store"];
config_ui -> state [label="configure"];
state -> approved [style=dashed];
state -> pending [style=dashed];
state -> rejected [style=dashed];
mock_api -> backend [label="webhook callback", style=dashed];
}

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.1 (0)
-->
<!-- Title: MercadoPagoShunt Pages: 1 -->
<svg width="931pt" height="240pt"
viewBox="0.00 0.00 931.00 240.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 235.5)">
<title>MercadoPagoShunt</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-235.5 926.75,-235.5 926.75,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="461.38" y="-212.3" font-family="Helvetica,sans-Serif" font-size="16.00">MercadoPago Shunt &#45; Mock Payment Flow</text>
<g id="clust1" class="cluster">
<title>cluster_client</title>
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="5.25,-16 5.25,-96 252.25,-96 252.25,-16 5.25,-16"/>
<text xml:space="preserve" text-anchor="middle" x="128.75" y="-76.8" font-family="Helvetica,sans-Serif" font-size="16.00">Managed Room (e.g., AMAR)</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_shunt</title>
<polygon fill="#fff3e0" stroke="#fff3e0" points="365.25,-13 365.25,-159 691.5,-159 691.5,-13 365.25,-13"/>
<text xml:space="preserve" text-anchor="middle" x="528.38" y="-139.8" font-family="Helvetica,sans-Serif" font-size="16.00">Shunt (artery/shunts/mercadopago)</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_responses</title>
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="712.5,-8 712.5,-196 922.75,-196 922.75,-8 712.5,-8"/>
<text xml:space="preserve" text-anchor="middle" x="817.62" y="-176.8" font-family="Helvetica,sans-Serif" font-size="16.00">Configurable Responses</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_real</title>
<polygon fill="none" stroke="#bdbdbd" stroke-dasharray="5,2" points="0,-105 0,-185 257.5,-185 257.5,-105 0,-105"/>
<text xml:space="preserve" text-anchor="middle" x="128.75" y="-165.8" font-family="Helvetica,sans-Serif" font-size="16.00">Real MercadoPago (bypassed)</text>
</g>
<!-- backend -->
<g id="node1" class="node">
<title>backend</title>
<path fill="#c8e6c9" stroke="black" d="M169.62,-60C169.62,-60 86.88,-60 86.88,-60 80.88,-60 74.88,-54 74.88,-48 74.88,-48 74.88,-36 74.88,-36 74.88,-30 80.88,-24 86.88,-24 86.88,-24 169.62,-24 169.62,-24 175.62,-24 181.62,-30 181.62,-36 181.62,-36 181.62,-48 181.62,-48 181.62,-54 175.62,-60 169.62,-60"/>
<text xml:space="preserve" text-anchor="middle" x="128.25" y="-45.05" font-family="Helvetica,sans-Serif" font-size="11.00">Backend</text>
<text xml:space="preserve" text-anchor="middle" x="128.25" y="-31.55" font-family="Helvetica,sans-Serif" font-size="11.00">(Django/FastAPI)</text>
</g>
<!-- mock_api -->
<g id="node2" class="node">
<title>mock_api</title>
<path fill="#ffcc80" stroke="black" d="M454.88,-69.25C454.88,-69.25 393.88,-69.25 393.88,-69.25 387.88,-69.25 381.88,-63.25 381.88,-57.25 381.88,-57.25 381.88,-32.75 381.88,-32.75 381.88,-26.75 387.88,-20.75 393.88,-20.75 393.88,-20.75 454.88,-20.75 454.88,-20.75 460.88,-20.75 466.88,-26.75 466.88,-32.75 466.88,-32.75 466.88,-57.25 466.88,-57.25 466.88,-63.25 460.88,-69.25 454.88,-69.25"/>
<text xml:space="preserve" text-anchor="middle" x="424.38" y="-54.8" font-family="Helvetica,sans-Serif" font-size="11.00">Mock API</text>
<text xml:space="preserve" text-anchor="middle" x="424.38" y="-41.3" font-family="Helvetica,sans-Serif" font-size="11.00">/payments</text>
<text xml:space="preserve" text-anchor="middle" x="424.38" y="-27.8" font-family="Helvetica,sans-Serif" font-size="11.00">/preferences</text>
</g>
<!-- backend&#45;&gt;mock_api -->
<g id="edge1" class="edge">
<title>backend&#45;&gt;mock_api</title>
<path fill="none" stroke="black" d="M181.83,-42.54C235.1,-43.08 317.03,-43.92 370.43,-44.46"/>
<polygon fill="black" stroke="black" points="370.15,-47.96 380.18,-44.56 370.22,-40.96 370.15,-47.96"/>
<text xml:space="preserve" text-anchor="middle" x="311.38" y="-47.53" font-family="Helvetica,sans-Serif" font-size="10.00">POST /payments</text>
</g>
<!-- mock_api&#45;&gt;backend -->
<g id="edge7" class="edge">
<title>mock_api&#45;&gt;backend</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M381.55,-32.32C372.93,-30.21 363.86,-28.35 355.25,-27.25 300.78,-20.27 238.48,-25.3 193.1,-31.31"/>
<polygon fill="black" stroke="black" points="192.87,-27.8 183.44,-32.64 193.83,-34.74 192.87,-27.8"/>
<text xml:space="preserve" text-anchor="middle" x="311.38" y="-30.5" font-family="Helvetica,sans-Serif" font-size="10.00">webhook callback</text>
</g>
<!-- state -->
<g id="node4" class="node">
<title>state</title>
<path fill="#ffa726" stroke="black" d="M671.5,-106C671.5,-106 568.5,-106 568.5,-106 562.5,-106 556.5,-100 556.5,-94 556.5,-94 556.5,-82 556.5,-82 556.5,-76 562.5,-70 568.5,-70 568.5,-70 671.5,-70 671.5,-70 677.5,-70 683.5,-76 683.5,-82 683.5,-82 683.5,-94 683.5,-94 683.5,-100 677.5,-106 671.5,-106"/>
<text xml:space="preserve" text-anchor="middle" x="620" y="-91.05" font-family="Helvetica,sans-Serif" font-size="11.00">State</text>
<text xml:space="preserve" text-anchor="middle" x="620" y="-77.55" font-family="Helvetica,sans-Serif" font-size="11.00">(pending payments)</text>
</g>
<!-- mock_api&#45;&gt;state -->
<g id="edge2" class="edge">
<title>mock_api&#45;&gt;state</title>
<path fill="none" stroke="black" d="M466.91,-54.22C489.66,-59.28 518.54,-65.69 545.01,-71.57"/>
<polygon fill="black" stroke="black" points="544.18,-74.97 554.7,-73.72 545.69,-68.14 544.18,-74.97"/>
<text xml:space="preserve" text-anchor="middle" x="516" y="-72.32" font-family="Helvetica,sans-Serif" font-size="10.00">store</text>
</g>
<!-- config_ui -->
<g id="node3" class="node">
<title>config_ui</title>
<path fill="#ffb74d" stroke="black" d="M463.5,-123C463.5,-123 385.25,-123 385.25,-123 379.25,-123 373.25,-117 373.25,-111 373.25,-111 373.25,-99 373.25,-99 373.25,-93 379.25,-87 385.25,-87 385.25,-87 463.5,-87 463.5,-87 469.5,-87 475.5,-93 475.5,-99 475.5,-99 475.5,-111 475.5,-111 475.5,-117 469.5,-123 463.5,-123"/>
<text xml:space="preserve" text-anchor="middle" x="424.38" y="-108.05" font-family="Helvetica,sans-Serif" font-size="11.00">Config UI</text>
<text xml:space="preserve" text-anchor="middle" x="424.38" y="-94.55" font-family="Helvetica,sans-Serif" font-size="11.00">(set responses)</text>
</g>
<!-- config_ui&#45;&gt;state -->
<g id="edge3" class="edge">
<title>config_ui&#45;&gt;state</title>
<path fill="none" stroke="black" d="M475.99,-100.56C496.99,-98.71 521.76,-96.54 544.75,-94.52"/>
<polygon fill="black" stroke="black" points="545.05,-98.01 554.7,-93.65 544.44,-91.03 545.05,-98.01"/>
<text xml:space="preserve" text-anchor="middle" x="516" y="-102.27" font-family="Helvetica,sans-Serif" font-size="10.00">configure</text>
</g>
<!-- approved -->
<g id="node5" class="node">
<title>approved</title>
<path fill="#c8e6c9" stroke="black" d="M839,-160C839,-160 795.25,-160 795.25,-160 789.25,-160 783.25,-154 783.25,-148 783.25,-148 783.25,-136 783.25,-136 783.25,-130 789.25,-124 795.25,-124 795.25,-124 839,-124 839,-124 845,-124 851,-130 851,-136 851,-136 851,-148 851,-148 851,-154 845,-160 839,-160"/>
<text xml:space="preserve" text-anchor="middle" x="817.12" y="-138.3" font-family="Helvetica,sans-Serif" font-size="11.00">approved</text>
</g>
<!-- state&#45;&gt;approved -->
<g id="edge4" class="edge">
<title>state&#45;&gt;approved</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M682.45,-106.43C692.47,-109.35 702.77,-112.3 712.5,-115 731.93,-120.4 753.5,-126.07 771.91,-130.81"/>
<polygon fill="black" stroke="black" points="770.9,-134.17 781.45,-133.26 772.64,-127.38 770.9,-134.17"/>
</g>
<!-- pending -->
<g id="node6" class="node">
<title>pending</title>
<path fill="#fff9c4" stroke="black" d="M834.88,-106C834.88,-106 799.38,-106 799.38,-106 793.38,-106 787.38,-100 787.38,-94 787.38,-94 787.38,-82 787.38,-82 787.38,-76 793.38,-70 799.38,-70 799.38,-70 834.88,-70 834.88,-70 840.88,-70 846.88,-76 846.88,-82 846.88,-82 846.88,-94 846.88,-94 846.88,-100 840.88,-106 834.88,-106"/>
<text xml:space="preserve" text-anchor="middle" x="817.12" y="-84.3" font-family="Helvetica,sans-Serif" font-size="11.00">pending</text>
</g>
<!-- state&#45;&gt;pending -->
<g id="edge5" class="edge">
<title>state&#45;&gt;pending</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M683.81,-88C713.99,-88 749.28,-88 775.82,-88"/>
<polygon fill="black" stroke="black" points="775.47,-91.5 785.47,-88 775.47,-84.5 775.47,-91.5"/>
</g>
<!-- rejected -->
<g id="node7" class="node">
<title>rejected</title>
<path fill="#ffcdd2" stroke="black" d="M835.62,-52C835.62,-52 798.62,-52 798.62,-52 792.62,-52 786.62,-46 786.62,-40 786.62,-40 786.62,-28 786.62,-28 786.62,-22 792.62,-16 798.62,-16 798.62,-16 835.62,-16 835.62,-16 841.62,-16 847.62,-22 847.62,-28 847.62,-28 847.62,-40 847.62,-40 847.62,-46 841.62,-52 835.62,-52"/>
<text xml:space="preserve" text-anchor="middle" x="817.12" y="-30.3" font-family="Helvetica,sans-Serif" font-size="11.00">rejected</text>
</g>
<!-- state&#45;&gt;rejected -->
<g id="edge6" class="edge">
<title>state&#45;&gt;rejected</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M682.45,-69.57C692.47,-66.65 702.77,-63.7 712.5,-61 733.07,-55.29 756.03,-49.27 775.1,-44.37"/>
<polygon fill="black" stroke="black" points="775.84,-47.79 784.66,-41.92 774.11,-41.01 775.84,-47.79"/>
</g>
<!-- real_api -->
<g id="node8" class="node">
<title>real_api</title>
<path fill="#e0e0e0" stroke="black" d="M185.38,-149C185.38,-149 71.12,-149 71.12,-149 65.12,-149 59.12,-143 59.12,-137 59.12,-137 59.12,-125 59.12,-125 59.12,-119 65.12,-113 71.12,-113 71.12,-113 185.38,-113 185.38,-113 191.38,-113 197.38,-119 197.38,-125 197.38,-125 197.38,-137 197.38,-137 197.38,-143 191.38,-149 185.38,-149"/>
<text xml:space="preserve" text-anchor="middle" x="128.25" y="-127.3" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#9e9e9e">api.mercadopago.com</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

64
docs/veins/slack.dot Normal file
View File

@@ -0,0 +1,64 @@
digraph SlackVein {
rankdir=LR;
compound=true;
fontname="Helvetica";
node [fontname="Helvetica", fontsize=11, shape=box, style="rounded,filled"];
edge [fontname="Helvetica", fontsize=10];
labelloc="t";
label="Slack Vein - API Flow";
fontsize=16;
// Client
subgraph cluster_client {
label="Soleprint";
style=filled;
color="#E8F5E9";
fillcolor="#E8F5E9";
app [label="Application", fillcolor="#C8E6C9"];
vein [label="Slack Vein\n(artery/veins/slack)", fillcolor="#A5D6A7"];
}
// Auth
subgraph cluster_auth {
label="Authentication";
style=filled;
color="#FFF8E1";
fillcolor="#FFF8E1";
token [label="Bot Token\n(Bearer)", fillcolor="#FFECB3"];
}
// Slack API
subgraph cluster_slack {
label="Slack API";
style=filled;
color="#E3F2FD";
fillcolor="#E3F2FD";
chat [label="chat.postMessage", fillcolor="#BBDEFB"];
channels [label="conversations.list", fillcolor="#BBDEFB"];
users [label="users.list", fillcolor="#BBDEFB"];
files [label="files.upload", fillcolor="#BBDEFB"];
}
// Webhooks
subgraph cluster_webhooks {
label="Incoming";
style=dashed;
color=gray;
webhook [label="Webhook URL", fillcolor="#F5F5F5"];
}
// Flow
app -> vein [label="send_message()"];
vein -> token [label="auth"];
token -> chat [label="POST"];
token -> channels [label="GET"];
token -> users [label="GET"];
token -> files [label="POST"];
vein -> webhook [label="simple post", style=dashed];
}

133
docs/veins/slack.svg Normal file
View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.1 (0)
-->
<!-- Title: SlackVein Pages: 1 -->
<svg width="746pt" height="294pt"
viewBox="0.00 0.00 746.00 294.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 289.5)">
<title>SlackVein</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-289.5 741.75,-289.5 741.75,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="368.88" y="-266.3" font-family="Helvetica,sans-Serif" font-size="16.00">Slack Vein &#45; API Flow</text>
<g id="clust1" class="cluster">
<title>cluster_client</title>
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="8,-67 8,-147 340.25,-147 340.25,-67 8,-67"/>
<text xml:space="preserve" text-anchor="middle" x="174.12" y="-127.8" font-family="Helvetica,sans-Serif" font-size="16.00">Soleprint</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_auth</title>
<polygon fill="#fff8e1" stroke="#fff8e1" points="417.25,-104 417.25,-184 551.75,-184 551.75,-104 417.25,-104"/>
<text xml:space="preserve" text-anchor="middle" x="484.5" y="-164.8" font-family="Helvetica,sans-Serif" font-size="16.00">Authentication</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_slack</title>
<polygon fill="#e3f2fd" stroke="#e3f2fd" points="597.25,-8 597.25,-250 729.75,-250 729.75,-8 597.25,-8"/>
<text xml:space="preserve" text-anchor="middle" x="663.5" y="-230.8" font-family="Helvetica,sans-Serif" font-size="16.00">Slack API</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_webhooks</title>
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="430.12,-16 430.12,-96 537.88,-96 537.88,-16 430.12,-16"/>
<text xml:space="preserve" text-anchor="middle" x="484" y="-76.8" font-family="Helvetica,sans-Serif" font-size="16.00">Incoming</text>
</g>
<!-- app -->
<g id="node1" class="node">
<title>app</title>
<path fill="#c8e6c9" stroke="black" d="M80.75,-111C80.75,-111 28,-111 28,-111 22,-111 16,-105 16,-99 16,-99 16,-87 16,-87 16,-81 22,-75 28,-75 28,-75 80.75,-75 80.75,-75 86.75,-75 92.75,-81 92.75,-87 92.75,-87 92.75,-99 92.75,-99 92.75,-105 86.75,-111 80.75,-111"/>
<text xml:space="preserve" text-anchor="middle" x="54.38" y="-89.3" font-family="Helvetica,sans-Serif" font-size="11.00">Application</text>
</g>
<!-- vein -->
<g id="node2" class="node">
<title>vein</title>
<path fill="#a5d6a7" stroke="black" d="M320.25,-111C320.25,-111 221,-111 221,-111 215,-111 209,-105 209,-99 209,-99 209,-87 209,-87 209,-81 215,-75 221,-75 221,-75 320.25,-75 320.25,-75 326.25,-75 332.25,-81 332.25,-87 332.25,-87 332.25,-99 332.25,-99 332.25,-105 326.25,-111 320.25,-111"/>
<text xml:space="preserve" text-anchor="middle" x="270.62" y="-96.05" font-family="Helvetica,sans-Serif" font-size="11.00">Slack Vein</text>
<text xml:space="preserve" text-anchor="middle" x="270.62" y="-82.55" font-family="Helvetica,sans-Serif" font-size="11.00">(artery/veins/slack)</text>
</g>
<!-- app&#45;&gt;vein -->
<g id="edge1" class="edge">
<title>app&#45;&gt;vein</title>
<path fill="none" stroke="black" d="M93.19,-93C122.02,-93 162.6,-93 197.57,-93"/>
<polygon fill="black" stroke="black" points="197.19,-96.5 207.19,-93 197.19,-89.5 197.19,-96.5"/>
<text xml:space="preserve" text-anchor="middle" x="150.88" y="-96.25" font-family="Helvetica,sans-Serif" font-size="10.00">send_message()</text>
</g>
<!-- token -->
<g id="node3" class="node">
<title>token</title>
<path fill="#ffecb3" stroke="black" d="M506.62,-148C506.62,-148 461.38,-148 461.38,-148 455.38,-148 449.38,-142 449.38,-136 449.38,-136 449.38,-124 449.38,-124 449.38,-118 455.38,-112 461.38,-112 461.38,-112 506.62,-112 506.62,-112 512.62,-112 518.62,-118 518.62,-124 518.62,-124 518.62,-136 518.62,-136 518.62,-142 512.62,-148 506.62,-148"/>
<text xml:space="preserve" text-anchor="middle" x="484" y="-133.05" font-family="Helvetica,sans-Serif" font-size="11.00">Bot Token</text>
<text xml:space="preserve" text-anchor="middle" x="484" y="-119.55" font-family="Helvetica,sans-Serif" font-size="11.00">(Bearer)</text>
</g>
<!-- vein&#45;&gt;token -->
<g id="edge2" class="edge">
<title>vein&#45;&gt;token</title>
<path fill="none" stroke="black" d="M332.48,-103.65C366.11,-109.54 407.18,-116.73 437.92,-122.11"/>
<polygon fill="black" stroke="black" points="437.21,-125.54 447.67,-123.82 438.42,-118.64 437.21,-125.54"/>
<text xml:space="preserve" text-anchor="middle" x="378.75" y="-119.15" font-family="Helvetica,sans-Serif" font-size="10.00">auth</text>
</g>
<!-- webhook -->
<g id="node8" class="node">
<title>webhook</title>
<path fill="#f5f5f5" stroke="black" d="M517.88,-60C517.88,-60 450.12,-60 450.12,-60 444.12,-60 438.12,-54 438.12,-48 438.12,-48 438.12,-36 438.12,-36 438.12,-30 444.12,-24 450.12,-24 450.12,-24 517.88,-24 517.88,-24 523.88,-24 529.88,-30 529.88,-36 529.88,-36 529.88,-48 529.88,-48 529.88,-54 523.88,-60 517.88,-60"/>
<text xml:space="preserve" text-anchor="middle" x="484" y="-38.3" font-family="Helvetica,sans-Serif" font-size="11.00">Webhook URL</text>
</g>
<!-- vein&#45;&gt;webhook -->
<g id="edge7" class="edge">
<title>vein&#45;&gt;webhook</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M332.48,-78.32C362.09,-71.17 397.46,-62.64 426.47,-55.64"/>
<polygon fill="black" stroke="black" points="427.29,-59.04 436.19,-53.29 425.65,-52.24 427.29,-59.04"/>
<text xml:space="preserve" text-anchor="middle" x="378.75" y="-76.82" font-family="Helvetica,sans-Serif" font-size="10.00">simple post</text>
</g>
<!-- chat -->
<g id="node4" class="node">
<title>chat</title>
<path fill="#bbdefb" stroke="black" d="M709.75,-214C709.75,-214 617.25,-214 617.25,-214 611.25,-214 605.25,-208 605.25,-202 605.25,-202 605.25,-190 605.25,-190 605.25,-184 611.25,-178 617.25,-178 617.25,-178 709.75,-178 709.75,-178 715.75,-178 721.75,-184 721.75,-190 721.75,-190 721.75,-202 721.75,-202 721.75,-208 715.75,-214 709.75,-214"/>
<text xml:space="preserve" text-anchor="middle" x="663.5" y="-192.3" font-family="Helvetica,sans-Serif" font-size="11.00">chat.postMessage</text>
</g>
<!-- token&#45;&gt;chat -->
<g id="edge3" class="edge">
<title>token&#45;&gt;chat</title>
<path fill="none" stroke="black" d="M518.8,-142.57C542.44,-151.36 574.65,-163.33 602.58,-173.72"/>
<polygon fill="black" stroke="black" points="601.04,-176.88 611.63,-177.09 603.48,-170.32 601.04,-176.88"/>
<text xml:space="preserve" text-anchor="middle" x="574.5" y="-170.2" font-family="Helvetica,sans-Serif" font-size="10.00">POST</text>
</g>
<!-- channels -->
<g id="node5" class="node">
<title>channels</title>
<path fill="#bbdefb" stroke="black" d="M708.25,-160C708.25,-160 618.75,-160 618.75,-160 612.75,-160 606.75,-154 606.75,-148 606.75,-148 606.75,-136 606.75,-136 606.75,-130 612.75,-124 618.75,-124 618.75,-124 708.25,-124 708.25,-124 714.25,-124 720.25,-130 720.25,-136 720.25,-136 720.25,-148 720.25,-148 720.25,-154 714.25,-160 708.25,-160"/>
<text xml:space="preserve" text-anchor="middle" x="663.5" y="-138.3" font-family="Helvetica,sans-Serif" font-size="11.00">conversations.list</text>
</g>
<!-- token&#45;&gt;channels -->
<g id="edge4" class="edge">
<title>token&#45;&gt;channels</title>
<path fill="none" stroke="black" d="M518.8,-132.29C540.36,-133.74 569.04,-135.68 595.11,-137.44"/>
<polygon fill="black" stroke="black" points="594.76,-140.93 604.97,-138.11 595.23,-133.94 594.76,-140.93"/>
<text xml:space="preserve" text-anchor="middle" x="574.5" y="-139.97" font-family="Helvetica,sans-Serif" font-size="10.00">GET</text>
</g>
<!-- users -->
<g id="node6" class="node">
<title>users</title>
<path fill="#bbdefb" stroke="black" d="M684.62,-106C684.62,-106 642.38,-106 642.38,-106 636.38,-106 630.38,-100 630.38,-94 630.38,-94 630.38,-82 630.38,-82 630.38,-76 636.38,-70 642.38,-70 642.38,-70 684.62,-70 684.62,-70 690.62,-70 696.62,-76 696.62,-82 696.62,-82 696.62,-94 696.62,-94 696.62,-100 690.62,-106 684.62,-106"/>
<text xml:space="preserve" text-anchor="middle" x="663.5" y="-84.3" font-family="Helvetica,sans-Serif" font-size="11.00">users.list</text>
</g>
<!-- token&#45;&gt;users -->
<g id="edge5" class="edge">
<title>token&#45;&gt;users</title>
<path fill="none" stroke="black" d="M518.8,-122C547.29,-115.26 588.23,-105.57 619.13,-98.26"/>
<polygon fill="black" stroke="black" points="619.62,-101.74 628.55,-96.03 618.01,-94.93 619.62,-101.74"/>
<text xml:space="preserve" text-anchor="middle" x="574.5" y="-114.76" font-family="Helvetica,sans-Serif" font-size="10.00">GET</text>
</g>
<!-- files -->
<g id="node7" class="node">
<title>files</title>
<path fill="#bbdefb" stroke="black" d="M691,-52C691,-52 636,-52 636,-52 630,-52 624,-46 624,-40 624,-40 624,-28 624,-28 624,-22 630,-16 636,-16 636,-16 691,-16 691,-16 697,-16 703,-22 703,-28 703,-28 703,-40 703,-40 703,-46 697,-52 691,-52"/>
<text xml:space="preserve" text-anchor="middle" x="663.5" y="-30.3" font-family="Helvetica,sans-Serif" font-size="11.00">files.upload</text>
</g>
<!-- token&#45;&gt;files -->
<g id="edge6" class="edge">
<title>token&#45;&gt;files</title>
<path fill="none" stroke="black" d="M519.01,-117.82C529.97,-113.1 541.84,-107.11 551.75,-100 557.05,-96.2 556.96,-93.68 561.75,-89.25 576.55,-75.55 579.88,-71.23 597.25,-61 602.4,-57.97 607.95,-55.11 613.57,-52.47"/>
<polygon fill="black" stroke="black" points="614.59,-55.85 622.3,-48.59 611.74,-49.46 614.59,-55.85"/>
<text xml:space="preserve" text-anchor="middle" x="574.5" y="-92.5" font-family="Helvetica,sans-Serif" font-size="10.00">POST</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.4 KiB