mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
dev day #99
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
%%{init: {
|
||||
'theme': 'base',
|
||||
'securityLevel': 'loose',
|
||||
'flowchart': { 'curve': 'basis' },
|
||||
'flowchart': { 'curve': 'linear', 'useMaxWidth': true, 'nodeSpacing': 36, 'rankSpacing': 48 },
|
||||
'themeVariables': {
|
||||
'primaryColor': '#0b5cff',
|
||||
'primaryTextColor': '#ffffff',
|
||||
@@ -9,7 +9,7 @@
|
||||
'tertiaryColor': '#f8fafc',
|
||||
'fontSize': '12px'
|
||||
},
|
||||
'themeCSS': '.edgePath path { stroke-dasharray: 5 5; animation: dash 24s linear infinite; } @keyframes dash { to { stroke-dashoffset: 1000; } } .cluster rect { rx:8; ry:8; }'
|
||||
'themeCSS': '.edgePath path { stroke-opacity: .6; } .cluster rect { rx:8; ry:8; }'
|
||||
}}%%
|
||||
flowchart TB
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%%{init: {"theme":"forest","flowchart":{"curve":"linear"},"themeCSS":".edgePath path { stroke-dasharray: 6 4; animation: dash 16s linear infinite; } @keyframes dash { to { stroke-dashoffset: -1000; } }" }}%%
|
||||
%%{init: {"theme":"forest","securityLevel":"loose","flowchart":{"curve":"linear","useMaxWidth":true,"nodeSpacing":35,"rankSpacing":45},"themeCSS":".edgePath path { stroke-opacity:.6 } .cluster rect { rx:8; ry:8 }" }}%%
|
||||
flowchart TB
|
||||
|
||||
classDef group fill:#eef7ff,stroke:#2b6cb0,color:#0b3a60;
|
||||
@@ -16,7 +16,7 @@ client ==>|HTTP| api
|
||||
client ==>|HTTP| rootgrp
|
||||
|
||||
subgraph PUBLIC["Public endpoints"]
|
||||
direction TB
|
||||
direction LR
|
||||
p_health["GET /health"]:::pub
|
||||
p_csrf["GET /csrf-token"]:::pub
|
||||
p_image_proxy["GET /proxy/image"]:::pub
|
||||
@@ -54,7 +54,7 @@ subgraph PUBLIC["Public endpoints"]
|
||||
end
|
||||
|
||||
subgraph PROTECTED["Protected (JWTAuth + CSRF for state)"]
|
||||
direction TB
|
||||
direction LR
|
||||
prot_sweep["POST /sweepstakes/:id/enter | POST /sweepstakes/:id/played | GET /sweepstakes/my-winnings"]:::route
|
||||
prot_eng["Engagement: GET /leaderboard, /profile, /achievements, /transactions | POST /checkin, /article-read, /redeem | PATCH /profile, /avatar"]:::route
|
||||
prot_comments["Comments: POST /comments | PUT/DELETE /comments/:id | react/unreact | unban-request | report"]:::route
|
||||
@@ -68,7 +68,7 @@ subgraph PROTECTED["Protected (JWTAuth + CSRF for state)"]
|
||||
end
|
||||
|
||||
subgraph ADMIN["Admin groups (JWT + Role: admin)"]
|
||||
direction TB
|
||||
direction LR
|
||||
ad_errors["/admin/errors: list, get, external proxies"]:::admin
|
||||
ad_comments["/admin/comments: list, status, bans, unban requests"]:::admin
|
||||
ad_comp_aliases["/admin/competition-aliases: CRUD + reorder"]:::admin
|
||||
@@ -98,7 +98,7 @@ subgraph ADMIN["Admin groups (JWT + Role: admin)"]
|
||||
end
|
||||
|
||||
subgraph ROOT["Root endpoints"]
|
||||
direction TB
|
||||
direction LR
|
||||
r_robots["GET /robots.txt"]:::root
|
||||
r_sitemap["GET /sitemap.xml"]:::root
|
||||
r_short["GET /s/:code"]:::root
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%%{init: {"theme":"forest","flowchart":{"curve":"linear"},"themeCSS":".edgePath path { stroke-dasharray: 6 4; animation: dash 16s linear infinite; } @keyframes dash { to { stroke-dashoffset: -1000; } }" }}%%
|
||||
%%{init: {"theme":"forest","securityLevel":"loose","flowchart":{"curve":"linear","useMaxWidth":true,"nodeSpacing":35,"rankSpacing":45},"themeCSS":".edgePath path { stroke-opacity:.6 } .cluster rect { rx:8; ry:8 }" }}%%
|
||||
flowchart TD
|
||||
%% Routes to Pages Mapping (from App.lazy.tsx)
|
||||
classDef page fill:#fff7ed,stroke:#f59e0b,color:#7c2d12;
|
||||
@@ -7,6 +7,7 @@ flowchart TD
|
||||
Router[BrowserRouter]:::route --> Routes:::route
|
||||
|
||||
subgraph PublicRoutes[Public Routes]
|
||||
direction LR
|
||||
R0["/"]:::route --> HomeRoute:::route --> HomePage:::page
|
||||
R1["/blog"]:::route --> BlogRoute:::route --> BlogPage:::page
|
||||
R2["/hledat"]:::route --> SearchPage:::page
|
||||
@@ -60,6 +61,7 @@ flowchart TD
|
||||
end
|
||||
|
||||
subgraph AdminRoutes[Admin Routes - guarded by ProtectedRoute]
|
||||
direction LR
|
||||
A0["/admin"]:::route --> AdminDashboardPage:::page
|
||||
A1["/admin/docs"]:::route --> AdminDocsPage:::page
|
||||
A2["/admin/o-klubu"]:::route --> AboutAdminPage:::page
|
||||
|
||||
+31
-19
@@ -20,7 +20,7 @@
|
||||
.btn.primary{background:var(--primary);border-color:var(--primary);color:#fff}
|
||||
.btn.ghost{background:transparent}
|
||||
main{padding:16px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:16px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(640px,1fr));gap:16px}
|
||||
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
|
||||
.card header{display:flex;align-items:center;gap:8px;justify-content:space-between;background:#0f131f;border-bottom:1px solid var(--border);padding:10px 12px;position:static}
|
||||
.title{display:flex;flex-direction:column;gap:4px}
|
||||
@@ -39,14 +39,15 @@
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js"></script>
|
||||
<script>
|
||||
mermaid.initialize({ startOnLoad:false, securityLevel:'loose', theme:'dark', flowchart:{ curve:'basis', useMaxWidth:true } });
|
||||
mermaid.initialize({ startOnLoad:false, securityLevel:'loose', theme:'dark', flowchart:{ curve:'linear', useMaxWidth:true } });
|
||||
|
||||
async function renderMermaidFile(mmdPath, container){
|
||||
try{
|
||||
container.innerHTML = '<div style="padding:16px;color:#9aa3b2">Loading '+mmdPath+'…</div>';
|
||||
const res = await fetch(mmdPath, { cache: 'no-store' });
|
||||
const res = await fetch(mmdPath + '?v=' + Date.now(), { cache: 'no-store' });
|
||||
if(!res.ok) throw new Error('Failed to load '+mmdPath+': '+res.status);
|
||||
const code = await res.text();
|
||||
const raw = await res.text();
|
||||
const code = raw.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||
const id = 'm-'+Math.random().toString(36).slice(2);
|
||||
const { svg } = await mermaid.render(id, code);
|
||||
container.innerHTML = svg;
|
||||
@@ -82,10 +83,13 @@
|
||||
if(!source.match(/^<svg[^>]+xmlns=/)) source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
||||
source = '<?xml version="1.0" standalone="no"?>\n'+source;
|
||||
// Inject white background and readable styles for new tab view
|
||||
const firstGt = source.indexOf('>');
|
||||
if(firstGt > 0){
|
||||
const inject = '<rect width="100%" height="100%" fill="#ffffff"/><style>text{fill:#111827}.edgePath path,.flowchart-link{stroke:#334155}</style>';
|
||||
source = source.slice(0, firstGt+1) + inject + source.slice(firstGt+1);
|
||||
const svgStart = source.indexOf('<svg');
|
||||
if(svgStart !== -1){
|
||||
const svgTagEnd = source.indexOf('>', svgStart);
|
||||
if(svgTagEnd !== -1){
|
||||
const inject = '<rect width="100%" height="100%" fill="#ffffff"/><style>text{fill:#111827}.edgePath path,.flowchart-link{stroke:#334155}</style>';
|
||||
source = source.slice(0, svgTagEnd+1) + inject + source.slice(svgTagEnd+1);
|
||||
}
|
||||
}
|
||||
const blob = new Blob([source], { type:'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -95,8 +99,7 @@
|
||||
|
||||
const ALL_DIAGRAMS = [
|
||||
// System & DB
|
||||
{ id:'system-clean', label:'System Overview (Clean)', file:'system-overall-clean.mmd', cat:'System', tags:['overview','recommended','big'] },
|
||||
{ id:'system', label:'System Overview (Classic)', file:'system-overall.mmd', cat:'System', tags:['overview','big'], defaultWires:'faint' },
|
||||
{ id:'system-clean', label:'System Overview', file:'system-overall-clean.mmd', cat:'System', tags:['overview','recommended','big'] },
|
||||
{ id:'db-er', label:'Database ER', file:'db-er.mmd', cat:'System', tags:['db'] },
|
||||
{ id:'db-models', label:'Database Models', file:'db-models.mmd', cat:'System', tags:['db'] },
|
||||
// Backend
|
||||
@@ -107,15 +110,13 @@
|
||||
{ id:'auth', label:'Auth Flow', file:'auth-flow.mmd', cat:'Backend', tags:['auth','flow'] },
|
||||
{ id:'err-flow', label:'Error Tracking Flow', file:'error-tracking-flow.mmd', cat:'Backend', tags:['errors','flow'] },
|
||||
// Frontend
|
||||
{ id:'fe-everything', label:'Frontend — Everything (Big)', file:'frontend-everything.mmd', cat:'Frontend', tags:['overview','big'], defaultWires:'faint' },
|
||||
{ id:'fe-overall', label:'Frontend — Overall', file:'frontend-overall.mmd', cat:'Frontend', tags:['architecture'] },
|
||||
{ id:'fe-everything', label:'Frontend — Everything (Big)', file:'frontend-everything.mmd', cat:'Frontend', tags:['overview','big','recommended'], defaultWires:'faint' },
|
||||
{ id:'fe-overall', label:'Frontend — Overall', file:'frontend-overall.mmd', cat:'Frontend', tags:['architecture','recommended'] },
|
||||
{ id:'fe-routes', label:'Frontend — Routes', file:'frontend-routes.mmd', cat:'Frontend', tags:['routes'] },
|
||||
{ id:'fe-home', label:'Frontend — Homepage', file:'frontend-homepage.mmd', cat:'Frontend', tags:['homepage'] },
|
||||
{ id:'fe-modules', label:'Frontend — Modules', file:'frontend-modules.mmd', cat:'Frontend', tags:['modules'] },
|
||||
{ id:'fe-arch', label:'Frontend — Provider Tree', file:'frontend-architecture.mmd', cat:'Frontend', tags:['providers'] },
|
||||
{ id:'fe-api', label:'Frontend — API Map', file:'frontend-api-map.mmd', cat:'Frontend', tags:['api'] },
|
||||
// Admin
|
||||
{ id:'admin-overall', label:'Admin — Overall', file:'admin-overall.mmd', cat:'Admin', tags:['admin','overview'], defaultWires:'faint' },
|
||||
{ id:'admin-overall', label:'Admin — Overall', file:'admin-overall.mmd', cat:'Admin', tags:['admin','overview','recommended'], defaultWires:'faint' },
|
||||
{ id:'scoreboard', label:'Scoreboard Flow', file:'scoreboard-flow.mmd', cat:'Admin', tags:['scoreboard','flow'] },
|
||||
{ id:'newsletter', label:'Newsletter Flow', file:'newsletter-flow.mmd', cat:'Admin', tags:['newsletter','flow'] },
|
||||
{ id:'comments', label:'Comments Flow', file:'comments-flow.mmd', cat:'Admin', tags:['comments','flow'] },
|
||||
@@ -145,7 +146,8 @@
|
||||
|
||||
const tb = document.createElement('div'); tb.className='toolbar';
|
||||
tb.innerHTML = `
|
||||
<label><input type="checkbox" class="fit" checked> Fit width</label>
|
||||
<label style="display:inline-flex;align-items:center;gap:8px"><input type="checkbox" class="fit" checked> Fit width</label>
|
||||
<label style="display:inline-flex;align-items:center;gap:6px">Zoom <input class="zoom" type="range" min="50" max="300" value="100" style="width:140px"></label>
|
||||
<a class="btn ghost src" href="${d.file}" target="_blank">Source</a>
|
||||
<span class="sp"></span>
|
||||
<button class="btn open">Open SVG in new tab</button>
|
||||
@@ -161,9 +163,17 @@
|
||||
const svg = container?.querySelector('svg');
|
||||
if(!svg) return;
|
||||
const fit = card.querySelector('.fit');
|
||||
if(fit && fit.checked){ svg.style.width='100%'; svg.style.height='auto'; } else { svg.style.width=''; svg.style.height=''; }
|
||||
svg.style.transformOrigin = '';
|
||||
svg.style.transform = '';
|
||||
const zoom = card.querySelector('.zoom');
|
||||
if(fit && fit.checked){
|
||||
svg.style.width='100%'; svg.style.height='auto';
|
||||
svg.style.transformOrigin = '';
|
||||
svg.style.transform = '';
|
||||
} else {
|
||||
svg.style.width=''; svg.style.height='';
|
||||
const z = Math.max(50, Math.min(300, parseInt(zoom?.value || '100', 10)));
|
||||
svg.style.transformOrigin = 'top left';
|
||||
svg.style.transform = 'scale('+(z/100)+')';
|
||||
}
|
||||
}
|
||||
|
||||
function wireCardControls(card, file){
|
||||
@@ -173,7 +183,9 @@
|
||||
const openBtn = card.querySelector('.open');
|
||||
const refresh = card.querySelector('.refresh');
|
||||
const download = card.querySelector('.download');
|
||||
const zoom = card.querySelector('.zoom');
|
||||
fit.addEventListener('change', () => applyFitZoomFor(card));
|
||||
zoom.addEventListener('input', () => applyFitZoomFor(card));
|
||||
openBtn.addEventListener('click', () => openSVGInNewTab(diag));
|
||||
refresh.addEventListener('click', async () => { diag.dataset.rendered=''; await renderMermaidFile(file, diag); diag.dataset.rendered='1'; applyFitZoomFor(card); });
|
||||
download.addEventListener('click', () => downloadSVGOf(diag, (file.replace('.mmd','')||'diagram')+'.svg'));
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
%%{init: {"theme":"forest","flowchart":{"curve":"linear"},"themeCSS":".edgePath path { stroke-dasharray: 6 4; animation: dash 16s linear infinite; } @keyframes dash { to { stroke-dashoffset: -1000; } }" }}%%
|
||||
%%{init: {"theme":"forest","securityLevel":"loose","flowchart":{"curve":"linear","useMaxWidth":true,"nodeSpacing":40,"rankSpacing":50},"themeCSS":".edgePath path { stroke-opacity:.6 } .cluster rect { rx:8; ry:8 }" }}%%
|
||||
flowchart LR
|
||||
|
||||
classDef client fill:#f1f5f9,stroke:#334155,color:#0f172a;
|
||||
classDef fe fill:#fff7ed,stroke:#f59e0b,color:#7c2d12;
|
||||
classDef be fill:#ecfdf5,stroke:#16a34a,color:#065f46;
|
||||
classDef db fill:#e3f2fd,stroke:#1e88e5,color:#0c4a6e;
|
||||
classDef ext fill:#f5f3ff,stroke:#8b5cf6,color:#4c1d95;
|
||||
classDef stat fill:#e2e8f0,stroke:#475569,color:#111827;
|
||||
classDef client fill:#f1f5f9,stroke:#334155,color:#0f172a
|
||||
classDef fe fill:#fff7ed,stroke:#f59e0b,color:#7c2d12
|
||||
classDef be fill:#ecfdf5,stroke:#16a34a,color:#065f46
|
||||
classDef db fill:#e3f2fd,stroke:#1e88e5,color:#0c4a6e
|
||||
classDef ext fill:#f5f3ff,stroke:#8b5cf6,color:#4c1d95
|
||||
classDef stat fill:#e2e8f0,stroke:#475569,color:#111827
|
||||
|
||||
U((User Browser)):::client
|
||||
FE[Frontend (React app)]:::fe
|
||||
API[Backend API (Go + Gin)\n/api/v1]:::be
|
||||
DB[(PostgreSQL DB)]:::db
|
||||
STATIC[Static & Uploads\n/assets, /uploads]:::stat
|
||||
EXT[External Services\n(SMTP, Error Receiver, Umami, FACR, Zonerama, YouTube)]:::ext
|
||||
U(("User Browser"))
|
||||
FE["Frontend (React app)"]
|
||||
API["Backend API (Go + Gin)<br/>/api/v1"]
|
||||
DB[(PostgreSQL DB)]
|
||||
STATIC["Static & Uploads<br/>/assets, /uploads"]
|
||||
EXT["External Services<br/>(SMTP, Error Receiver, Umami, FACR, Zonerama, YouTube)"]
|
||||
|
||||
class U client
|
||||
class FE fe
|
||||
class API be
|
||||
class DB db
|
||||
class STATIC stat
|
||||
class EXT ext
|
||||
|
||||
U --> FE
|
||||
FE ==>|HTTP| API
|
||||
|
||||
+40
-40
@@ -1,4 +1,4 @@
|
||||
%%{init: {"theme":"forest","flowchart":{"curve":"linear"},"themeCSS":"svg { font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; } .edgePath path { stroke-dasharray: 6 4; animation: dash 16s linear infinite; } .animated-edge path { stroke-dasharray: 6 4; animation: dash 16s linear infinite; } @keyframes dash { to { stroke-dashoffset: -1000; } } .ext > rect, .ext > polygon, .ext > path { stroke: #7e57c2; } .db > rect, .db > polygon, .db > path { fill: #e3f2fd; stroke: #1e88e5; } .svc > rect, .svc > polygon, .svc > path { fill: #e8f5e9; stroke: #43a047; } .fe > rect, .fe > polygon, .fe > path { fill: #fff8e1; stroke: #f9a825; } .ctrl > rect, .ctrl > polygon, .ctrl > path { fill: #f3e5f5; stroke: #8e24aa; } .mid > rect, .mid > polygon, .mid > path { fill: #e0f2f1; stroke: #00897b; } .model > rect, .model > polygon, .model > path { fill: #ede7f6; stroke: #5e35b1; } .route > rect, .route > polygon, .route > path { fill: #e8eaf6; stroke: #3f51b5; }" }}%%
|
||||
%%{init: {"theme":"forest","securityLevel":"loose","flowchart":{"curve":"linear","useMaxWidth":true,"nodeSpacing":40,"rankSpacing":50},"themeCSS":"svg { font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; } .edgePath path { stroke-opacity:.6 } .ext > rect, .ext > polygon, .ext > path { stroke: #7e57c2; } .db > rect, .db > polygon, .db > path { fill: #e3f2fd; stroke: #1e88e5; } .svc > rect, .svc > polygon, .svc > path { fill: #e8f5e9; stroke: #43a047; } .fe > rect, .fe > polygon, .fe > path { fill: #fff8e1; stroke: #f9a825; } .ctrl > rect, .ctrl > polygon, .ctrl > path { fill: #f3e5f5; stroke: #8e24aa; } .mid > rect, .mid > polygon, .mid > path { fill: #e0f2f1; stroke: #00897b; } .model > rect, .model > polygon, .model > path { fill: #ede7f6; stroke: #5e35b1; } .route > rect, .route > polygon, .route > path { fill: #e8eaf6; stroke: #3f51b5; } .cluster rect { rx:8; ry:8 }" }}%%
|
||||
flowchart TB
|
||||
|
||||
%% ========================= Docker & Runtime =========================
|
||||
@@ -37,13 +37,13 @@ subgraph DOCKER["Docker Compose (Local Dev/Prod)"]
|
||||
end
|
||||
|
||||
user_browser((User Browser)):::ext
|
||||
user_browser ==>|HTTP 80| docker_frontend:::animated-edge
|
||||
user_browser -.->|dev direct (HTTP 8080)| docker_backend
|
||||
user_browser ==>|HTTP 80| fe_3000
|
||||
user_browser -.->|dev direct :8080| be_8080
|
||||
|
||||
%% ========================= Backend (Go/Gin) =========================
|
||||
subgraph BACKEND["Backend Service (Golang + Gin) :8080"]
|
||||
direction TB
|
||||
cfg[Config (internal/config.Config)\n- APP_ENV/PORT/DEBUG\n- DATABASE_URL (GORM)\n- JWT_SECRET/EXP\n- ALLOWED_ORIGINS (CORS)\n- UPLOAD_DIR/MAX_UPLOAD_SIZE\n- SMTP_* (Email)\n- FRONTEND_BASE_URL\n- PUBLIC_API_BASE_URL\n- ERROR_INGEST_URL/TOKEN\n- FACR_SCRAPER_BASE_URL\n- UMAMI_*\n- CLAMAV_* (optional)]
|
||||
cfg["Config (internal/config.Config)<br/>- APP_ENV/PORT/DEBUG<br/>- DATABASE_URL (GORM)<br/>- JWT_SECRET/EXP<br/>- ALLOWED_ORIGINS (CORS)<br/>- UPLOAD_DIR/MAX_UPLOAD_SIZE<br/>- SMTP_* (Email)<br/>- FRONTEND_BASE_URL<br/>- PUBLIC_API_BASE_URL<br/>- ERROR_INGEST_URL/TOKEN<br/>- FACR_SCRAPER_BASE_URL<br/>- UMAMI_*<br/>- CLAMAV_* (optional)"]
|
||||
logger[Logger (pkg/logger)]
|
||||
db_init[[InitDB() + AutoMigrate()]]:::db
|
||||
email_svc[EmailService (pkg/email)]:::svc
|
||||
@@ -77,42 +77,42 @@ subgraph BACKEND["Backend Service (Golang + Gin) :8080"]
|
||||
|
||||
subgraph controllers[Controllers]
|
||||
direction TB
|
||||
c_auth[AuthController\n/login,/logout,/register,/me\n/password-reset]
|
||||
c_contact[ContactController\n/contact + newsletter + admin forwarding]
|
||||
c_auth["AuthController<br/>/login,/logout,/register,/me<br/>/password-reset"]
|
||||
c_contact["ContactController<br/>/contact + newsletter + admin forwarding"]
|
||||
c_pass[PasswordController]
|
||||
c_ai[AIController\n/ai/blog,/ai/about,/ai/css,/ai/instagram]
|
||||
c_score[ScoreboardController\n/public + admin timer/sponsors/qr]
|
||||
c_ai["AIController<br/>/ai/blog,/ai/about,/ai/css,/ai/instagram"]
|
||||
c_score["ScoreboardController<br/>/public + admin timer/sponsors/qr"]
|
||||
c_about[AboutController]
|
||||
c_gallery[GalleryController\n/Zonerama profile/albums/picks]
|
||||
c_files[FilesController\n/list/unused/duplicates/usage\n/scan/refresh-tracking/delete]
|
||||
c_gallery["GalleryController<br/>/Zonerama profile/albums/picks"]
|
||||
c_files["FilesController<br/>/list/unused/duplicates/usage<br/>/scan/refresh-tracking/delete"]
|
||||
c_notify[NotificationsController]
|
||||
c_email[EmailController\n/open.gif/click/unsubscribe/stats]
|
||||
c_prefetch[PrefetchController\n/status/trigger]
|
||||
c_seo[SEOController\n/seo (public) + robots.txt + sitemap]
|
||||
c_nav[NavigationController\n/navigation + social-links + admin CRUD]
|
||||
c_poll[PollController\n/public vote/results + admin]
|
||||
c_sw[SweepstakesController\n/public current/visual + admin CRUD/finalize]
|
||||
c_cloth[ClothingController\n/public + admin CRUD]
|
||||
c_pec[PageElementConfigController\n/public + admin CRUD/batch]
|
||||
c_article[ArticleController\n/create + match-link]
|
||||
c_base[BaseController\n/health, uploads, categories, teams, players, matches, standings, zonerama, settings, shortlinks(public)]
|
||||
c_myu[MyUIbrixController\n/validate,/preview,/optimize]
|
||||
c_editor[EditorPreviewController\n/preview state + variants]
|
||||
c_short[ShortLinkController\n/public create + admin + redirect /s/:code]
|
||||
c_comment[CommentController\n/public list + CRUD + reactions\nban/unban/report (admin)]
|
||||
c_eng[EngagementController\n/rewards/leaderboard/profile/actions]
|
||||
c_facr[FACRController\n/facr club search/info/table]
|
||||
c_yt[YouTubeController\n/youtube/videos]
|
||||
c_umami[UmamiController\n/config + admin initialize/stats]
|
||||
c_error[ErrorController\n/errors ingest + admin + external]
|
||||
c_email["EmailController<br/>/open.gif/click/unsubscribe/stats"]
|
||||
c_prefetch["PrefetchController<br/>/status/trigger"]
|
||||
c_seo["SEOController<br/>/seo (public) + robots.txt + sitemap"]
|
||||
c_nav["NavigationController<br/>/navigation + social-links + admin CRUD"]
|
||||
c_poll["PollController<br/>/public vote/results + admin"]
|
||||
c_sw["SweepstakesController<br/>/public current/visual + admin CRUD/finalize"]
|
||||
c_cloth["ClothingController<br/>/public + admin CRUD"]
|
||||
c_pec["PageElementConfigController<br/>/public + admin CRUD/batch"]
|
||||
c_article["ArticleController<br/>/create + match-link"]
|
||||
c_base["BaseController<br/>/health, uploads, categories, teams, players, matches, standings, zonerama, settings, shortlinks(public)"]
|
||||
c_myu["MyUIbrixController<br/>/validate,/preview,/optimize"]
|
||||
c_editor["EditorPreviewController<br/>/preview state + variants"]
|
||||
c_short["ShortLinkController<br/>/public create + admin + redirect /s/:code"]
|
||||
c_comment["CommentController<br/>/public list + CRUD + reactions<br/>ban/unban/report (admin)"]
|
||||
c_eng["EngagementController<br/>/rewards/leaderboard/profile/actions"]
|
||||
c_facr["FACRController<br/>/facr club search/info/table"]
|
||||
c_yt["YouTubeController<br/>/youtube/videos"]
|
||||
c_umami["UmamiController<br/>/config + admin initialize/stats"]
|
||||
c_error["ErrorController<br/>/errors ingest + admin + external"]
|
||||
end
|
||||
|
||||
subgraph services[Services & Jobs]
|
||||
direction TB
|
||||
s_errrep[ErrorReporter]
|
||||
s_prefetch[Prefetcher\nStartPrefetcher(target)]
|
||||
s_prefetch["Prefetcher<br/>StartPrefetcher(target)"]
|
||||
s_nlsched[NewsletterScheduler]
|
||||
s_nlauto[NewsletterAutomation\nweekly, reminders, results]
|
||||
s_nlauto["NewsletterAutomation<br/>weekly, reminders, results"]
|
||||
s_sweep[SweepstakesScheduler]
|
||||
s_umami[UmamiService]
|
||||
s_facr[FACRService]
|
||||
@@ -213,8 +213,8 @@ subgraph BACKEND["Backend Service (Golang + Gin) :8080"]
|
||||
errors_admin["Error Review Admin UI/API: errors.tdvorak.dev"]:::ext
|
||||
umami_ext["Umami Analytics server"]:::ext
|
||||
|
||||
s_facr <---> facr_ext:::animated-edge
|
||||
s_errrep --> errors_ingest:::animated-edge
|
||||
s_facr <---> facr_ext
|
||||
s_errrep --> errors_ingest
|
||||
c_error <---> errors_admin
|
||||
s_umami <---> umami_ext
|
||||
|
||||
@@ -228,7 +228,7 @@ subgraph BACKEND["Backend Service (Golang + Gin) :8080"]
|
||||
prometheus --- user_browser
|
||||
end
|
||||
|
||||
user_browser ==>|HTTP /api/v1| api_grp:::animated-edge
|
||||
user_browser ==>|HTTP /api/v1| api_grp
|
||||
user_browser ==>|HTTP /robots.txt, /sitemap.xml, /s/:code| root_grp
|
||||
|
||||
%% ========================= Frontend (React) =========================
|
||||
@@ -241,7 +241,7 @@ subgraph FRONTEND[Frontend (React + ChakraUI)]
|
||||
p_home[HomePage /]
|
||||
p_blog[BlogPage /blog]
|
||||
p_newslist[ArticlesListPage]
|
||||
p_article[ArticleDetailPage /news/:slug | /articles/:id]
|
||||
p_article["ArticleDetailPage /news/:slug | /articles/:id"]
|
||||
p_about[AboutPage /o-klubu]
|
||||
p_club[ClubPage /klub]
|
||||
p_calendar[CalendarPage /kalendar]
|
||||
@@ -315,9 +315,9 @@ subgraph FRONTEND[Frontend (React + ChakraUI)]
|
||||
end
|
||||
|
||||
%% FE -> BE API mappings (high level)
|
||||
fe_router -->|services/api.ts| api_grp:::animated-edge
|
||||
fe_router -->|services/api.ts| api_grp
|
||||
p_blog -->|GET /articles| api_grp
|
||||
p_article -->|GET /articles/slug/:slug, /articles/:id\nPOST /articles/:id/read| api_grp
|
||||
p_article -->|GET /articles/slug/:slug, /articles/:id<br/>POST /articles/:id/read| api_grp
|
||||
p_home -->|GET /articles/featured, /matches, /standings, /settings, /navigation| api_grp
|
||||
p_matches -->|GET /matches,/standings| api_grp
|
||||
p_match -->|GET /matches/:id| api_grp
|
||||
@@ -334,7 +334,7 @@ subgraph FRONTEND[Frontend (React + ChakraUI)]
|
||||
p_short -->|GET /s/:code (root)| root_grp
|
||||
|
||||
%% Admin flows
|
||||
a_articles[ArticlesAdminPage] -->|POST/PUT/DELETE /articles\n/link-match| api_grp
|
||||
a_articles[ArticlesAdminPage] -->|POST/PUT/DELETE /articles<br/>/link-match| api_grp
|
||||
a_matches -->|GET /admin/matches| api_grp
|
||||
a_comments -->|GET/PATCH /admin/comments| api_grp
|
||||
a_navigation -->|CRUD /admin/navigation| api_grp
|
||||
@@ -347,7 +347,7 @@ subgraph FRONTEND[Frontend (React + ChakraUI)]
|
||||
a_analytics -->|/admin/umami| api_grp
|
||||
|
||||
%% FE error reporting & analytics
|
||||
fe_router -->|POST /errors (ErrorReporter)| api_grp:::animated-edge
|
||||
fe_router -->|POST /errors (ErrorReporter)| api_grp
|
||||
fe_router -->|GET /umami/config| api_grp
|
||||
|
||||
end
|
||||
@@ -358,7 +358,7 @@ subgraph PORTS[Ports & CORS]
|
||||
port_be[Backend :8080]
|
||||
port_fe[Frontend :3000 -> :80]
|
||||
port_db[Postgres :5432]
|
||||
cors[CORS AllowedOrigins\n- http://localhost:3000\n- http://localhost:8080\n+ FrontendBaseURL origin\n+ "*" optional in dev]
|
||||
cors["CORS AllowedOrigins<br/>- http://localhost:3000<br/>- http://localhost:8080<br/>+ FrontendBaseURL origin<br/>+ * optional in dev"]
|
||||
end
|
||||
port_be --- docker_backend
|
||||
port_fe --- docker_frontend
|
||||
|
||||
Reference in New Issue
Block a user