Skip to content

Project Structure

Root Layout

gis-data/
├── frontend/              # Public-facing Vue 3 application
├── admin/                 # Admin dashboard (Vue 3)
├── api/                   # NestJS REST API
├── shared/                # Shared entities, types, constants
├── data-fetching/         # Background worker: fetches raw data
├── data-processing/       # Background worker: transforms raw data
├── help/                  # User documentation (VitePress)
├── docs/                  # Developer documentation (VitePress)
├── migrations/            # TypeORM database migrations
├── scripts/               # Utility scripts (backfill, etc.)
├── docker-compose.yml     # Development container orchestration
├── pnpm-workspace.yaml    # Workspace package definitions
├── package.json           # Root scripts, husky, lint-staged
├── Makefile               # Convenience shortcuts
├── .env                   # Environment variables
└── .env.example           # Environment template

Frontend (frontend/)

frontend/
└── src/
    ├── main.ts                              # App entry, plugins, global components
    ├── App.vue                              # Root component
    ├── router/index.ts                      # Routes + auth guard (beforeEach)
    ├── modules/                             # Feature modules
    │   ├── accidents/                       # Main map module
    │   │   ├── pages/accidents.vue          # Map page
    │   │   ├── components/                  # Detail views, filters, clusters
    │   │   │   ├── detail/                  # Accident detail tabs
    │   │   │   ├── filters/                 # Filter bar, multiselect, date range
    │   │   │   └── cluster/                 # Cluster popover
    │   │   ├── composables/                 # Data fetching, map logic
    │   │   ├── api/accidents.api.ts         # API functions
    │   │   ├── utils/                       # Formatters, clustering
    │   │   ├── types/                       # TypeScript interfaces
    │   │   └── config/                      # Date range options
    │   ├── breaking/                        # Hard-braking events map
    │   ├── auth/                            # Login, forgot/reset password
    │   ├── profile/                         # Current-user profile editor
    │   ├── legal/                           # Privacy, cookies, terms pages
    │   └── dashboard/                       # Dashboard (placeholder)
    └── shared/
        ├── api/                             # TanStack Query client + helpers
        ├── assets/styles/
        │   └── main.css                     # Tailwind import + design tokens (@theme)
        ├── composables/                     # Cross-module composables
        │   ├── useMapTiling.ts              # Viewport → tile aggregation
        │   ├── useMapboxInstance.ts         # Map boot/teardown helper
        │   ├── useDetailDrawer.ts           # Selection + drawer sync
        │   └── usePagination.ts             # Bounds-safe pagination state
        ├── layouts/
        │   ├── default.vue                  # Sidebar + content layout
        │   ├── auth.vue                     # Centered auth layout
        │   ├── legal.vue                    # Legal pages layout
        │   └── components/
        │       ├── sidebar-menu.vue         # Navigation sidebar
        │       └── sidebar-menu-item.vue    # Nav item component
        └── stores/
            ├── app.ts                       # Sidebar state, responsive logic
            └── auth.ts                      # Current-user / session state

UIKit components live in the shared/ package (shared/src/uikit/) and are imported via the @uikit/ alias — see UIKit for the full list.

Admin (admin/)

The admin panel mirrors the frontend's stack but is a separate Vite app on port 8002.

admin/
└── src/
    ├── main.ts
    ├── App.vue
    ├── router/                              # Routes + auth/role guard
    ├── modules/
    │   ├── auth/                            # Login, forgot/reset password
    │   ├── users/                           # CRUD, GDPR delete/export
    │   ├── profile/                         # Self profile + change password
    │   ├── companies/                       # Company management
    │   ├── data-upload/                     # CSV upload + manual fetch trigger
    │   ├── audit-log/                       # Activity log viewer + CSV export
    │   └── bug-reports/                     # Bug/improvement reports
    └── shared/
        ├── api/
        ├── assets/
        ├── components/
        ├── layouts/
        ├── stores/                          # Pinia stores (auth, etc.)
        └── utils/

API (api/)

api/
├── src/
│   ├── main.ts                              # Bootstrap, Helmet, CORS, pipes, Swagger
│   ├── app.module.ts                        # Root module (imports all features)
│   ├── common/                              # Cross-cutting helpers
│   │   ├── auth/company-scope.ts            # applyCompanyScope() query helper
│   │   └── map-query/map-query-builder.ts   # Shared density/markers/zoom-band SQL
│   ├── database/
│   │   ├── database.module.ts               # TypeORM async configuration
│   │   └── data-source.ts                   # DataSource for migrations
│   ├── accidents/
│   │   ├── accidents.module.ts
│   │   ├── accidents.controller.ts          # /api/accidents endpoints
│   │   ├── accidents.service.ts             # Query building, filtering
│   │   ├── accidents.service.spec.ts        # Unit tests
│   │   ├── dto/accident-filters.query.ts    # Query parameter validation
│   │   └── entities/
│   │       ├── accident.entity.ts           # Accident with PostGIS Point
│   │       └── accident-participant.entity.ts
│   ├── roads/
│   │   ├── roads.module.ts
│   │   ├── road-sections.controller.ts      # /api/road-sections endpoints
│   │   ├── road-sections.service.ts
│   │   └── entities/road-section.entity.ts  # Road with MultiLineString
│   ├── auth/
│   │   ├── auth.module.ts
│   │   ├── auth.controller.ts               # /api/auth endpoints
│   │   ├── auth.service.ts                  # JWT, password hashing, atomic reset
│   │   ├── jwt-secret.ts                    # resolveJwtSecret() — prod-strict secret check
│   │   ├── strategies/jwt.strategy.ts       # Passport JWT strategy
│   │   ├── guards/                          # JwtAuthGuard, RolesGuard
│   │   ├── decorators/                      # @Public, @Roles, @CurrentUser
│   │   └── dto/                             # Login, register, reset DTOs
│   ├── users/
│   │   ├── users.module.ts
│   │   ├── users.controller.ts              # /api/users CRUD
│   │   ├── users.service.ts
│   │   └── entities/user.entity.ts
│   ├── audit-log/
│   │   ├── audit-log.module.ts
│   │   ├── audit-log.controller.ts          # /api/audit-log (superadmin)
│   │   ├── audit-log.service.ts
│   │   └── entities/audit-log.entity.ts
│   ├── bug-reports/                         # Bug reports & feature requests
│   ├── data-upload/                         # CSV upload + manual fetch / reprocess
│   ├── breaking/                            # /api/breaking — hard-braking events
│   ├── companies/                           # /api/companies — company CRUD (admin+)
│   ├── tiles/                               # /api/tiles/* — filter-aware MVT proxy over pg_tileserv
│   ├── queues/                              # BullMQ queues + Bull Board (auth-gated)
│   │   ├── queues.module.ts
│   │   ├── bull-board.module.ts             # Mounts Bull Board UI at /api/queues
│   │   └── bull-board-auth.middleware.ts    # Superadmin-only middleware
│   ├── mail/                                # Resend email service
│   └── seeds/seed-admin.ts                  # Admin/superadmin user seeder
└── test/
    ├── setup-e2e.ts                         # Test app factory, helpers
    ├── app.e2e-spec.ts
    ├── auth.e2e-spec.ts
    ├── users.e2e-spec.ts
    ├── accidents.e2e-spec.ts
    ├── road-sections.e2e-spec.ts
    ├── bug-reports.e2e-spec.ts
    └── audit-log.e2e-spec.ts

Shared (shared/)

The shared package exports entities, types, interfaces, configs, validators, and UIKit components consumed by every other package:

shared/
└── src/
    ├── index.ts                 # Re-exports everything
    ├── entities/                # TypeORM entities (Accident, RoadSection, User,
    │                            #   AuditLog, RawData, BugReport, Breaking, Company,
    │                            #   AccidentParticipant)
    ├── interfaces/              # Shared TS interfaces (auth, users, accidents,
    │                            #   roads, breaking, companies, jobs, common)
    ├── constants/               # Queue names, user roles, audit actions, bug-report enums
    ├── config/                  # Label/variant maps for roles, bug reports,
    │                            #   accident codes, segment sizes, etc.
    ├── validators/              # Shared password rules
    ├── assets/styles/           # Tailwind 4 entry + design tokens (@theme)
    └── uikit/                   # Shared UI components (used by frontend & admin)

Packages that depend on shared:

  • api — entities, constants, configs, validators
  • frontend — UIKit, theme styles, interfaces, configs
  • admin — UIKit, theme styles, interfaces, configs
  • data-fetching — entities, queue constants, job interfaces
  • data-processing — entities, queue constants, job interfaces

Built with tsc to shared/dist/. Must be built (or live-mounted) before downstream packages can resolve @gis-data/shared types.

Data Pipeline

Both workers are headless NestJS apps (no HTTP server) that consume BullMQ jobs from Redis. Each has its own production Dockerfile and railway.json and is deployed as a separate Railway service.

data-fetching/
├── Dockerfile                    # Production multi-stage build (Node 22-alpine)
├── Dockerfile.dev                # Local dev (live-reloads /app/data-fetching/src)
├── railway.json                  # Railway deploy config
└── src/
    ├── main.ts                   # NestJS application context (no HTTP)
    ├── app.module.ts             # Wires TypeORM/BullMQ via config/connections
    ├── common/                   # Reusable HTTP/ZIP/CSV plumbing
    │   └── http-fetcher.ts       # Range/retry-aware fetch helper
    ├── config/
    │   └── connections.ts        # buildTypeOrmOptions / buildRedisConnection
    ├── raw-data/                 # Raw-data persistence helpers
    └── processors/
        ├── fetch.processor.ts    # Consumes FETCH_QUEUE
        └── sources/
            └── avp-accidents.fetcher.ts  # AVP WFS API + HTML scraping

data-processing/
├── Dockerfile
├── Dockerfile.dev
├── railway.json
└── src/
    ├── main.ts                   # NestJS application context (no HTTP)
    ├── app.module.ts             # Wires TypeORM/BullMQ via config/connections
    ├── common/                   # Shared transform plumbing
    │   ├── coordinate-projector.ts       # EPSG:3794/3912 → WGS84
    │   ├── date-parser.ts                # Slovene date/time parsing
    │   └── base-accident-transformer.ts  # Abstract upsert pipeline
    ├── config/
    │   └── connections.ts        # buildTypeOrmOptions / buildRedisConnection
    ├── raw-data/
    └── processors/
        ├── process.processor.ts             # Consumes PROCESS_QUEUE (concurrency 5)
        ├── analytics-refresh.processor.ts   # Consumes ANALYTICS_QUEUE
        ├── transformers/
        │   ├── avp-accidents.transformer.ts # AVP records → Accident + AccidentParticipant
        │   ├── breaking.transformer.ts      # Hard-braking events
        │   └── road-snap.helper.ts          # Snaps points to nearest road segment
        └── analytics-transforms/
            ├── accidents-by-segment.transform.ts
            ├── breaking-by-segment.transform.ts
            ├── density-by-segment.transform.ts
            └── snap-helpers.ts

Worker boot guarantees (both apps):

  • config/connections.ts#buildTypeOrmOptions refuses to start in production unless DATABASE_URL or every individual DB_* var is set.
  • config/connections.ts#buildRedisConnection refuses to start in production unless REDIS_PASSWORD is set — running BullMQ against an unauthenticated Redis would expose the queue.
  • AvpAccidentsTransformer extends common/base-accident-transformer.ts. The subclass only implements normalize(raw, rawRecord); the abstract base owns the find-by-altkey, upsert, road-snap, geom-write and participant-replace ceremony.

Migrations (migrations/)

TypeORM migrations are in the migrations/ folder at the project root and are run via the root pnpm migration:* scripts (which point at migrations/data-source.ts).

migrations/
├── data-source.ts                              # DataSource config for CLI
├── tsconfig.json                               # TypeScript config
├── 1700000000000-InitialSchema.ts
├── 1700000000001-PerformanceOptimizations.ts
├── 1700000000002-AccidentSubsegmentMap.ts
├── 1700000000003-AccidentGeomColumns.ts
├── 1700000000004-NewDbSchema.ts
├── 1700000000005-AddUsersAndAuditLogs.ts
├── 1700000000006-AddPasswordResetToken.ts
├── 1700000000007-AddRawDataTable.ts
├── 1700000000008-BugReportsAndGDPR.ts
├── 1700000000009-AddBreakingTable.ts
├── 1700000000010-AddDensityBySegment.ts
├── 1700000000011-CreateAnalyticsSchema.ts
├── 1700000000012-AddAnalyticsDimensionalTables.ts
├── 1700000000013-RestructureAnalyticsTables.ts
├── 1700000000014-AddCompanies.ts
├── 1700000000015-AddSubsegmentSimplifiedGeom.ts
├── 1700000000016-AddRefreshTokens.ts
├── 1700000000017-CreateTileFunctions.ts          # tileserv role + tiles schema + accidents_density MVT
├── 1700000000018-CreateFilteredTileFunction.ts   # filter/scope-aware accidents_filtered (called by API only)
├── 1700000000019-CreatePointsTileFunction.ts     # marker-mode accidents_points (called by API only)
└── 1700000000020-CreateBreakingTileFunctions.ts  # breaking_density (public), breaking_filtered, breaking_points

See Tile Serving for how the tiles.* SQL functions, the tileserv role, the pg_tileserv Docker service, and the NestJS /api/tiles/* proxy fit together.