Teknisk dokumentation · Hitta Ditt Sverige

Systemarkitektur: Kommunvertikalen

Stack: PHP 8.3 · MySQL 8.0 · Vanilla JS/HTML/CSS · nginx · Hetzner (Helsingfors, nuvarande) · Kolo SE3 (Piteå, planerad)
Frontend: Publika sidor
🌐hittadittsverige.se
Nationell startsida. Kommunkort, karta, exploration entry points.
Static HTML
🏛pitea.hittadittsverige.se
Kommunportal. Branding via _applyCommuneBranding(). loadCommune() API-call vid init.
HTML+JSAPI
📄commune-subpage.html
Generisk mall för kommunens undersidor (mat, boende, aktiviteter, gemenskap, karta, rutter m.fl.). Läser page_slug från URL. Renderar blocks från API.
Templateblocks.php
📝artiklar/ · artikel/
Kommunartikellistning med kategorifilter. Detaljsida med breadcrumb och body HTML.
editorial/list.php
❓hjalp/ · hjalp/artikel/
Kunskapsbas med sökning, kategorigrid och artikelrendering. Feedback i localStorage.
articles.json
Backend: API-lager
🔐_helpers.php
hds_require_auth(), hds_require_entity_access(), hds_audit(), hds_db(), hds_ok/err(). Kärna för all API-logik.
Auth corePDO
🏛commune/settings.php
GET/PATCH kommunens varumärke, navigation, kontakt.
GET PATCH
📦commune/blocks.php → profile/pages.php
blocks.php är numera en kompatibilitetsshim (endast GET) som vidarebefordrar till den generaliserade profile/pages.php. 11 blocktyper validerade mot profile_page_blocks.
GET (shim) · POST/PATCH/DELETE via profile/pages.php
👥commune/users.php
GET team, PATCH roll (inkl. village_admin + village_id), DELETE user. Joins village_admins.
GET PATCH DELETE
📝editorial/ (save, list, delete)
CRUD artiklar. Non-admin scoped till hds_managed_komkod. Kategorifilter i list.php.
POST GET DELETE
📷upload/ (commune-image, entity)
Magic-byte validering, GD resize 2000px, max 20 per kommun. Public GET utan auth.
POST GET DELETE
🏠village/details.php
Byprofil: GET sammanfogar villages.json + village_overrides. PATCH upserts overrides.
GET PATCHvillage_overrides
📋commune/audit-log.php
Returnerar senaste 50 loggposter för alla konton kopplade till kommunen.
GETaudit_log
Dashboard och data
🔧dashboard/index.html
SPA-liknande dashboard. ROLE_CONFIG mappar roller till flikar. Alla administrativa funktioner.
~2800 rader
📊ROLE_CONFIG objekt
admin, commune_coordinator, tourism_board, rural_dev, field_agent, village_admin, forening, business, member_local, member_tourist
10 roller
🗃data/entities.json
510+ entiteter. Statisk fil, läses direkt av frontend. Källa för entity_commune-tabell.
JSON statisk
🏠data/villages.json
Byar med namn, koordinater, taxonomy. Bas för village/details.php.
JSON statisk
🗺data/ routes (GPX, JSON)
Cykelleder, vandringsleder, semesterrutter. Visas på karta och i rutter-katalogen.
GPX + JSON
⚡APCu in-process cache
entities/browse.php cachar verifierade sökresultat i PHP-process. TTL 60 sekunder.
In-memory
Viktiga dataflöden
Browser
→
pitea.hittadittsverige.se
→
loadCommune()
→
commune/settings.php
→
MySQL communes
Branding load
commune-subpage.html
→
commune/blocks.php?page_slug=X
→
profile_page_blocks
→
renderBlock(type, data)
Block rendering per page slug
Dashboard (coordinator)
→
commune/invite.php
→
accounts INSERT
→
_mailer.php → SMTP
Team invitation flow
Fältagent (app)
→
entities/update.php
→
hds_require_entity_access()
→
entity_commune lookup
Access grant: field_agent via entity_commune table
village_admin
→
hds_entity_id = village_id
→
entity.village == hds_entity_id
Village admin scoping via session entity_id field
Databastabeller: kommunvertikalen
accounts
  • id (PK)
  • display_name
  • email
  • role (ENUM)
  • managed_komkod
  • entity_id (village_id)
  • active TINYINT
communes
  • komkod (PK)
  • display_name
  • tagline
  • accent_color
  • logo_url
  • banner_url
profile_pages
  • id (PK)
  • profile_id
  • profile_type ENUM(entity,commune,village,area)
  • slug
  • published TINYINT
  • sort_order INT
profile_page_blocks
  • id (PK)
  • profile_id
  • profile_type ENUM
  • page_slug
  • type (ENUM 11)
  • sort_order INT
  • data JSON
editorial_posts
  • id (PK)
  • slug (UNIQUE)
  • commune_id (nullable)
  • category
  • status ENUM
  • featured TINYINT
  • publish_at DATETIME
entity_commune
  • entity_id (PK)
  • commune_id
  • /* rader härledda från entities.json */
village_admins
  • id (PK)
  • account_id
  • village_id
  • created_at
village_overrides
  • village_id (PK)
  • data JSON
  • tagline
  • description TEXT
  • image_url
  • tabs_config JSON
  • updated_at / updated_by
audit_log
  • id (PK)
  • account_id
  • action VARCHAR
  • entity_id
  • ip_hash
  • created_at
entity_overrides
  • entity_id (PK)
  • data JSON
  • verified TINYINT
  • verified_at
  • verified_certifications JSON
  • updated_at / updated_by
entity_media
  • id (PK)
  • entity_id
  • file_path
  • media_type ENUM(photo,logo,cover)
  • caption
  • sort_order
entity_contact_messages
  • id (PK)
  • entity_id
  • sender_name
  • sender_email
  • message TEXT
  • read_at DATETIME
  • created_at
Obs: commune_settings, commune_page_blocks, entity_blocks, entity_branding, entity_seo_meta, contact_messages och gallery_albums från en tidigare version av detta dokument existerar inte längre — ersättes av profile_pages/profile_page_blocks (migration 108), entity_overrides, entity_media och entity_contact_messages ovan.
Technical Documentation · Hitta Ditt Sverige

System Architecture: The Municipal Vertical

Stack: PHP 8.3 · MySQL 8.0 · Vanilla JS/HTML/CSS · nginx · Hetzner (Helsinki, current) · Kolo SE3 (Pitea, planned)
Frontend: Public Pages
🌐hittadittsverige.se
National homepage. Municipality cards, map, exploration entry points.
Static HTML
🏛pitea.hittadittsverige.se
Municipality portal. Branding via _applyCommuneBranding(). loadCommune() API call on init.
HTML+JSAPI
📄commune-subpage.html
Generic template for the municipality's sub-pages (food, lodging, activities, community, map, routes, etc). Reads page_slug from URL. Renders blocks from API.
Templateblocks.php
📝artiklar/ · artikel/
Municipality article listing with category filter. Detail page with breadcrumb and body HTML.
editorial/list.php
❓hjalp/ · hjalp/artikel/
Knowledge base with search, category grid, and article rendering. Feedback in localStorage.
articles.json
Backend: API Layer
🔐_helpers.php
hds_require_auth(), hds_require_entity_access(), hds_audit(), hds_db(), hds_ok/err(). Core for all API logic.
Auth corePDO
🏛commune/settings.php
GET/PATCH municipality brand, navigation, contact.
GET PATCH
📦commune/blocks.php → profile/pages.php
blocks.php is now a GET-only compatibility shim that forwards to the generalized profile/pages.php. 11 block types validated against profile_page_blocks.
GET (shim) · POST/PATCH/DELETE via profile/pages.php
👥commune/users.php
GET team, PATCH role (incl. village_admin + village_id), DELETE user. Joins village_admins.
GET PATCH DELETE
📝editorial/ (save, list, delete)
CRUD articles. Non-admin scoped to hds_managed_komkod. Category filter in list.php.
POST GET DELETE
📷upload/ (commune-image, entity)
Magic-byte validation, GD resize 2000px, max 20 per municipality. Public GET without auth.
POST GET DELETE
🏠village/details.php
Village profile: GET merges villages.json + village_overrides. PATCH upserts overrides.
GET PATCHvillage_overrides
📋commune/audit-log.php
Returns the last 50 log entries for all accounts linked to the municipality.
GETaudit_log
Dashboard and Data
🔧dashboard/index.html
SPA-style dashboard. ROLE_CONFIG maps roles to tabs. All administrative functions.
~2800 lines
📊ROLE_CONFIG object
admin, commune_coordinator, tourism_board, rural_dev, field_agent, village_admin, forening, business, member_local, member_tourist
10 roles
🗃data/entities.json
510+ entities. Static file, read directly by frontend. Source for entity_commune table.
JSON static
🏠data/villages.json
Villages with name, coordinates, taxonomy. Base for village/details.php.
JSON static
🗺data/ routes (GPX, JSON)
Cycling trails, hiking routes, holiday routes. Shown on map and in routes catalogue.
GPX + JSON
⚡APCu in-process cache
entities/browse.php caches verified search results in the PHP process. TTL 60 seconds.
In-memory
Key Data Flows
Browser
→
pitea.hittadittsverige.se
→
loadCommune()
→
commune/settings.php
→
MySQL communes
Branding load
commune-subpage.html
→
commune/blocks.php?page_slug=X
→
profile_page_blocks
→
renderBlock(type, data)
Block rendering per page slug
Dashboard (coordinator)
→
commune/invite.php
→
accounts INSERT
→
_mailer.php → SMTP
Team invitation flow
Field Agent (app)
→
entities/update.php
→
hds_require_entity_access()
→
entity_commune lookup
Access grant: field_agent via entity_commune table
village_admin
→
hds_entity_id = village_id
→
entity.village == hds_entity_id
Village admin scoping via session entity_id field
Database Tables: the municipal vertical
accounts
  • id (PK)
  • display_name
  • email
  • role (ENUM)
  • managed_komkod
  • entity_id (village_id)
  • active TINYINT
communes
  • komkod (PK)
  • display_name
  • tagline
  • accent_color
  • logo_url
  • banner_url
profile_pages
  • id (PK)
  • profile_id
  • profile_type ENUM(entity,commune,village,area)
  • slug
  • published TINYINT
  • sort_order INT
profile_page_blocks
  • id (PK)
  • profile_id
  • profile_type ENUM
  • page_slug
  • type (ENUM 11)
  • sort_order INT
  • data JSON
editorial_posts
  • id (PK)
  • slug (UNIQUE)
  • commune_id (nullable)
  • category
  • status ENUM
  • featured TINYINT
  • publish_at DATETIME
entity_commune
  • entity_id (PK)
  • commune_id
  • /* rows derived from entities.json */
village_admins
  • id (PK)
  • account_id
  • village_id
  • created_at
village_overrides
  • village_id (PK)
  • data JSON
  • tagline
  • description TEXT
  • image_url
  • tabs_config JSON
  • updated_at / updated_by
audit_log
  • id (PK)
  • account_id
  • action VARCHAR
  • entity_id
  • ip_hash
  • created_at
entity_overrides
  • entity_id (PK)
  • data JSON
  • verified TINYINT
  • verified_at
  • verified_certifications JSON
  • updated_at / updated_by
entity_media
  • id (PK)
  • entity_id
  • file_path
  • media_type ENUM(photo,logo,cover)
  • caption
  • sort_order
entity_contact_messages
  • id (PK)
  • entity_id
  • sender_name
  • sender_email
  • message TEXT
  • read_at DATETIME
  • created_at
Note: commune_settings, commune_page_blocks, entity_blocks, entity_branding, entity_seo_meta, contact_messages and gallery_albums from an earlier version of this document no longer exist — superseded by profile_pages/profile_page_blocks (migration 108), entity_overrides, entity_media and entity_contact_messages above.