%%{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)
- APP_ENV/PORT/DEBUG
- DATABASE_URL (GORM)
- JWT_SECRET/EXP
- ALLOWED_ORIGINS (CORS)
- UPLOAD_DIR/MAX_UPLOAD_SIZE
- SMTP_* (Email)
- FRONTEND_BASE_URL
- PUBLIC_API_BASE_URL
- ERROR_INGEST_URL/TOKEN
- FACR_SCRAPER_BASE_URL
- UMAMI_*
- 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
/login,/logout,/register,/me
/password-reset"] c_contact["ContactController
/contact + newsletter + admin forwarding"] c_pass[PasswordController] c_ai["AIController
/ai/blog,/ai/about,/ai/css,/ai/instagram"] c_score["ScoreboardController
/public + admin timer/sponsors/qr"] c_about[AboutController] c_gallery["GalleryController
/Zonerama profile/albums/picks"] c_files["FilesController
/list/unused/duplicates/usage
/scan/refresh-tracking/delete"] c_notify[NotificationsController] c_email["EmailController
/open.gif/click/unsubscribe/stats"] c_prefetch["PrefetchController
/status/trigger"] c_seo["SEOController
/seo (public) + robots.txt + sitemap"] c_nav["NavigationController
/navigation + social-links + admin CRUD"] c_poll["PollController
/public vote/results + admin"] c_sw["SweepstakesController
/public current/visual + admin CRUD/finalize"] c_cloth["ClothingController
/public + admin CRUD"] c_pec["PageElementConfigController
/public + admin CRUD/batch"] c_article["ArticleController
/create + match-link"] c_base["BaseController
/health, uploads, categories, teams, players, matches, standings, zonerama, settings, shortlinks(public)"] c_myu["MyUIbrixController
/validate,/preview,/optimize"] c_editor["EditorPreviewController
/preview state + variants"] c_short["ShortLinkController
/public create + admin + redirect /s/:code"] c_comment["CommentController
/public list + CRUD + reactions
ban/unban/report (admin)"] c_eng["EngagementController
/rewards/leaderboard/profile/actions"] c_facr["FACRController
/facr club search/info/table"] c_yt["YouTubeController
/youtube/videos"] c_umami["UmamiController
/config + admin initialize/stats"] c_error["ErrorController
/errors ingest + admin + external"] end subgraph services[Services & Jobs] direction TB s_errrep[ErrorReporter] s_prefetch["Prefetcher
StartPrefetcher(target)"] s_nlsched[NewsletterScheduler] s_nlauto["NewsletterAutomation
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
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
/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
- http://localhost:3000
- http://localhost:8080
+ FrontendBaseURL origin
+ * 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