Files
MyClub/diagrams/system-overall.mmd
T
Tomas Dvorak f5b6f83974 dev day #99
2025-11-21 08:44:44 +01:00

379 lines
13 KiB
Plaintext

%%{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 =========================
subgraph DOCKER["Docker Compose (Local Dev/Prod)"]
direction TB
docker_net([Bridge Network: fotbal-network]):::svc
docker_vol1["Volume: postgres_data"]:::svc
docker_vol2["Bind: ./uploads -> /app/uploads"]:::svc
docker_vol3["Bind: ./cache -> /app/cache"]:::svc
subgraph docker_backend["backend (Go) container"]
direction TB
be_8080["Expose 8080:8080"]
be_env[".env + overrides"]
be_cmd["command: ./main"]
end
subgraph docker_frontend["frontend (Nginx) container"]
direction TB
fe_3000["Expose 3000:80"]
fe_env[".env.frontend"]
end
subgraph docker_db["postgres:15-alpine"]
direction TB
db_5432["Expose 5432:5432"]
db_env["POSTGRES_* env"]
end
docker_net --- docker_backend
docker_net --- docker_frontend
docker_net --- docker_db
docker_vol1 --- docker_db
docker_vol2 --- docker_backend
docker_vol3 --- docker_backend
end
user_browser((User Browser)):::ext
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)<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
subgraph middleware[Middleware]
direction TB
mw_reqid[RequestID]
mw_logger[RequestLogger]
mw_recovery[CustomRecoveryWithReporter]
mw_errstatus[ErrorStatusReporter]
mw_sanitize[SanitizeHeaders]
mw_dbctx[DBContext (req ctx timeout)]
mw_size[RequestSizeLimit (2MB)]
mw_ct[ValidateContentType]
mw_csrf[CSRFProtection (protected)]
mw_rate[RateLimit (per-route)]
mw_sec[SecurityHeaders + AssetCacheControl]
end
prometheus["GET /metrics (promhttp)"]
static1["Static: /uploads -> UPLOAD_DIR"]
static2["Static: /cache -> ./cache"]
static3["Static: /dist -> ./static"]
static4["Static: /premium-assets -> ./pro"]
subgraph router["Router"]
direction TB
api_grp["/api/v1"]:::route
root_grp["/root/"]:::route
end
subgraph controllers[Controllers]
direction TB
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<br/>/ai/blog,/ai/about,/ai/css,/ai/instagram"]
c_score["ScoreboardController<br/>/public + admin timer/sponsors/qr"]
c_about[AboutController]
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<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<br/>StartPrefetcher(target)"]
s_nlsched[NewsletterScheduler]
s_nlauto["NewsletterAutomation<br/>weekly, reminders, results"]
s_sweep[SweepstakesScheduler]
s_umami[UmamiService]
s_facr[FACRService]
s_cache[CacheService]
s_logo[LogoCache]
s_filetrk[FileTracker]
s_imgopt[ImageOptimizer]
s_bad[BadWords/Spam]
s_setup[SetupService]
end
subgraph models[Models (GORM)]
direction LR
m_settings[Settings]
m_user[User]
m_article[Article]
m_scoreboard[ScoreboardState]
m_compalias[CompetitionAlias]
m_team[Team]
m_player[Player]
m_contact_cat[ContactCategory]
m_contact[Contact]
m_contact_msg[ContactMessage]
m_news[NewsletterSubscription]
m_sponsor[Sponsor]
m_cloth[Clothing]
m_poll[Poll + PollOption + PollVote]
m_nav[NavigationItem + SocialLink]
m_pageel[PageElementConfig]
m_short[ShortLink + LinkClick]
m_comment[Comment + Reaction + Ban + UnbanRequest + Report]
m_profile[UserProfile]
m_points[PointsTransaction]
m_ach[Achievement + UserAchievement]
m_reward[RewardItem + RewardRedemption]
m_over[MatchOverride + TeamLogoOverride]
m_sweep[Sweepstake + Prize + Entry + Winner]
m_up[UploadedFile + FileUsage]
m_error[ErrorEvent]
end
%% wiring inside backend
cfg --> db_init
cfg --> email_svc
router --> middleware
api_grp --> controllers
root_grp --> c_seo
root_grp --> c_short
api_grp --> c_auth
api_grp --> c_contact
api_grp --> c_pass
api_grp --> c_ai
api_grp --> c_score
api_grp --> c_about
api_grp --> c_gallery
api_grp --> c_files
api_grp --> c_notify
api_grp --> c_email
api_grp --> c_prefetch
api_grp --> c_seo
api_grp --> c_nav
api_grp --> c_poll
api_grp --> c_sw
api_grp --> c_cloth
api_grp --> c_pec
api_grp --> c_article
api_grp --> c_base
api_grp --> c_myu
api_grp --> c_editor
api_grp --> c_short
api_grp --> c_comment
api_grp --> c_eng
api_grp --> c_facr
api_grp --> c_yt
api_grp --> c_umami
api_grp --> c_error
%% controllers -> models
controllers -->|GORM| models
db_init ==>|Postgres conn| DB["PostgreSQL :5432 / fotbal_club"]:::db
models ==>|tables| DB
%% services wiring
s_prefetch -.->|GET public endpoints| api_grp
s_nlsched --> s_nlauto
s_nlauto --> email_svc
s_sweep --> email_svc
s_filetrk --> m_up
s_imgopt --> m_up
s_errrep --> c_error
s_umami --> c_umami
s_facr --> c_facr
email_svc -->|SMTP| smtp[(SMTP Provider)]:::ext
%% externals
facr_ext["FACR Scraper :8081"]:::ext
errors_ingest["Error Receiver: errors.tdvorak.dev/api/v1/errors or local :8083"]:::ext
errors_admin["Error Review Admin UI/API: errors.tdvorak.dev"]:::ext
umami_ext["Umami Analytics server"]:::ext
s_facr <---> facr_ext
s_errrep --> errors_ingest
c_error <---> errors_admin
s_umami <---> umami_ext
%% static serving
static1 --- user_browser
static2 --- user_browser
static3 --- user_browser
static4 --- user_browser
%% metrics
prometheus --- user_browser
end
user_browser ==>|HTTP /api/v1| api_grp
user_browser ==>|HTTP /robots.txt, /sitemap.xml, /s/:code| root_grp
%% ========================= Frontend (React) =========================
subgraph FRONTEND[Frontend (React + ChakraUI)]
direction TB
fe_router[React Router (src/App.tsx)]:::fe
subgraph fe_public[Public Pages]
direction LR
p_home[HomePage /]
p_blog[BlogPage /blog]
p_newslist[ArticlesListPage]
p_article["ArticleDetailPage /news/:slug | /articles/:id"]
p_about[AboutPage /o-klubu]
p_club[ClubPage /klub]
p_calendar[CalendarPage /kalendar]
p_actcal[ActivitiesCalendarPage /aktivity]
p_tables[TablesPage /tabulky]
p_matches[MatchesPage /zapasy]
p_match[MatchDetailPage /zapas/:id]
p_players[PlayersPage /hraci]
p_player[PlayerDetailPage /hraci/:id]
p_sponsors[SponsorsPage /sponzori]
p_contact[ContactPage /kontakt]
p_gallery[GalleryPage /galerie]
p_album[AlbumDetailPage /galerie/album/:id]
p_videos[VideosPage /videa]
p_clothing[ClothingPage /obleceni]
p_polls[PollsPage /ankety]
p_search[SearchPage /hledat]
p_short[ShortRedirectPage /s/:code]
p_over_sb[OverlayScoreboardPage /overlay/scoreboard]
p_over_sp[OverlaySponsorsPage /overlay/sponsors]
p_cookies[CookiePolicyPage]
p_terms[TermsPage]
p_privacy[PrivacyPolicyPage]
p_notfound[NotFoundPage *]
end
subgraph fe_auth[Auth & Setup]
direction LR
p_login[AuthPage /login]
p_register[RegisterPage /register]
p_forgot[ForgotPasswordPage /forgot-password]
p_reset[ResetPasswordPage /reset-password]
p_setup[SetupPage /setup]
p_style[StylePreviewPage /setup/styl]
p_news_unsub[NewsletterUnsubscribePage]
p_news_prefs[NewsletterPreferencesPage]
end
subgraph fe_admin[Admin Pages]
direction LR
a_dashboard[AdminDashboardPage]
a_docs[AdminDocsPage]
a_about[AboutAdminPage]
a_videos[AdminVideosPage]
a_gallery[GalleryAdminPage]
a_merch[AdminMerchPage]
a_sponsors[SponsorsAdminPage]
a_matches[MatchesAdminPage]
a_players[PlayersAdminPage]
a_teams[TeamsAdminPage]
a_users[UsersAdminPage]
a_banners[BannersAdminPage]
a_messages[MessagesAdminPage]
a_settings[SettingsAdminPage]
a_newsletter[NewsletterAdminPage]
a_polls[PollsAdminPage]
a_comp[CompetitionAliasesAdminPage]
a_prefetch[PrefetchAdminPage]
a_scoreboard[ScoreboardAdminPage]
a_score_remote[MobileScoreboardControlPage]
a_analytics[AnalyticsAdminPage]
a_shortlinks[ShortlinksAdminPage]
a_files[FilesAdminPage]
a_contacts[ContactsAdminPage]
a_navigation[NavigationAdminPage]
a_comments[CommentsAdminPage]
a_engagement[EngagementAdminPage]
a_sweep[SweepstakesAdminPage]
a_sweep_visual[SweepstakeVisualPage]
a_adminreset[AdminResetPasswordPage]
end
%% FE -> BE API mappings (high level)
fe_router -->|services/api.ts| api_grp
p_blog -->|GET /articles| 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
p_players -->|GET /players| api_grp
p_player -->|GET /players/:id| api_grp
p_gallery -->|GET /gallery/albums| api_grp
p_album -->|GET /gallery/albums/:id| api_grp
p_videos -->|GET /youtube/videos| api_grp
p_clothing -->|GET /clothing| api_grp
p_polls -->|GET /polls| api_grp
p_contact -->|POST /contact| api_grp
p_over_sb -->|GET /scoreboard (public)| api_grp
p_over_sp -->|GET /scoreboard/sponsors| api_grp
p_short -->|GET /s/:code (root)| root_grp
%% Admin flows
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
a_files -->|GET/DELETE /admin/files| api_grp
a_scoreboard -->|GET/PUT /admin/scoreboard + timer| api_grp
a_score_remote -->|POST timer controls| api_grp
a_newsletter -->|send/test/preview/status| api_grp
a_sweep -->|CRUD /admin/sweepstakes| api_grp
a_sweep_visual -->|GET /admin/sweepstakes/:id/visual| api_grp
a_analytics -->|/admin/umami| api_grp
%% FE error reporting & analytics
fe_router -->|POST /errors (ErrorReporter)| api_grp
fe_router -->|GET /umami/config| api_grp
end
%% ========================= Ports & CORS =========================
subgraph PORTS[Ports & CORS]
direction LR
port_be[Backend :8080]
port_fe[Frontend :3000 -> :80]
port_db[Postgres :5432]
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
port_db --- docker_db
cors -. controls .- router
%% Legend
subgraph LEGEND[Legend]
direction LR
L1[External Service]:::ext
L2[Database/Table]:::db
L3[Service/Daemon]:::svc
L4[Controller]:::ctrl
L5[Middleware]:::mid
L6[Model]:::model
L7[Route Group]:::route
end