CVRN logoCVRN Docs
DocsArchitekturaPlanyChangelog
Sections
  • Zaklady
  • Architektura
  • Komunita
  • Legal
  • Plany
  • Changelog
Zaklady
  • O dokumentacii
  • Ako citat dokumentaciu
  • Struktura repozitara
Architektura
  • Ako aplikacia funguje
  • Diagramy
Komunita
  • Prehlad komunitnych pravidiel
  • CVRN desatoro
  • Onboarding a aktivacia clena
  • Prevadzkove pravidla (draft)
  • Treningovy mod (navrh)
  • WhiteBikes poznamky
  • Analyza Rekola
  • Analyza Slovnaft BAjk
Legal
  • Zasady ochrany osobnych udajov (navrh)
  • GDPR brief pre pravnika
  • RoPA template
  • Reklamacny a incidencny poriadok (draft)
Plany
  • Prehlad planov
  • Stavovy guard model
  • Nahlasene nepozicatelne biky
  • Email notifikacie (outbox)
  • Dynamicky kod a historia
  • Mapa a stanovista
  • Statistiky a historia jazd
  • Logo hover header
  • Backlog whitebikes inspired
Changelog
  • Sync a automatizacia
  • CVRN App Changelog

Diagramy

PreviousNext

Kompletna sada diagramov CVRN na jednej stranke: popis, PlantUML kod a klikatelny render.

status: reference
type: reference
#architektura
#diagramy
#plantuml

Tato stranka obsahuje vsetky klucove diagramy CVRN v poradi 01 -> 10.

Pri kazdom diagrame je:

  1. kratky popis,
  2. zdrojovy PlantUML kod,
  3. renderovany PNG obrazok (kliknutim otvoris vacsi nahlad).

01 System Context

Vysokourovnovy system context: host routing, hlavni akteri a napojenie na Supabase.

PlantUML kod

@startuml
title CVRN - System Context (Apex + App Hosts)

left to right direction
skinparam shadowing false
skinparam defaultFontName Monospaced

actor "Navstevnik" as Visitor
actor "Clen komunity" as Member
actor "Admin" as Admin

cloud "DNS + Vercel Edge" as Edge

rectangle "CVRN Next.js app\n(single repo, two hosts)" as NextApp {
  rectangle "Middleware\n(host routing + auth gates)" as Middleware
  rectangle "Landing host: cvrn.sk\n/landing, /log, /ciele,\n/amnestia, /contact" as Landing
  rectangle "App host: app.cvrn.sk\n/login, /, /stats, /admin,\n/changelog, /auth/callback" as App
}

rectangle "Supabase Auth\nGoogle OAuth" as Auth
database "Supabase Postgres\nRLS + RPC" as DB
rectangle "Support\ninfo@cvrn.sk" as Support

Visitor --> Edge : Open cvrn.sk
Member --> Edge : Open app.cvrn.sk
Admin --> Edge : Open app.cvrn.sk/admin

Edge --> Middleware : Request + host header
Middleware --> Landing : Rewrite '/' -> '/landing'\nor serve landing routes
Middleware --> App : Redirect app-only routes\nfrom cvrn.sk to app.cvrn.sk

App --> Auth : signInWithOAuth + callback exchange
App --> DB : rent/return/admin/stats RPC
Middleware --> DB : is_allowed(), is_admin()

Landing --> App : CTA "Pozicaj si bicykel!" -> /login
Member --> Support : Nahlasenie problemu
Admin --> Support : Operativna komunikacia

note bottom of Middleware
  Static assets are skipped by matcher
  (.*\\..*) to keep /public files reachable.
end note

@enduml

Renderovany diagram (PNG)

01 System Context

Otvorit PNG v plnej velkosti


02 Auth Access Flow

Autentifikacia, autorizacia a vstupne guardy medzi user/admin castou aplikacie.

PlantUML kod

@startuml
title CVRN - Middleware Auth and Access Flow

skinparam shadowing false
skinparam defaultFontName Monospaced

start
:HTTP request na route;
:Normalize host + pathname;

if (Host je cvrn.sk/www.cvrn.sk?) then (ano)
  if (Path == / ?) then (ano)
    :Rewrite na /landing;
    stop
  endif

  if (Path je app-only?\n/login, /stats, /admin,\n/auth/callback, /changelog, /api) then (ano)
    :Redirect na https://app.cvrn.sk + path;
    stop
  endif

  :Povolit landing route;
  stop
endif

:Init Supabase middleware client;
if (Init zlyha?) then (ano)
  if (Public path?) then (ano)
    :Povolit request bez auth checku;
    stop
  else (nie)
    :Redirect /login?error=...;
    stop
  endif
endif

:Nacitaj session usera;
if (User existuje?) then (nie)
  if (Public path?) then (ano)
    :Povolit request;
    stop
  else (nie)
    :Redirect /login;
    stop
  endif
endif

if (Path == /login?) then (ano)
  :Redirect /;
  stop
endif

if (Public path?) then (ano)
  :Povolit request;
  stop
endif

:RPC is_allowed();
if (Allowed?) then (nie)
  :Redirect /not-authorized;
  stop
endif

if (Path zacina /admin?) then (ano)
  :RPC is_admin();
  if (Admin?) then (nie)
    :Redirect /;
    stop
  endif
endif

:Povolit request;
stop

@enduml

Renderovany diagram (PNG)

02 Auth Access Flow

Otvorit PNG v plnej velkosti


03 Rent Return Sequence

Sekvencny flow pozicania a vratenia bicykla vratane validacii a zapisov do DB.

PlantUML kod

@startuml
title CVRN - Rent and Return Sequence (Transition Engine)

skinparam shadowing false
skinparam defaultFontName Monospaced

actor "Clen komunity" as Member
participant "UI (/)" as UI
participant "Server Action" as Action
participant "RPC wrapper\nrent_bike / return_bike" as RpcWrapper
participant "Transition engine\nbike_transition_apply + guard" as Transition
database "Postgres\n(bikes, rentals)" as DB

== Rent bike ==
Member -> UI: Klik "Pozicat" na available bike
UI -> Action: rentBikeAction(bikeId)
Action -> RpcWrapper: rent_bike(bike_id)
RpcWrapper -> Transition: apply('user_rent', bike_id, auth.uid)
Transition -> DB: Guard checks\n(is_allowed, no active rental,\nbike status == available)

alt Rent uspesny
  Transition -> DB: update bikes -> rented + current_user_id
  Transition -> DB: insert rentals(user_id, bike_id)
  RpcWrapper --> Action: id, name, secret_code
  Action -> Action: revalidatePath('/'), revalidatePath('/stats')
  Action --> UI: redirect '/?success=...'
else Rent zlyha
  Transition --> RpcWrapper: error code\n(already_has_bike, bike_not_available, ...)
  RpcWrapper --> Action: exception
  Action --> UI: redirect '/?error=...'
end

== Return bike ==
Member -> UI: Submit "Vratit bicykel" (+ note optional)
UI -> Action: returnBikeAction(noteText)
Action -> RpcWrapper: return_bike(note_text)
RpcWrapper -> Transition: apply('user_return', null, auth.uid, meta)
Transition -> DB: Resolve current bike by current_user_id
Transition -> DB: Guard checks\n(is_allowed, bike rented by actor,\nactive rental exists)

alt Return uspesny
  Transition -> DB: update bikes -> available + clear current_user_id
  Transition -> DB: set issue_note if note is present
  Transition -> DB: update active rental returned_at + return_note
  RpcWrapper --> Action: void success
  Action -> Action: revalidatePath('/'), revalidatePath('/stats')
  Action --> UI: redirect '/?success=...'
else Return zlyha
  Transition --> RpcWrapper: error code\n(no_active_bike, rental_not_found, ...)
  RpcWrapper --> Action: exception
  Action --> UI: redirect '/?error=...'
end

note over Transition,DB
  bike_transition_guard/apply are internal-only functions.
  execute grant is revoked from authenticated clients.
end note

@enduml

Renderovany diagram (PNG)

03 Rent Return Sequence

Otvorit PNG v plnej velkosti


04 Admin Flow

Admin procesy pre operativu: sprava bicyklov, stavov a servisnych zasahov.

PlantUML kod

@startuml
title CVRN - Admin Flow (Sidebar + RPC Mutations)

skinparam shadowing false
skinparam defaultFontName Monospaced

start
:Request na /admin;

:RPC is_admin();
if (Admin?) then (nie)
  :Redirect /;
  stop
endif

fork
  :Load bikes\n(select id,name,status,secret_code,issue_note);
fork again
  :Load allowlist\n(select allowed_emails);
fork again
  :Load rentals history\n(rpc admin_list_rentals(limit=200));
fork again
  :Load admin operations\n(rpc admin_list_operations(limit=200));
end fork

:Render full-width admin page;
:Render optional left sidebar\n(sekcie + active section highlight);

if (Akcia?) then (Allowlist add/remove)
  :Insert/Delete allowed_emails;
  if (DB error?) then (ano)
    :Redirect /admin?error=...;
  else (nie)
    :revalidatePath('/admin');
    :Redirect /admin?success=...;
  endif
elseif (Bike code update)
  :admin_update_bike_code(bike_id, new_secret_code);
  :Write bike_code_history + admin_operations;
  if (RPC error?) then (ano)
    :Redirect /admin?error=...;
  else (nie)
    :revalidatePath('/admin');
    :Redirect /admin?success=...;
  endif
elseif (Bike status change)
  :admin_set_bike_status(bike_id, available|service);
  :Transition guard blocks rented bikes;
  :Audit row in admin_operations;
  if (RPC error?) then (ano)
    :Redirect /admin?error=...;
  else (nie)
    :revalidatePath('/admin');
    :Redirect /admin?success=...;
  endif
elseif (Clear bike note)
  :admin_clear_bike_note(bike_id);
  :Audit row in admin_operations;
  if (RPC error?) then (ano)
    :Redirect /admin?error=...;
  else (nie)
    :revalidatePath('/admin');
    :Redirect /admin?success=...;
  endif
elseif (Reveal code history)
  :POST /api/admin/reveal-bike-code-history\n(bikeId + PUK);
  :RPC admin_reveal_bike_code_history(...);
  :Success/fail is logged to admin_operations;
else (No action)
  :Observe admin dashboard;
endif

stop

@enduml

Renderovany diagram (PNG)

04 Admin Flow

Otvorit PNG v plnej velkosti


05 Stats Data Flow

Datovy tok pre komunitne statistiky, agregacie a zobrazenie na route /stats.

PlantUML kod

@startuml
title CVRN - Stats Data Flow (Month Switcher)

skinparam shadowing false
skinparam defaultFontName Monospaced

actor "Allowed user" as User
participant "Stats page\n/src/app/stats/page.tsx" as StatsPage
participant "MonthSelect\n(client)" as MonthSelect
participant "Supabase RPC layer" as RPC
database "Postgres\n(rentals, bikes, profiles)" as DB

User -> StatsPage: Open /stats?month=YYYY-MM
StatsPage -> RPC: auth.getUser() + is_allowed()

alt No session
  StatsPage --> User: redirect /login
else Not allowed
  StatsPage --> User: redirect /
else Allowed
  StatsPage -> RPC: stats_get_available_months(tz)
  RPC -> DB: First month with rentals .. current month
  DB --> RPC: month_start[] (DESC)
  RPC --> StatsPage: options for Select

  StatsPage -> StatsPage: Validate month query\n(invalid/missing -> current month)
  StatsPage -> StatsPage: month_start = YYYY-MM-01

  par Daily chart
    StatsPage -> RPC: stats_get_monthly_daily_rentals(month_start, tz)
    RPC -> DB: Aggregate rentals by day in selected month
    DB --> RPC: day + rentals_count
    RPC --> StatsPage: daily rows
  else Bike chart
    StatsPage -> RPC: stats_get_monthly_rentals_by_bike(month_start, tz)
    RPC -> DB: Aggregate rentals per bike in selected month
    DB --> RPC: bike_id + bike_name + rentals_count
    RPC --> StatsPage: bike rows
  else Cyclist chart
    StatsPage -> RPC: stats_get_monthly_rentals_by_cyclist(month_start, tz)
    RPC -> DB: Aggregate rentals per cyclist in selected month
    DB --> RPC: cyclist_id + cyclist_name + rentals_count
    RPC --> StatsPage: cyclist rows
  end

  StatsPage -> StatsPage: monthTotal = sum(daily rows)
  StatsPage --> User: Render month label + all charts for selected month
end

User -> MonthSelect: Change month option
MonthSelect -> MonthSelect: router.push(path?month=YYYY-MM)
MonthSelect -> StatsPage: Server rerender with new month

note over StatsPage
  Current month is always included in options,
  even when rentals_count is zero.
end note

@enduml

Renderovany diagram (PNG)

05 Stats Data Flow

Otvorit PNG v plnej velkosti


06 DB ERD

Entitno-relacny model databazy CVRN a hlavne vazby medzi tabulkami.

PlantUML kod

@startuml
title CVRN - Database ERD (Current Schema)

skinparam shadowing false
skinparam defaultFontName Monospaced
hide methods

entity "profiles" as profiles {
  * id : uuid <<PK>>
  --
  email : text
  is_admin : boolean
  created_at : timestamptz
}

entity "bikes" as bikes {
  * id : bigint <<PK>>
  --
  name : text
  status : text (available|rented|service)
  secret_code : text
  current_user_id : uuid <<FK nullable>>
  issue_note : text <<nullable>>
  issue_reported_at : timestamptz <<nullable>>
  created_at : timestamptz
  updated_at : timestamptz
}

entity "rentals" as rentals {
  * id : bigint <<PK>>
  --
  user_id : uuid <<FK>>
  bike_id : bigint <<FK>>
  rented_at : timestamptz
  returned_at : timestamptz <<nullable>>
  return_note : text <<nullable>>
}

entity "allowed_emails" as allowed_emails {
  * email : text <<PK>>
  --
  added_at : timestamptz
  added_by : uuid <<FK nullable>>
}

entity "admin_operations" as admin_operations {
  * id : bigint <<PK>>
  --
  action_type : text
  bike_id : bigint <<FK nullable>>
  actor_user_id : uuid <<FK nullable>>
  actor_email : text
  reason_text : text <<nullable>>
  details : jsonb
  created_at : timestamptz
}

entity "bike_code_history" as bike_code_history {
  * id : bigint <<PK>>
  --
  bike_id : bigint <<FK>>
  old_code : text
  changed_by : uuid <<FK nullable>>
  changed_by_email : text
  changed_at : timestamptz
}

entity "admin_puk_config" as admin_puk_config {
  * id : boolean <<PK, fixed true>>
  --
  puk_hash : text
  updated_at : timestamptz
  updated_by : uuid <<FK nullable>>
}

profiles ||--o{ rentals : user_id
bikes ||--o{ rentals : bike_id
profiles ||--o{ bikes : current_user_id
profiles ||--o{ allowed_emails : added_by
profiles ||--o{ admin_operations : actor_user_id
bikes ||--o{ admin_operations : bike_id
profiles ||--o{ bike_code_history : changed_by
bikes ||--o{ bike_code_history : bike_id
profiles ||--o{ admin_puk_config : updated_by

note right of rentals
  Unique active constraints:
  - one active rental per user
  - one active rental per bike
  (returned_at IS NULL)
end note

note right of bikes
  Direct table reads/updates are admin-only (RLS).
  Member flows use SECURITY DEFINER RPC functions.
end note

@enduml

Renderovany diagram (PNG)

06 DB ERD

Otvorit PNG v plnej velkosti


07 Bike State Machine

Stavovy model bicykla a povolene prechody medzi stavmi v prevadzke.

PlantUML kod

@startuml
title CVRN - Bike State Machine (Guard-driven)

skinparam shadowing false
skinparam defaultFontName Monospaced

state "Bike status" as BikeStatus {
  [*] --> Available

  Available --> Rented : user_rent\n(rent_bike)
  Rented --> Available : user_return\n(return_bike)

  Available --> Service : admin_set_service
  Service --> Available : admin_set_available

  Available --> Available : admin_update_code\n(no status change)
  Service --> Service : admin_update_code\n(no status change)
}

state "Issue note flag" as IssueFlag {
  [*] --> NoIssue

  NoIssue --> HasIssue : user_return(note_text != empty)
  HasIssue --> NoIssue : admin_clear_note

  HasIssue --> HasIssue : user_return(note_text != empty)\n(overwrite with latest note)
}

note right of BikeStatus
  Guard rules in bike_transition_guard:
  - user_rent allowed only from Available
  - user_return allowed only from Rented by same actor
  - admin cannot switch rented bike to service/available
  - direct Rented -> Service transition is blocked
end note

note right of IssueFlag
  issue_note is orthogonal to bike status.
  Bike can stay Available + HasIssue.
end note

@enduml

Renderovany diagram (PNG)

07 Bike State Machine

Otvorit PNG v plnej velkosti


08 RPC Security Boundary

Bezpecnostna hranica pre RPC vrstvu, role a policy enforcement.

PlantUML kod

@startuml
title CVRN - RPC Security Boundary

skinparam shadowing false
skinparam defaultFontName Monospaced

actor "Authenticated user" as User
participant "Browser UI" as Browser
participant "Next.js server layer\n(Server Components + Actions + Route Handlers)" as Server
participant "Supabase API" as Supabase
database "Postgres\n(RLS + RPC + internal guard funcs)" as DB

== Public bike list (safe) ==
User -> Browser: Open "/"
Browser -> Server: Render HomePage
Server -> Supabase: rpc("list_bikes_public")
Supabase -> DB: list_bikes_public() checks is_allowed()
DB --> Supabase: id, name, status, issue_note\n(no secret_code)
Supabase --> Server: bikes[]
Server --> Browser: Render list without lock codes

== User's own bike code ==
User -> Browser: Has active rental
Browser -> Server: Render HomePage
Server -> Supabase: rpc("get_my_bike")
Supabase -> DB: get_my_bike() scoped to auth.uid()
DB --> Supabase: one row incl. secret_code
Supabase --> Server: my_bike
Server --> Browser: Render own secret_code

== Admin mutation with audit ==
User -> Browser: Admin updates bike status/code
Browser -> Server: Server Action on /admin
Server -> Supabase: rpc("admin_set_bike_status" / "admin_update_bike_code")
Supabase -> DB: admin RPC -> bike_transition_apply(...)
DB --> DB: write bikes + admin_operations\n(+ bike_code_history for code update)
Supabase --> Server: success/error
Server --> Browser: redirect + refreshed /admin

== Direct table read attempt (blocked) ==
User -> Browser: Try direct client query
Browser -> Supabase: from("bikes").select("secret_code")
Supabase -> DB: SELECT bikes.secret_code
DB --> Supabase: denied by RLS (non-admin)
Supabase --> Browser: error / no rows

note over DB
  Security boundary summary:
  - RLS allows direct bikes/rentals writes only for admin.
  - member flows must go through SECURITY DEFINER RPC.
  - bike_transition_guard/apply execute is revoked
    from authenticated clients.
end note

@enduml

Renderovany diagram (PNG)

08 RPC Security Boundary

Otvorit PNG v plnej velkosti


09 User Journey

End-to-end user journey od vstupu do appky po uspesne ukoncenie jazdy.

PlantUML kod

@startuml
title CVRN - User Journey (Landing + App)

skinparam shadowing false
skinparam defaultFontName Monospaced

[*] --> Visitor

state "Visitor on cvrn.sk" as Visitor
state "Landing pages\n/landing, /log, /ciele,\n/amnestia, /contact" as Landing
state "Login\napp.cvrn.sk/login" as Login
state "OAuth callback\n/auth/callback" as Callback
state "Dashboard\n/" as Dashboard
state "Not authorized\n/not-authorized" as NotAuthorized
state "No bike view" as NoBike
state "Active ride view" as HasBike
state "Stats\n/stats?month=YYYY-MM" as Stats
state "Admin\n/admin" as Admin

Visitor --> Landing : open cvrn.sk
Landing --> Login : CTA "Pozicaj si bicykel!"

Login --> Callback : signInWithOAuth(google)
Callback --> Dashboard : exchangeCodeForSession success
Callback --> Login : callback error

Dashboard --> NotAuthorized : is_allowed = false
NotAuthorized --> Login : retry / sign in as different user

Dashboard --> NoBike : get_my_bike = none
Dashboard --> HasBike : get_my_bike = active bike

NoBike --> HasBike : rentBikeAction -> rpc rent_bike
HasBike --> NoBike : returnBikeAction -> rpc return_bike

Dashboard --> Stats : open /stats
Stats --> Stats : switch month via Select\n(URL month=YYYY-MM)
Stats --> Dashboard : back to /

Dashboard --> Admin : open /admin [is_admin]
Admin --> Dashboard : back to /

Dashboard --> Login : signOutAction
NoBike --> Login : signOutAction
HasBike --> Login : signOutAction
Stats --> Login : signOutAction
Admin --> Login : signOutAction

Login --> [*] : close flow

note right of Landing
  App-only routes on cvrn.sk are redirected
  by middleware to app.cvrn.sk.
end note

@enduml

Renderovany diagram (PNG)

09 User Journey

Otvorit PNG v plnej velkosti


10 Host Routing Flow

Routing medzi cvrn.sk a app.cvrn.sk, presmerovania a middleware rozhodovanie.

PlantUML kod

@startuml
title CVRN - Host Routing Flow (cvrn.sk vs app.cvrn.sk)

skinparam shadowing false
skinparam defaultFontName Monospaced

start
:HTTP request;
:Read host (x-forwarded-host / host);

if (Host in {cvrn.sk, www.cvrn.sk}?) then (ano)
  if (Path == / ?) then (ano)
    :Rewrite to /landing;
    stop
  endif

  if (Path is app-only?) then (ano)
    :Redirect to https://app.cvrn.sk + same path/query;
    stop
  endif

  :Serve landing content directly;
  stop
else (nie)
  :Treat as app host flow;

  if (Path is static asset?) then (ano)
    :Skip middleware (matcher excludes .*\\..*);
    stop
  endif

  :Initialize Supabase middleware client;
  if (Init/session check fails on protected path?) then (ano)
    :Redirect /login (with error when needed);
    stop
  endif

  :Apply app auth gates\n(user, is_allowed, is_admin for /admin);
  :Serve app route;
  stop
endif

@enduml

Renderovany diagram (PNG)

10 Host Routing Flow

Otvorit PNG v plnej velkosti

Ako aplikacia fungujePrehlad komunitnych pravidiel

On This Page

01 System ContextPlantUML kodRenderovany diagram (PNG)02 Auth Access FlowPlantUML kodRenderovany diagram (PNG)03 Rent Return SequencePlantUML kodRenderovany diagram (PNG)04 Admin FlowPlantUML kodRenderovany diagram (PNG)05 Stats Data FlowPlantUML kodRenderovany diagram (PNG)06 DB ERDPlantUML kodRenderovany diagram (PNG)07 Bike State MachinePlantUML kodRenderovany diagram (PNG)08 RPC Security BoundaryPlantUML kodRenderovany diagram (PNG)09 User JourneyPlantUML kodRenderovany diagram (PNG)10 Host Routing FlowPlantUML kodRenderovany diagram (PNG)
© CVRN dokumentacia