From 40e186ed49b86f7ee5a7525929d1b389872dddf7 Mon Sep 17 00:00:00 2001 From: Shivam Rao Date: Wed, 28 Jan 2026 02:22:53 +0530 Subject: [PATCH] feat: Phase 1 - OAuth2 Authentication & Core Setup - Add Spring Security with OAuth2 - Configure Flight Passport integration - Add Redis token caching - Create HomeView and AuthenticatedView --- application.properties.phase1 | 59 ++ java-frontend/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + java-frontend/mvnw.cmd | 188 +++++++ java-frontend/pom.xml | 146 +++++ java-frontend/run-local.ps1 | 91 ++++ java-frontend/run-with-env.ps1 | 56 ++ .../FlightSpotlightApplication.java | 21 + .../config/AppShellConfig.java | 19 + .../config/OAuth2ClientConfig.java | 26 + .../config/ObjectMapperConfig.java | 19 + .../flightspotlight/config/RedisConfig.java | 112 ++++ .../config/RedisTemplateConfig.java | 30 ++ .../config/SecurityConfig.java | 58 ++ .../config/WebClientConfig.java | 25 + .../service/FlightBlenderService.java | 298 +++++++++++ .../service/PassportTokenService.java | 125 +++++ .../view/AuthenticatedView.java | 83 +++ .../com/flightspotlight/view/HomeView.java | 68 +++ .../flightspotlight/view/LaunchpadView.java | 502 ++++++++++++++++++ .../view/NoticeboardGlobeView.java | 145 +++++ .../view/NoticeboardMapView.java | 147 +++++ .../flightspotlight/view/NoticeboardView.java | 276 ++++++++++ .../view/OperationStatusView.java | 181 +++++++ .../flightspotlight/view/SpotlightView.java | 145 +++++ .../src/main/resources/application.properties | 59 ++ .../main/resources/static/images/favicon.png | Bin 0 -> 200 bytes .../static/images/logo_transparent.png | Bin 0 -> 5561 bytes .../static/images/logo_transparent_sm.png | Bin 0 -> 8324 bytes .../main/resources/static/images/spinner.gif | Bin 0 -> 109248 bytes 30 files changed, 2881 insertions(+) create mode 100644 application.properties.phase1 create mode 100644 java-frontend/.mvn/wrapper/maven-wrapper.jar create mode 100644 java-frontend/.mvn/wrapper/maven-wrapper.properties create mode 100644 java-frontend/mvnw.cmd create mode 100644 java-frontend/pom.xml create mode 100644 java-frontend/run-local.ps1 create mode 100644 java-frontend/run-with-env.ps1 create mode 100644 java-frontend/src/main/java/com/flightspotlight/FlightSpotlightApplication.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/config/AppShellConfig.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/config/OAuth2ClientConfig.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/config/ObjectMapperConfig.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/config/RedisConfig.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/config/RedisTemplateConfig.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/config/SecurityConfig.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/config/WebClientConfig.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/service/FlightBlenderService.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/service/PassportTokenService.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/AuthenticatedView.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/HomeView.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/LaunchpadView.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/NoticeboardGlobeView.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/NoticeboardMapView.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/NoticeboardView.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/OperationStatusView.java create mode 100644 java-frontend/src/main/java/com/flightspotlight/view/SpotlightView.java create mode 100644 java-frontend/src/main/resources/application.properties create mode 100644 java-frontend/src/main/resources/static/images/favicon.png create mode 100644 java-frontend/src/main/resources/static/images/logo_transparent.png create mode 100644 java-frontend/src/main/resources/static/images/logo_transparent_sm.png create mode 100644 java-frontend/src/main/resources/static/images/spinner.gif diff --git a/application.properties.phase1 b/application.properties.phase1 new file mode 100644 index 0000000..569479d --- /dev/null +++ b/application.properties.phase1 @@ -0,0 +1,59 @@ +# Flight Spotlight Java Frontend Configuration +# Phase 1: Java Frontend Skeleton + Authentication + +# Server Configuration +server.port=${PORT:8080} + +# Application Name +spring.application.name=flight-spotlight-java + +# Vaadin Configuration +vaadin.servlet.production-mode=false +vaadin.whitelisted-packages=com.flightspotlight + +# OAuth2 / OIDC Configuration (User Authentication) +# These values should match the Node.js environment variables +spring.security.oauth2.client.registration.flight-passport.client-id=${CLIENT_ID} +spring.security.oauth2.client.registration.flight-passport.client-secret=${CLIENT_SECRET} +spring.security.oauth2.client.registration.flight-passport.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.flight-passport.redirect-uri=${SPOTLIGHT_BASE_URL}/login/oauth2/code/flight-passport +spring.security.oauth2.client.registration.flight-passport.scope=openid,profile + +spring.security.oauth2.client.provider.flight-passport.authorization-uri=${OIDC_DOMAIN}/authorize +spring.security.oauth2.client.provider.flight-passport.token-uri=${OIDC_DOMAIN}/token/ +spring.security.oauth2.client.provider.flight-passport.user-info-uri=${OIDC_DOMAIN}/userinfo/ +spring.security.oauth2.client.provider.flight-passport.jwk-set-uri=${OIDC_DOMAIN}/.well-known/jwks.json +spring.security.oauth2.client.provider.flight-passport.user-name-attribute=sub + +# Flight Passport M2M Token Configuration (for Flight Blender API calls) +flight.passport.m2m.client-id=${PASSPORT_BLENDER_CLIENT_ID} +flight.passport.m2m.client-secret=${PASSPORT_BLENDER_CLIENT_SECRET} +flight.passport.m2m.scope=${PASSPORT_BLENDER_SCOPE:blender.read blender.write} +flight.passport.m2m.audience=${PASSPORT_BLENDER_AUDIENCE} +flight.passport.m2m.token-url=${PASSPORT_URL}/oauth/token/ +flight.passport.m2m.token-cache-key=blender_passport_token +flight.passport.m2m.token-cache-ttl=3500 + +# Flight Blender API Configuration +flight.blender.base-url=${BLENDER_BASE_URL} +flight.blender.ping-endpoint=/ping +flight.blender.declarations-endpoint=/flight_declaration_ops/flight_declaration + +# Redis Configuration (for token caching) +# Note: REDIS_URL takes precedence if provided (format: rediss://host:port or redis://host:port) +# SSL is automatically detected from the URL scheme (rediss:// = SSL enabled) +spring.data.redis.host=${REDIS_HOST:localhost} +spring.data.redis.port=${REDIS_PORT:6379} +spring.data.redis.password=${REDIS_PASSWORD:} + +# Cache Configuration +spring.cache.type=redis +spring.cache.redis.time-to-live=3500000 + +# Mapbox Configuration (for future phases) +mapbox.access-token=${MAPBOX_KEY} + +# Logging +logging.level.com.flightspotlight=DEBUG +logging.level.org.springframework.security=DEBUG +logging.level.org.springframework.web=DEBUG diff --git a/java-frontend/.mvn/wrapper/maven-wrapper.jar b/java-frontend/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/java-frontend/.mvn/wrapper/maven-wrapper.properties b/java-frontend/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..10cf7bb --- /dev/null +++ b/java-frontend/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/java-frontend/mvnw.cmd b/java-frontend/mvnw.cmd new file mode 100644 index 0000000..2e4b889 --- /dev/null +++ b/java-frontend/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/java-frontend/pom.xml b/java-frontend/pom.xml new file mode 100644 index 0000000..bd2ee3f --- /dev/null +++ b/java-frontend/pom.xml @@ -0,0 +1,146 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + com.flightspotlight + flight-spotlight-java + 1.0.0-SNAPSHOT + Flight Spotlight Java Frontend + Flight Spotlight frontend rebuilt in Java using Spring Boot and Vaadin + + + 17 + 17 + 17 + 24.3.10 + UTF-8 + UTF-8 + + + + + + com.vaadin + vaadin-bom + ${vaadin.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.springframework.boot + spring-boot-starter-cache + + + + + com.vaadin + vaadin-spring-boot-starter + ${vaadin.version} + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + io.lettuce + lettuce-core + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + com.vaadin + vaadin-maven-plugin + ${vaadin.version} + + + + prepare-frontend + + + + + + + diff --git a/java-frontend/run-local.ps1 b/java-frontend/run-local.ps1 new file mode 100644 index 0000000..fc99cd1 --- /dev/null +++ b/java-frontend/run-local.ps1 @@ -0,0 +1,91 @@ +# Flight Spotlight Java Frontend - Local Run Script +# Loads .env file and runs the application + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Flight Spotlight Java Frontend" -ForegroundColor Cyan +Write-Host "Loading environment variables and starting..." -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# Get the project root directory (parent of java-frontend) +$projectRoot = Split-Path -Parent $PSScriptRoot +$envFile = Join-Path $projectRoot ".env" + +# Load .env file +if (Test-Path $envFile) { + Write-Host "Loading environment variables from .env file..." -ForegroundColor Yellow + Get-Content $envFile | ForEach-Object { + if ($_ -match '^\s*([^#][^=]*)\s*=\s*(.*)\s*$') { + $key = $matches[1].Trim() + $value = $matches[2].Trim() + # Remove quotes if present + if ($value -match '^"(.*)"$' -or $value -match "^'(.*)'$") { + $value = $matches[1] + } + [Environment]::SetEnvironmentVariable($key, $value, "Process") + Write-Host " Set: $key" -ForegroundColor Gray + } + } + Write-Host "✓ Environment variables loaded" -ForegroundColor Green +} else { + Write-Host "⚠ .env file not found at: $envFile" -ForegroundColor Yellow + Write-Host "Please ensure .env file exists in project root" -ForegroundColor Yellow + exit 1 +} + +# Override SPOTLIGHT_BASE_URL for local testing +$env:SPOTLIGHT_BASE_URL = "http://localhost:8080" +Write-Host " Override: SPOTLIGHT_BASE_URL = http://localhost:8080 (for local testing)" -ForegroundColor Gray + +# Clean REDIS_URL if it has quotes +if ($env:REDIS_URL) { + $env:REDIS_URL = $env:REDIS_URL.Trim('"').Trim("'") +} + +# Clean PASSPORT_BLENDER_SCOPE if it has quotes +if ($env:PASSPORT_BLENDER_SCOPE) { + $env:PASSPORT_BLENDER_SCOPE = $env:PASSPORT_BLENDER_SCOPE.Trim('"').Trim("'") +} + +Write-Host "" +Write-Host "Environment Variables Summary:" -ForegroundColor Yellow +Write-Host " CLIENT_ID: $($env:CLIENT_ID.Substring(0, [Math]::Min(20, $env:CLIENT_ID.Length)))..." -ForegroundColor Gray +Write-Host " OIDC_DOMAIN: $env:OIDC_DOMAIN" -ForegroundColor Gray +Write-Host " SPOTLIGHT_BASE_URL: $env:SPOTLIGHT_BASE_URL" -ForegroundColor Gray +Write-Host " BLENDER_BASE_URL: $env:BLENDER_BASE_URL" -ForegroundColor Gray +Write-Host " REDIS_URL: $($env:REDIS_URL.Substring(0, [Math]::Min(50, $env:REDIS_URL.Length)))..." -ForegroundColor Gray +Write-Host "" + +# Check Java +Write-Host "Checking Java..." -ForegroundColor Yellow +$javaVersion = java -version 2>&1 | Select-String "version" +if ($javaVersion) { + Write-Host "✓ $javaVersion" -ForegroundColor Green +} else { + Write-Host "✗ Java not found" -ForegroundColor Red + exit 1 +} + +# Build the project +Write-Host "" +Write-Host "Building the project..." -ForegroundColor Yellow +& .\mvnw.cmd clean compile -q +if ($LASTEXITCODE -ne 0) { + Write-Host "✗ Build failed" -ForegroundColor Red + exit 1 +} +Write-Host "✓ Build successful" -ForegroundColor Green + +# Run the application +Write-Host "" +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Starting application..." -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "Application will be available at:" -ForegroundColor Yellow +Write-Host " http://localhost:8080" -ForegroundColor Green +Write-Host "" +Write-Host "Press Ctrl+C to stop the application" -ForegroundColor Yellow +Write-Host "" + +& .\mvnw.cmd spring-boot:run diff --git a/java-frontend/run-with-env.ps1 b/java-frontend/run-with-env.ps1 new file mode 100644 index 0000000..2804a4e --- /dev/null +++ b/java-frontend/run-with-env.ps1 @@ -0,0 +1,56 @@ +# Flight Spotlight Java Frontend - Run with .env file +# This script loads environment variables from .env file and starts the application + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Flight Spotlight Java - Starting..." -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# Load .env file from parent directory +$envFile = Join-Path (Split-Path $PSScriptRoot -Parent) ".env" + +if (Test-Path $envFile) { + Write-Host "Loading environment variables from .env file..." -ForegroundColor Yellow + + Get-Content $envFile | ForEach-Object { + if ($_ -match '^([^#][^=]+)=(.*)$') { + $key = $matches[1].Trim() + $value = $matches[2].Trim() + + # Remove quotes if present + $value = $value -replace '^[""'']|[""'']$', '' + + # Set environment variable + [Environment]::SetEnvironmentVariable($key, $value, "Process") + Write-Host " * $key" -ForegroundColor Green + } + } + + Write-Host "" + Write-Host "Environment variables loaded successfully!" -ForegroundColor Green + Write-Host "" +} else { + Write-Host "Warning: .env file not found at: $envFile" -ForegroundColor Red + Write-Host "Please create a .env file with required environment variables." -ForegroundColor Red + exit 1 +} + +# Override SPOTLIGHT_BASE_URL for local testing +Write-Host "Setting SPOTLIGHT_BASE_URL to http://localhost:8080 for local testing..." -ForegroundColor Yellow +[Environment]::SetEnvironmentVariable("SPOTLIGHT_BASE_URL", "http://localhost:8080", "Process") +Write-Host "" + +# Add Maven to PATH +$env:PATH = "$env:USERPROFILE\.m2\wrapper\dists\apache-maven-3.9.9\977a63e90f436cd6ade95b4c0e10c20c\bin;" + $env:PATH + +# Navigate to script directory (java-frontend) +Set-Location -Path $PSScriptRoot + +# Start the application +Write-Host "Starting Spring Boot application..." -ForegroundColor Cyan +Write-Host "" +Write-Host "The application will be available at: http://localhost:8080" -ForegroundColor Green +Write-Host "Press Ctrl+C to stop the application" -ForegroundColor Yellow +Write-Host "" + +mvn spring-boot:run diff --git a/java-frontend/src/main/java/com/flightspotlight/FlightSpotlightApplication.java b/java-frontend/src/main/java/com/flightspotlight/FlightSpotlightApplication.java new file mode 100644 index 0000000..e85a97a --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/FlightSpotlightApplication.java @@ -0,0 +1,21 @@ +package com.flightspotlight; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +/** + * Main application class for Flight Spotlight Java Frontend. + * + * This application replaces the Node.js/Express frontend with a Java/Spring Boot/Vaadin implementation. + * + * Phase 1: Java Frontend Skeleton + Authentication + */ +@SpringBootApplication +@EnableCaching +public class FlightSpotlightApplication { + + public static void main(String[] args) { + SpringApplication.run(FlightSpotlightApplication.class, args); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/config/AppShellConfig.java b/java-frontend/src/main/java/com/flightspotlight/config/AppShellConfig.java new file mode 100644 index 0000000..4df4e56 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/config/AppShellConfig.java @@ -0,0 +1,19 @@ +package com.flightspotlight.config; + +import com.vaadin.flow.component.page.AppShellConfigurator; +import com.vaadin.flow.component.page.Push; +import com.vaadin.flow.server.PWA; +import com.vaadin.flow.shared.communication.PushMode; + +/** + * Application shell configuration. + * + * Phase 5.2: Vaadin Push Integration + * + * Configures Vaadin Push (WebSocket) for server-initiated updates to clients. + * This enables real-time air traffic updates in the Spotlight view. + */ +@Push(PushMode.AUTOMATIC) +public class AppShellConfig implements AppShellConfigurator { + // Configuration is provided via annotations +} diff --git a/java-frontend/src/main/java/com/flightspotlight/config/OAuth2ClientConfig.java b/java-frontend/src/main/java/com/flightspotlight/config/OAuth2ClientConfig.java new file mode 100644 index 0000000..dc1f58d --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/config/OAuth2ClientConfig.java @@ -0,0 +1,26 @@ +package com.flightspotlight.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; + +/** + * OAuth2 Client configuration. + * + * This configuration is handled primarily through application.properties + * with the following properties: + * - spring.security.oauth2.client.registration.flight-passport.* + * - spring.security.oauth2.client.provider.flight-passport.* + * + * Phase 1.2: OAuth2 User Authentication + */ +@Configuration +public class OAuth2ClientConfig { + + // OAuth2 client configuration is done via application.properties + // Spring Boot auto-configures OAuth2ClientRegistrationRepository + // and OAuth2AuthorizedClientRepository based on properties + + // Additional custom configuration can be added here if needed +} diff --git a/java-frontend/src/main/java/com/flightspotlight/config/ObjectMapperConfig.java b/java-frontend/src/main/java/com/flightspotlight/config/ObjectMapperConfig.java new file mode 100644 index 0000000..57ea29c --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/config/ObjectMapperConfig.java @@ -0,0 +1,19 @@ +package com.flightspotlight.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * ObjectMapper configuration for JSON parsing. + * + * Phase 1.3: M2M Token & Flight Blender API + */ +@Configuration +public class ObjectMapperConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/config/RedisConfig.java b/java-frontend/src/main/java/com/flightspotlight/config/RedisConfig.java new file mode 100644 index 0000000..8330453 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/config/RedisConfig.java @@ -0,0 +1,112 @@ +package com.flightspotlight.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.net.URI; +import java.time.Duration; + +/** + * Redis configuration for token caching. + * + * Supports both REDIS_URL format and individual host/port/password configuration. + * Token TTL is set to 3500 seconds (matching Node.js implementation). + * + * Phase 1.3: M2M Token & Flight Blender API + */ +@Configuration +@EnableCaching +public class RedisConfig { + + private static final Logger log = LoggerFactory.getLogger(RedisConfig.class); + private static final Duration TOKEN_TTL = Duration.ofSeconds(3500); + + @Value("${REDIS_URL:}") + private String redisUrl; + + @Value("${spring.data.redis.host:localhost}") + private String redisHost; + + @Value("${spring.data.redis.port:6379}") + private int redisPort; + + @Value("${spring.data.redis.password:}") + private String redisPassword; + + private boolean redisSsl = false; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); + + // Parse REDIS_URL if provided (format: rediss://host:port or redis://host:port) + if (redisUrl != null && !redisUrl.isEmpty()) { + try { + URI uri = new URI(redisUrl); + config.setHostName(uri.getHost()); + config.setPort(uri.getPort()); + + // Check if SSL is enabled (rediss://) + if (uri.getScheme().equals("rediss")) { + redisSsl = true; + } + + // Parse password from URL if present (format: redis://:password@host:port) + if (uri.getUserInfo() != null && uri.getUserInfo().contains(":")) { + String[] userInfo = uri.getUserInfo().split(":"); + if (userInfo.length > 1) { + config.setPassword(userInfo[1]); + } + } + + log.info("Redis connection configured from REDIS_URL: {}:{}", uri.getHost(), uri.getPort()); + } catch (Exception e) { + log.warn("Failed to parse REDIS_URL, using individual properties: {}", e.getMessage()); + config.setHostName(redisHost); + config.setPort(redisPort); + if (redisPassword != null && !redisPassword.isEmpty()) { + config.setPassword(redisPassword); + } + } + } else { + // Use individual properties + config.setHostName(redisHost); + config.setPort(redisPort); + if (redisPassword != null && !redisPassword.isEmpty()) { + config.setPassword(redisPassword); + } + log.info("Redis connection configured from properties: {}:{}", redisHost, redisPort); + } + + LettuceConnectionFactory factory = new LettuceConnectionFactory(config); + factory.setValidateConnection(true); + return factory; + } + + @Bean + public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(TOKEN_TTL) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer())); + + return RedisCacheManager.builder(connectionFactory) + .cacheDefaults(cacheConfig) + .build(); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/config/RedisTemplateConfig.java b/java-frontend/src/main/java/com/flightspotlight/config/RedisTemplateConfig.java new file mode 100644 index 0000000..a4000d2 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/config/RedisTemplateConfig.java @@ -0,0 +1,30 @@ +package com.flightspotlight.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * RedisTemplate configuration for string-based operations. + * + * Used by PassportTokenService for token caching. + * + * Phase 1.3: M2M Token & Flight Blender API + */ +@Configuration +public class RedisTemplateConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new StringRedisSerializer()); + template.afterPropertiesSet(); + return template; + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/config/SecurityConfig.java b/java-frontend/src/main/java/com/flightspotlight/config/SecurityConfig.java new file mode 100644 index 0000000..0adc7e8 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/config/SecurityConfig.java @@ -0,0 +1,58 @@ +package com.flightspotlight.config; + +import com.vaadin.flow.spring.security.VaadinWebSecurity; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.firewall.HttpFirewall; +import org.springframework.security.web.firewall.StrictHttpFirewall; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +/** + * Spring Security configuration for Flight Spotlight. + * + * Configures OAuth2 login with Flight Passport and Vaadin security. + * + * Phase 1.2: OAuth2 User Authentication + */ +@Configuration +@EnableWebSecurity +public class SecurityConfig extends VaadinWebSecurity { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // Allow public access to landing page, static resources, and OAuth2 endpoints + http.authorizeHttpRequests(auth -> auth + .requestMatchers("/", "/login", "/oauth2/**", "/login/oauth2/**", + "/images/**", "/css/**", "/js/**", "/favicon.ico").permitAll() + ); + + // Configure OAuth2 login before calling super + http.oauth2Login(oauth2 -> oauth2 + .defaultSuccessUrl("/authenticated", true) + ); + + // Call super to configure Vaadin security (must be called after oauth2Login) + super.configure(http); + + // Configure logout after super.configure() + http.logout(logout -> logout + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/") + .invalidateHttpSession(true) + .clearAuthentication(true) + ); + } + + /** + * Configure HTTP firewall to allow semicolons in URLs (needed for jsessionid). + */ + @Bean + public HttpFirewall allowSemicolonHttpFirewall() { + StrictHttpFirewall firewall = new StrictHttpFirewall(); + firewall.setAllowSemicolon(true); + return firewall; + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/config/WebClientConfig.java b/java-frontend/src/main/java/com/flightspotlight/config/WebClientConfig.java new file mode 100644 index 0000000..ee06c0c --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/config/WebClientConfig.java @@ -0,0 +1,25 @@ +package com.flightspotlight.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * WebClient configuration for Flight Blender API calls. + * + * Phase 1.3: M2M Token & Flight Blender API + */ +@Configuration +public class WebClientConfig { + + @Bean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + + @Bean + public WebClient flightBlenderWebClient(WebClient.Builder webClientBuilder) { + // Base URL will be set per request using application.properties value + return webClientBuilder.build(); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/service/FlightBlenderService.java b/java-frontend/src/main/java/com/flightspotlight/service/FlightBlenderService.java new file mode 100644 index 0000000..9bf633f --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/service/FlightBlenderService.java @@ -0,0 +1,298 @@ +package com.flightspotlight.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.flightspotlight.model.FlightDeclaration; +import com.flightspotlight.model.FlightDeclarationRequest; +import com.flightspotlight.model.FlightDeclarationResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +/** + * Service for calling Flight Blender API endpoints. + * + * Uses M2M OAuth2 tokens from Flight Passport for authentication. + * + * Phase 1.3: M2M Token & Flight Blender API + */ +@Service +public class FlightBlenderService { + + private static final Logger log = LoggerFactory.getLogger(FlightBlenderService.class); + private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30); + + private final WebClient webClient; + private final PassportTokenService passportTokenService; + private final ObjectMapper objectMapper; + + @Value("${flight.blender.base-url}") + private String baseUrl; + + @Value("${flight.blender.ping-endpoint}") + private String pingEndpoint; + + public FlightBlenderService(WebClient.Builder webClientBuilder, + PassportTokenService passportTokenService, + ObjectMapper objectMapper) { + this.webClient = webClientBuilder + .baseUrl(baseUrl) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + this.passportTokenService = passportTokenService; + this.objectMapper = objectMapper; + } + + /** + * Health check endpoint - verifies Flight Blender API is accessible. + * + * @return Response body as string + */ + public Mono ping() { + log.debug("Calling Flight Blender ping endpoint"); + + return passportTokenService.getPassportToken() + .flatMap(token -> webClient.get() + .uri(pingEndpoint) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .retrieve() + .bodyToMono(String.class) + .timeout(REQUEST_TIMEOUT) + .doOnSuccess(response -> log.debug("Flight Blender ping successful")) + .doOnError(error -> log.error("Flight Blender ping failed: {}", error.getMessage()))) + .onErrorResume(error -> { + log.error("Error calling Flight Blender ping endpoint", error); + return Mono.error(new RuntimeException("Failed to call Flight Blender API", error)); + }); + } + + /** + * Get flight declarations from Flight Blender. + * + * @param startDate Start date (YYYY-MM-DD format) + * @param endDate End date (YYYY-MM-DD format) + * @param page Page number (default: 1) + * @return FlightDeclarationResponse with list of declarations + */ + public Mono getFlightDeclarations(String startDate, String endDate, Integer page) { + log.debug("Calling Flight Blender getFlightDeclarations: startDate={}, endDate={}, page={}", + startDate, endDate, page); + + String endpoint = String.format("%s?start_date=%s&end_date=%s&page=%d", + "/flight_declaration_ops/flight_declaration", startDate, endDate, page); + + return passportTokenService.getPassportToken() + .flatMap(token -> webClient.get() + .uri(endpoint) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .retrieve() + .bodyToMono(String.class) + .timeout(REQUEST_TIMEOUT) + .map(response -> { + try { + FlightDeclarationResponse declarationResponse = + objectMapper.readValue(response, FlightDeclarationResponse.class); + log.debug("Flight declarations retrieved successfully: {} results", + declarationResponse.getResults() != null ? declarationResponse.getResults().size() : 0); + return declarationResponse; + } catch (Exception e) { + log.error("Error parsing flight declarations response: {}", e.getMessage()); + throw new RuntimeException("Failed to parse flight declarations", e); + } + }) + .doOnError(error -> log.error("Failed to retrieve flight declarations: {}", error.getMessage()))) + .onErrorResume(error -> { + log.error("Error calling Flight Blender getFlightDeclarations endpoint", error); + return Mono.error(new RuntimeException("Failed to retrieve flight declarations", error)); + }); + } + + /** + * Submit flight declaration to Flight Blender. + * + * Phase 3.2: Form Submission + * + * @param request Flight declaration request + * @return FlightDeclaration response + */ + public Mono submitFlightDeclaration(FlightDeclarationRequest request) { + log.debug("Submitting flight declaration: operator={}, type={}", + request.getOriginatingParty(), request.getTypeOfOperation()); + + String endpoint = "/flight_declaration_ops/set_flight_declaration"; + + return passportTokenService.getPassportToken() + .flatMap(token -> webClient.post() + .uri(endpoint) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .bodyValue(request) + .retrieve() + .bodyToMono(String.class) + .timeout(REQUEST_TIMEOUT) + .map(response -> { + try { + FlightDeclaration declaration = + objectMapper.readValue(response, FlightDeclaration.class); + log.debug("Flight declaration submitted successfully: id={}", declaration.getId()); + return declaration; + } catch (Exception e) { + log.error("Error parsing flight declaration response: {}", e.getMessage()); + throw new RuntimeException("Failed to parse flight declaration response", e); + } + }) + .doOnError(error -> log.error("Failed to submit flight declaration: {}", error.getMessage()))) + .onErrorResume(error -> { + log.error("Error calling Flight Blender submitFlightDeclaration endpoint", error); + return Mono.error(new RuntimeException("Failed to submit flight declaration", error)); + }); + } + + /** + * Get single flight declaration by ID. + * + * Phase 3.3: Operation Status View + * + * @param uuid Flight declaration UUID + * @return FlightDeclaration + */ + public Mono getFlightDeclarationById(String uuid) { + log.debug("Getting flight declaration by ID: {}", uuid); + + String endpoint = String.format("/flight_declaration_ops/flight_declaration/%s", uuid); + + return passportTokenService.getPassportToken() + .flatMap(token -> webClient.get() + .uri(endpoint) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .retrieve() + .bodyToMono(String.class) + .timeout(REQUEST_TIMEOUT) + .map(response -> { + try { + FlightDeclaration declaration = + objectMapper.readValue(response, FlightDeclaration.class); + log.debug("Flight declaration retrieved successfully: id={}", declaration.getId()); + return declaration; + } catch (Exception e) { + log.error("Error parsing flight declaration response: {}", e.getMessage()); + throw new RuntimeException("Failed to parse flight declaration", e); + } + }) + .doOnError(error -> log.error("Failed to retrieve flight declaration: {}", error.getMessage()))) + .onErrorResume(error -> { + log.error("Error calling Flight Blender getFlightDeclarationById endpoint", error); + return Mono.error(new RuntimeException("Failed to retrieve flight declaration", error)); + }); + } + + /** + * Approve or reject a flight declaration. + * + * Phase 4.1: Approval/Rejection Actions + * + * Based on Node.js routes/spotlight_noticeboard.js lines 450-488 + * + * @param uuid Flight declaration UUID + * @param isApproved true to approve, false to reject + * @param notes Reviewer notes + * @return Updated FlightDeclaration + */ + public Mono reviewFlightDeclaration(String uuid, boolean isApproved, String notes) { + log.debug("Reviewing flight declaration: uuid={}, approved={}", uuid, isApproved); + + String endpoint = String.format("/flight_declaration_ops/flight_declaration_review/%s", uuid); + + // Build request body + var requestBody = new java.util.HashMap(); + requestBody.put("is_approved", isApproved ? "1" : "0"); + if (notes != null && !notes.isEmpty()) { + requestBody.put("notes", notes); + } + + return passportTokenService.getPassportToken() + .flatMap(token -> webClient.put() + .uri(endpoint) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .bodyValue(requestBody) + .retrieve() + .bodyToMono(String.class) + .timeout(REQUEST_TIMEOUT) + .map(response -> { + try { + FlightDeclaration declaration = + objectMapper.readValue(response, FlightDeclaration.class); + log.debug("Flight declaration reviewed successfully: id={}, approved={}", + declaration.getId(), isApproved); + return declaration; + } catch (Exception e) { + log.error("Error parsing flight declaration review response: {}", e.getMessage()); + throw new RuntimeException("Failed to parse review response", e); + } + }) + .doOnError(error -> log.error("Failed to review flight declaration: {}", error.getMessage()))) + .onErrorResume(error -> { + log.error("Error calling Flight Blender reviewFlightDeclaration endpoint", error); + return Mono.error(new RuntimeException("Failed to review flight declaration", error)); + }); + } + + /** + * Update the state of a flight declaration. + * + * Phase 4.2: State Updates + * + * Based on Node.js routes/spotlight_noticeboard.js lines 490-528 + * + * @param uuid Flight declaration UUID + * @param newState New state (Accepted, Activated, Closed, Contingent, NonConforming) + * @param notes Optional notes about the state change + * @return Updated FlightDeclaration + */ + public Mono updateFlightDeclarationState(String uuid, String newState, String notes) { + log.debug("Updating flight declaration state: uuid={}, newState={}", uuid, newState); + + String endpoint = String.format("/flight_declaration_ops/flight_declaration_state/%s", uuid); + + // Build request body + var requestBody = new java.util.HashMap(); + requestBody.put("state", newState); + if (notes != null && !notes.isEmpty()) { + requestBody.put("notes", notes); + } + + return passportTokenService.getPassportToken() + .flatMap(token -> webClient.put() + .uri(endpoint) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .bodyValue(requestBody) + .retrieve() + .bodyToMono(String.class) + .timeout(REQUEST_TIMEOUT) + .map(response -> { + try { + FlightDeclaration declaration = + objectMapper.readValue(response, FlightDeclaration.class); + log.debug("Flight declaration state updated successfully: id={}, state={}", + declaration.getId(), newState); + return declaration; + } catch (Exception e) { + log.error("Error parsing flight declaration state update response: {}", e.getMessage()); + throw new RuntimeException("Failed to parse state update response", e); + } + }) + .doOnError(error -> log.error("Failed to update flight declaration state: {}", error.getMessage()))) + .onErrorResume(error -> { + log.error("Error calling Flight Blender updateFlightDeclarationState endpoint", error); + return Mono.error(new RuntimeException("Failed to update flight declaration state", error)); + }); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/service/PassportTokenService.java b/java-frontend/src/main/java/com/flightspotlight/service/PassportTokenService.java new file mode 100644 index 0000000..c1db4d0 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/service/PassportTokenService.java @@ -0,0 +1,125 @@ +package com.flightspotlight.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * Service for managing M2M (Machine-to-Machine) OAuth2 tokens from Flight Passport. + * + * Implements OAuth2 Client Credentials flow to obtain tokens for Flight Blender API calls. + * Tokens are cached in Redis with 3500s TTL (matching Node.js implementation). + * + * Phase 1.3: M2M Token & Flight Blender API + */ +@Service +public class PassportTokenService { + + private static final Logger log = LoggerFactory.getLogger(PassportTokenService.class); + private static final String CACHE_KEY = "blender_passport_token"; + private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(10); + + private final WebClient webClient; + private final ObjectMapper objectMapper; + private final RedisTemplate redisTemplate; + private final ValueOperations redisOps; + + @Value("${flight.passport.m2m.client-id}") + private String clientId; + + @Value("${flight.passport.m2m.client-secret}") + private String clientSecret; + + @Value("${flight.passport.m2m.scope}") + private String scope; + + @Value("${flight.passport.m2m.audience}") + private String audience; + + @Value("${flight.passport.m2m.token-url}") + private String tokenUrl; + + @Value("${PASSPORT_URL}") + private String passportBaseUrl; + + @Value("${flight.passport.m2m.token-cache-ttl:3500}") + private long tokenCacheTtl; + + public PassportTokenService(WebClient.Builder webClientBuilder, + ObjectMapper objectMapper, + RedisTemplate redisTemplate) { + this.webClient = webClientBuilder + .baseUrl(passportBaseUrl) + .build(); + this.objectMapper = objectMapper; + this.redisTemplate = redisTemplate; + this.redisOps = redisTemplate.opsForValue(); + } + + /** + * Get M2M access token from Flight Passport. + * Uses Client Credentials flow (OAuth2). + * Token is cached in Redis with 3500s TTL (matching Node.js implementation). + * + * @return Access token string + */ + public Mono getPassportToken() { + // Check Redis cache first + String cachedToken = redisOps.get(CACHE_KEY); + if (cachedToken != null) { + log.debug("Using cached M2M token from Redis"); + return Mono.just(cachedToken); + } + + log.debug("Requesting new M2M token from Flight Passport"); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("client_id", clientId); + formData.add("client_secret", clientSecret); + formData.add("grant_type", "client_credentials"); + formData.add("scope", scope); + formData.add("audience", audience); + + return webClient.post() + .uri(tokenUrl) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData(formData)) + .retrieve() + .bodyToMono(String.class) + .timeout(REQUEST_TIMEOUT) + .map(response -> { + try { + JsonNode jsonNode = objectMapper.readTree(response); + String accessToken = jsonNode.get("access_token").asText(); + + // Cache token in Redis with TTL (matching Node.js: 3500s) + redisOps.set(CACHE_KEY, accessToken, tokenCacheTtl, TimeUnit.SECONDS); + log.debug("Successfully obtained and cached M2M token (TTL: {}s)", tokenCacheTtl); + + return accessToken; + } catch (Exception e) { + log.error("Error parsing token response: {}", e.getMessage()); + throw new RuntimeException("Failed to parse token response", e); + } + }) + .doOnError(error -> log.error("Error obtaining M2M token: {}", error.getMessage())) + .onErrorResume(error -> { + log.error("Failed to obtain M2M token from Flight Passport", error); + return Mono.error(new RuntimeException("Failed to obtain M2M token", error)); + }); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/AuthenticatedView.java b/java-frontend/src/main/java/com/flightspotlight/view/AuthenticatedView.java new file mode 100644 index 0000000..7d9301f --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/AuthenticatedView.java @@ -0,0 +1,83 @@ +package com.flightspotlight.view; + +import com.flightspotlight.service.FlightBlenderService; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import reactor.core.publisher.Mono; + +/** + * Authenticated landing page view. + * + * Displays user information after successful OAuth2 login. + * Includes Flight Blender API integration (Phase 1.3). + * + * Phase 1.2: OAuth2 User Authentication + * Phase 1.3: M2M Token & Flight Blender API + */ +@Route("authenticated") +@AnonymousAllowed // Will be secured by Spring Security +public class AuthenticatedView extends VerticalLayout { + + private final FlightBlenderService flightBlenderService; + private final Paragraph apiStatus; + + public AuthenticatedView(@AuthenticationPrincipal OidcUser user, + FlightBlenderService flightBlenderService) { + this.flightBlenderService = flightBlenderService; + + setSizeFull(); + setAlignItems(Alignment.CENTER); + setJustifyContentMode(JustifyContentMode.CENTER); + + H1 title = new H1("Flight Spotlight"); + H2 welcome = new H2("Welcome!"); + + Paragraph userInfo = new Paragraph(); + if (user != null) { + String email = user.getEmail(); + String name = user.getFullName(); + + userInfo.setText( + String.format("Logged in as: %s (%s)", + name != null ? name : "User", + email != null ? email : "No email") + ); + } else { + userInfo.setText("Not authenticated"); + } + + Paragraph status = new Paragraph("Phase 1: Java Frontend Skeleton + Authentication"); + + // Flight Blender API integration (Phase 1.3) + apiStatus = new Paragraph("Flight Blender API: Not tested yet"); + Button testApiButton = new Button("Test Flight Blender API", e -> testFlightBlenderApi()); + testApiButton.addClassName("primary"); + + add(title, welcome, userInfo, status, apiStatus, testApiButton); + } + + private void testFlightBlenderApi() { + apiStatus.setText("Testing Flight Blender API..."); + + flightBlenderService.ping() + .subscribe( + response -> { + getUI().ifPresent(ui -> ui.access(() -> + apiStatus.setText("Flight Blender API: ✅ Success! Response: " + response) + )); + }, + error -> { + getUI().ifPresent(ui -> ui.access(() -> + apiStatus.setText("Flight Blender API: ❌ Error: " + error.getMessage()) + )); + } + ); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/HomeView.java b/java-frontend/src/main/java/com/flightspotlight/view/HomeView.java new file mode 100644 index 0000000..3bede71 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/HomeView.java @@ -0,0 +1,68 @@ +package com.flightspotlight.view; + +import com.vaadin.flow.component.Html; +import com.vaadin.flow.component.dependency.CssImport; +import com.vaadin.flow.component.dependency.StyleSheet; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; + +/** + * Landing page (home page) - accessible without authentication. + * Matches the design from the Node.js version at flight-spotlight.onrender.com + * + * Phase 1.2: OAuth2 User Authentication + */ +@Route("") +@AnonymousAllowed +@StyleSheet("https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css") +public class HomeView extends Div { + + public HomeView() { + addClassName("h-100"); + + // Create the HTML structure matching the original home.ejs + String htmlContent = """ +
+ + + + +
+
+
+ Flight Spotlight Logo +
+
+

Open airspace connectivity

+

With Flight Spotlight, you can see flights and drones in an airspace real-time and in compliance with standards. You can connect to a U-Space / UTM airspace for Network Remote ID information and live air-traffic. You can also integrate other data sources such as ADS-B feeds for showing non-drone traffic. In addition, it can display standards-compliant geofences for an area. Finally, it is compatible with different data sources to display flight information e.g. drone registration data, flight declarations and permissioning.

+
+
+ +
+
+
+
+ + +
+
+
+

+ See homepage for more information. © Openskies Aerial Technology Limited. +

+
+
+
+
+ """; + + add(new Html(htmlContent)); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/LaunchpadView.java b/java-frontend/src/main/java/com/flightspotlight/view/LaunchpadView.java new file mode 100644 index 0000000..c3ab9c6 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/LaunchpadView.java @@ -0,0 +1,502 @@ +package com.flightspotlight.view; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.flightspotlight.component.GeoJsonUpload; +import com.flightspotlight.component.MapboxMap; +import com.flightspotlight.model.FlightDeclaration; +import com.flightspotlight.model.FlightDeclarationRequest; +import com.flightspotlight.service.FlightBlenderService; +import com.flightspotlight.validator.GeoJsonValidator; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.H6; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * Launchpad view - Flight declaration submission form. + * + * Phase 3.1: Launchpad Form + */ +@Route("launchpad") +@AnonymousAllowed +public class LaunchpadView extends VerticalLayout { + + private final FlightBlenderService flightBlenderService; + private final GeoJsonValidator geoJsonValidator; + private final ObjectMapper objectMapper; + private final String mapboxAccessToken; + private final String operators; + private final Integer defaultApproved; + + // Form fields + private final GeoJsonUpload geoJsonUpload; + private final TextField operatorNameField; + private final ComboBox operatorComboBox; + private final IntegerField altitudeField; + private final DatePicker operationDatePicker; + private final ComboBox startTimeComboBox; + private final ComboBox endTimeComboBox; + private final ComboBox operationTypeComboBox; + private final MapboxMap previewMap; + private final Button submitButton; + private final Paragraph statusMessage; + + private OidcUser currentUser; + + public LaunchpadView(FlightBlenderService flightBlenderService, + GeoJsonValidator geoJsonValidator, + ObjectMapper objectMapper, + @Value("${mapbox.access-token}") String mapboxAccessToken, + @Value("${operators:}") String operators, + @Value("${default.approved:0}") Integer defaultApproved, + @AuthenticationPrincipal OidcUser user) { + this.flightBlenderService = flightBlenderService; + this.geoJsonValidator = geoJsonValidator; + this.objectMapper = objectMapper; + this.mapboxAccessToken = mapboxAccessToken; + this.operators = operators; + this.defaultApproved = defaultApproved; + this.currentUser = user; + + setSizeFull(); + setPadding(true); + setSpacing(true); + + // Title + H2 title = new H2("Submit a Flight Plan"); + Paragraph description = new Paragraph("Use this form to submit a Flight Plan into the UTM system."); + + // Status message + statusMessage = new Paragraph(); + statusMessage.setVisible(false); + + // GeoJSON upload section + geoJsonUpload = new GeoJsonUpload(geoJsonValidator); + geoJsonUpload.setOnValidGeoJson(geoJson -> updatePreviewMap(geoJson)); + + // Operator name field or combo box + if (operators == null || operators.trim().isEmpty()) { + operatorNameField = new TextField("Operator Name"); + operatorNameField.setPlaceholder("e.g. Aerobridge Drones F521"); + operatorNameField.setRequiredIndicatorVisible(true); + operatorNameField.setWidth("100%"); + operatorComboBox = null; + } else { + operatorComboBox = new ComboBox<>("Operator Name"); + List operatorList = List.of(operators.split(",")); + operatorComboBox.setItems(operatorList); + operatorComboBox.setValue(operatorList.get(0)); + operatorComboBox.setRequiredIndicatorVisible(true); + operatorComboBox.setWidth("100%"); + operatorNameField = null; + } + + // Altitude field + altitudeField = new IntegerField("Altitude (meters AGL)"); + altitudeField.setPlaceholder("e.g. 100"); + altitudeField.setRequiredIndicatorVisible(true); + altitudeField.setMin(0); + altitudeField.setMax(4000); + altitudeField.setHelperText("Altitude in meters above ground level (0-4000)"); + altitudeField.setWidth("100%"); + + // Operation date picker + operationDatePicker = new DatePicker("Operation Date"); + operationDatePicker.setRequiredIndicatorVisible(true); + operationDatePicker.setMin(LocalDate.now()); + operationDatePicker.setMax(LocalDate.now().plusDays(7)); + operationDatePicker.setValue(LocalDate.now()); + operationDatePicker.setWidth("100%"); + + // Time slots + startTimeComboBox = new ComboBox<>("Intended Start Time"); + startTimeComboBox.setItems(generateTimeSlots()); + startTimeComboBox.setItemLabelGenerator(TimeSlot::getLabel); + startTimeComboBox.setRequiredIndicatorVisible(true); + startTimeComboBox.setWidth("100%"); + startTimeComboBox.addValueChangeListener(event -> { + if (event.getValue() != null) { + updateEndTimeOptions(event.getValue()); + } + }); + + endTimeComboBox = new ComboBox<>("Intended End Time"); + endTimeComboBox.setItems(generateTimeSlots()); + endTimeComboBox.setItemLabelGenerator(TimeSlot::getLabel); + endTimeComboBox.setRequiredIndicatorVisible(true); + endTimeComboBox.setWidth("100%"); + + // Operation type + operationTypeComboBox = new ComboBox<>("Operation Type"); + operationTypeComboBox.setItems(OperationType.values()); + operationTypeComboBox.setItemLabelGenerator(OperationType::getLabel); + operationTypeComboBox.setValue(OperationType.BVLOS); + operationTypeComboBox.setRequiredIndicatorVisible(true); + operationTypeComboBox.setWidth("100%"); + + // Preview map + previewMap = new MapboxMap(); + previewMap.setAccessToken(mapboxAccessToken); + previewMap.setCenter(-79.378161, 43.659752); // Toronto coordinates + previewMap.setZoom(2.0); + previewMap.setHeight("400px"); + previewMap.setWidth("100%"); + + // Submit button + submitButton = new Button("Submit Flight Plan"); + submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + submitButton.addClickListener(event -> handleSubmit()); + + // Layout assembly + HorizontalLayout timeLayout = new HorizontalLayout(startTimeComboBox, endTimeComboBox); + timeLayout.setWidth("100%"); + + VerticalLayout formLayout = new VerticalLayout( + geoJsonUpload, + operatorNameField != null ? operatorNameField : operatorComboBox, + altitudeField, + operationDatePicker, + timeLayout, + operationTypeComboBox, + submitButton, + statusMessage + ); + formLayout.setWidth("60%"); + + VerticalLayout mapPreviewLayout = new VerticalLayout( + new H6("Flight Plan Preview"), + previewMap + ); + mapPreviewLayout.setWidth("100%"); + + add(title, description, formLayout, mapPreviewLayout); + } + + /** + * Generate time slots from 3:00 AM to 10:00 PM in 15-minute intervals + */ + private List generateTimeSlots() { + List timeSlots = new ArrayList<>(); + + // 180 minutes = 3:00 AM, 1320 minutes = 10:00 PM + for (int minutes = 180; minutes <= 1320; minutes += 15) { + int hours = minutes / 60; + int mins = minutes % 60; + timeSlots.add(new TimeSlot(minutes, hours, mins)); + } + + return timeSlots; + } + + /** + * Update end time options based on selected start time + */ + private void updateEndTimeOptions(TimeSlot startTime) { + List allTimeSlots = generateTimeSlots(); + List validEndTimes = allTimeSlots.stream() + .filter(slot -> slot.getMinutes() > startTime.getMinutes()) + .toList(); + + endTimeComboBox.setItems(validEndTimes); + + // Select first valid end time + if (!validEndTimes.isEmpty() && endTimeComboBox.getValue() == null) { + endTimeComboBox.setValue(validEndTimes.get(0)); + } + } + + /** + * Update preview map with GeoJSON + */ + private void updatePreviewMap(String geoJson) { + try { + previewMap.clearGeoJson(); + previewMap.addGeoJson(geoJson); + } catch (Exception e) { + showError("Failed to update map preview: " + e.getMessage()); + } + } + + /** + * Handle form submission + */ + private void handleSubmit() { + // Validate form + List validationErrors = validateForm(); + if (!validationErrors.isEmpty()) { + showError("Please fix the following errors: " + String.join(", ", validationErrors)); + return; + } + + try { + // Build request + FlightDeclarationRequest request = buildFlightDeclarationRequest(); + + // Submit to Flight Blender + statusMessage.setText("Submitting flight declaration..."); + statusMessage.setVisible(true); + + flightBlenderService.submitFlightDeclaration(request) + .subscribe( + declaration -> { + // Success + getUI().ifPresent(ui -> ui.access(() -> { + showSuccess("Flight declaration submitted successfully!"); + + // Navigate to operation status page + ui.navigate("launchpad/operation-status/" + declaration.getId()); + })); + }, + error -> { + // Error + getUI().ifPresent(ui -> ui.access(() -> { + showError("Failed to submit flight declaration: " + error.getMessage()); + })); + } + ); + + } catch (Exception e) { + showError("Error submitting flight declaration: " + e.getMessage()); + } + } + + /** + * Validate form fields + */ + private List validateForm() { + List errors = new ArrayList<>(); + + // GeoJSON validation + if (!geoJsonUpload.isValid()) { + errors.add("Valid GeoJSON is required"); + } + + // Operator name validation + String operatorName = getOperatorName(); + if (operatorName == null || operatorName.trim().length() < 5 || operatorName.trim().length() > 50) { + errors.add("Operator name must be between 5 and 50 characters"); + } + + // Altitude validation + if (altitudeField.getValue() == null || altitudeField.getValue() < 0 || altitudeField.getValue() > 4000) { + errors.add("Altitude must be between 0 and 4000 meters"); + } + + // Date validation + if (operationDatePicker.getValue() == null) { + errors.add("Operation date is required"); + } + + // Time validation + if (startTimeComboBox.getValue() == null) { + errors.add("Start time is required"); + } + if (endTimeComboBox.getValue() == null) { + errors.add("End time is required"); + } + if (startTimeComboBox.getValue() != null && endTimeComboBox.getValue() != null) { + if (endTimeComboBox.getValue().getMinutes() <= startTimeComboBox.getValue().getMinutes()) { + errors.add("End time must be after start time"); + } + } + + // Operation type validation + if (operationTypeComboBox.getValue() == null) { + errors.add("Operation type is required"); + } + + return errors; + } + + /** + * Build Flight Declaration Request from form data + */ + private FlightDeclarationRequest buildFlightDeclarationRequest() throws Exception { + FlightDeclarationRequest request = new FlightDeclarationRequest(); + + // Date/time + LocalDate opDate = operationDatePicker.getValue(); + TimeSlot startTime = startTimeComboBox.getValue(); + TimeSlot endTime = endTimeComboBox.getValue(); + + LocalDateTime startDateTime = LocalDateTime.of(opDate, LocalTime.of(startTime.getHours(), startTime.getMins())); + LocalDateTime endDateTime = LocalDateTime.of(opDate, LocalTime.of(endTime.getHours(), endTime.getMins())); + + request.setStartDatetime(startDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + request.setEndDatetime(endDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + + // Operator and submission info + request.setOriginatingParty(getOperatorName()); + request.setSubmittedBy(currentUser != null ? currentUser.getEmail() : "unknown@example.com"); + + // Aircraft ID (default from Node.js code) + request.setAircraftId("83a5a039-8fa0-4780-bfae-ee7ba458af0c"); + + // Operation type + request.setTypeOfOperation(operationTypeComboBox.getValue().getValue()); + + // Approval status + request.setIsApproved(defaultApproved); + + // GeoJSON with altitude + String geoJsonString = geoJsonUpload.getGeoJson(); + JsonNode geoJsonNode = objectMapper.readTree(geoJsonString); + + FlightDeclarationRequest.GeoJsonWithAltitude geoJsonWithAltitude = + addAltitudeToGeoJson(geoJsonNode, altitudeField.getValue()); + + request.setFlightDeclarationGeoJson(geoJsonWithAltitude); + + return request; + } + + /** + * Add altitude properties to GeoJSON features + */ + private FlightDeclarationRequest.GeoJsonWithAltitude addAltitudeToGeoJson(JsonNode geoJson, Integer altitude) { + FlightDeclarationRequest.GeoJsonWithAltitude result = new FlightDeclarationRequest.GeoJsonWithAltitude(); + result.setType("FeatureCollection"); + + JsonNode featuresNode = geoJson.get("features"); + if (featuresNode != null && featuresNode.isArray()) { + List features = new ArrayList<>(); + + for (JsonNode featureNode : featuresNode) { + FlightDeclarationRequest.Feature feature = new FlightDeclarationRequest.Feature(); + feature.setType("Feature"); + feature.setGeometry(featureNode.get("geometry")); + + // Add altitude properties + FlightDeclarationRequest.FeatureProperties properties = new FlightDeclarationRequest.FeatureProperties(); + properties.setMinAltitude(new FlightDeclarationRequest.Altitude(altitude)); + properties.setMaxAltitude(new FlightDeclarationRequest.Altitude(altitude)); + feature.setProperties(properties); + + features.add(feature); + } + + result.setFeatures(features.toArray(new FlightDeclarationRequest.Feature[0])); + } + + return result; + } + + /** + * Get operator name from field or combo box + */ + private String getOperatorName() { + if (operatorNameField != null) { + return operatorNameField.getValue(); + } else if (operatorComboBox != null) { + return operatorComboBox.getValue(); + } + return null; + } + + /** + * Show error message + */ + private void showError(String message) { + Notification notification = Notification.show(message, 5000, Notification.Position.MIDDLE); + notification.addThemeVariants(NotificationVariant.LUMO_ERROR); + + statusMessage.setText(message); + statusMessage.getStyle().set("color", "red"); + statusMessage.setVisible(true); + } + + /** + * Show success message + */ + private void showSuccess(String message) { + Notification notification = Notification.show(message, 5000, Notification.Position.MIDDLE); + notification.addThemeVariants(NotificationVariant.LUMO_SUCCESS); + + statusMessage.setText(message); + statusMessage.getStyle().set("color", "green"); + statusMessage.setVisible(true); + } + + // Inner classes + + /** + * Time slot representation + */ + private static class TimeSlot { + private final int minutes; // Total minutes from midnight + private final int hours; + private final int mins; + + public TimeSlot(int minutes, int hours, int mins) { + this.minutes = minutes; + this.hours = hours; + this.mins = mins; + } + + public int getMinutes() { + return minutes; + } + + public int getHours() { + return hours; + } + + public int getMins() { + return mins; + } + + public String getLabel() { + String ampm = hours < 12 ? "AM" : "PM"; + int displayHours = hours % 12; + if (displayHours == 0) displayHours = 12; + return String.format("%d:%02d %s", displayHours, mins, ampm); + } + } + + /** + * Operation type enum + */ + private enum OperationType { + VLOS(1, "VLOS"), + BVLOS(2, "BVLOS"), + CREWED(3, "Crewed"); + + private final int value; + private final String label; + + OperationType(int value, String label) { + this.value = value; + this.label = label; + } + + public int getValue() { + return value; + } + + public String getLabel() { + return label; + } + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardGlobeView.java b/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardGlobeView.java new file mode 100644 index 0000000..e23b746 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardGlobeView.java @@ -0,0 +1,145 @@ +package com.flightspotlight.view; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.flightspotlight.component.CesiumGlobe; +import com.flightspotlight.model.FlightDeclaration; +import com.flightspotlight.model.FlightDeclarationResponse; +import com.flightspotlight.service.FlightBlenderService; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import reactor.core.publisher.Mono; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * Noticeboard globe view - displays flight declarations on a 3D Cesium globe. + * + * Phase 2.3: Noticeboard Globe View (Cesium) + */ +@Route("noticeboard/globe") +@AnonymousAllowed +public class NoticeboardGlobeView extends VerticalLayout { + + private final FlightBlenderService flightBlenderService; + private final String mapboxAccessToken; + private final ObjectMapper objectMapper; + private final DatePicker startDatePicker; + private final DatePicker endDatePicker; + private final Button searchButton; + private final CesiumGlobe cesiumGlobe; + private final Paragraph statusMessage; + + public NoticeboardGlobeView(FlightBlenderService flightBlenderService, + @Value("${mapbox.access-token}") String mapboxAccessToken, + ObjectMapper objectMapper, + @AuthenticationPrincipal OidcUser user) { + this.flightBlenderService = flightBlenderService; + this.mapboxAccessToken = mapboxAccessToken; + this.objectMapper = objectMapper; + + setSizeFull(); + setPadding(true); + setSpacing(true); + + // Title + H2 title = new H2("Flight Noticeboard - Globe View"); + add(title); + + // Date range picker + startDatePicker = new DatePicker("Start Date"); + endDatePicker = new DatePicker("End Date"); + searchButton = new Button("Show Schedule", e -> searchDeclarations()); + searchButton.addClassName("primary"); + + HorizontalLayout dateLayout = new HorizontalLayout(startDatePicker, endDatePicker, searchButton); + dateLayout.setAlignItems(Alignment.END); + dateLayout.setSpacing(true); + add(dateLayout); + + // Status message + statusMessage = new Paragraph(); + statusMessage.setVisible(false); + add(statusMessage); + + // Cesium globe + cesiumGlobe = new CesiumGlobe(); + cesiumGlobe.setMapboxAccessToken(mapboxAccessToken); + cesiumGlobe.setWidthFull(); + cesiumGlobe.setHeight("600px"); + add(cesiumGlobe); + } + + private void searchDeclarations() { + LocalDate startDate = startDatePicker.getValue(); + LocalDate endDate = endDatePicker.getValue(); + + if (startDate == null || endDate == null) { + statusMessage.setText("Please select both start and end dates."); + statusMessage.setVisible(true); + return; + } + + if (startDate.isAfter(endDate)) { + statusMessage.setText("Start date must be before end date."); + statusMessage.setVisible(true); + return; + } + + statusMessage.setText("Loading flight declarations..."); + statusMessage.setVisible(true); + cesiumGlobe.clearGeoJson(); + + String startDateStr = startDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + String endDateStr = endDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + + flightBlenderService.getFlightDeclarations(startDateStr, endDateStr, 1) + .subscribe( + response -> { + getUI().ifPresent(ui -> ui.access(() -> { + List declarations = response.getResults(); + if (declarations == null || declarations.isEmpty()) { + statusMessage.setText("No flight declarations found for the selected date range."); + statusMessage.setVisible(true); + return; + } + + statusMessage.setText( + String.format("Found %d flight declaration(s)", declarations.size()) + ); + statusMessage.setVisible(true); + + // Add GeoJSON from each declaration to globe + declarations.forEach(declaration -> { + if (declaration.getFlightDeclarationGeoJson() != null) { + try { + String geoJson = objectMapper.writeValueAsString( + declaration.getFlightDeclarationGeoJson() + ); + cesiumGlobe.addGeoJson(geoJson); + } catch (Exception e) { + // Skip invalid GeoJSON + } + } + }); + })); + }, + error -> { + getUI().ifPresent(ui -> ui.access(() -> { + statusMessage.setText("Error: " + error.getMessage()); + statusMessage.setVisible(true); + })); + } + ); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardMapView.java b/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardMapView.java new file mode 100644 index 0000000..8dcf0ea --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardMapView.java @@ -0,0 +1,147 @@ +package com.flightspotlight.view; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.flightspotlight.component.MapboxMap; +import com.flightspotlight.model.FlightDeclaration; +import com.flightspotlight.model.FlightDeclarationResponse; +import com.flightspotlight.service.FlightBlenderService; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import reactor.core.publisher.Mono; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * Noticeboard map view - displays flight declarations on a 2D Mapbox map. + * + * Phase 2.2: Noticeboard Map View (Mapbox) + */ +@Route("noticeboard/map") +@AnonymousAllowed +public class NoticeboardMapView extends VerticalLayout { + + private final FlightBlenderService flightBlenderService; + private final String mapboxAccessToken; + private final ObjectMapper objectMapper; + private final DatePicker startDatePicker; + private final DatePicker endDatePicker; + private final Button searchButton; + private final MapboxMap mapboxMap; + private final Paragraph statusMessage; + + public NoticeboardMapView(FlightBlenderService flightBlenderService, + @Value("${mapbox.access-token}") String mapboxAccessToken, + ObjectMapper objectMapper, + @AuthenticationPrincipal OidcUser user) { + this.flightBlenderService = flightBlenderService; + this.mapboxAccessToken = mapboxAccessToken; + this.objectMapper = objectMapper; + + setSizeFull(); + setPadding(true); + setSpacing(true); + + // Title + H2 title = new H2("Flight Noticeboard - Map View"); + add(title); + + // Date range picker + startDatePicker = new DatePicker("Start Date"); + endDatePicker = new DatePicker("End Date"); + searchButton = new Button("Show Schedule", e -> searchDeclarations()); + searchButton.addClassName("primary"); + + HorizontalLayout dateLayout = new HorizontalLayout(startDatePicker, endDatePicker, searchButton); + dateLayout.setAlignItems(Alignment.END); + dateLayout.setSpacing(true); + add(dateLayout); + + // Status message + statusMessage = new Paragraph(); + statusMessage.setVisible(false); + add(statusMessage); + + // Mapbox map + mapboxMap = new MapboxMap(); + mapboxMap.setAccessToken(mapboxAccessToken); + mapboxMap.setCenter(-79.378161, 43.659752); // Default center + mapboxMap.setZoom(2); + mapboxMap.setWidthFull(); + mapboxMap.setHeight("600px"); + add(mapboxMap); + } + + private void searchDeclarations() { + LocalDate startDate = startDatePicker.getValue(); + LocalDate endDate = endDatePicker.getValue(); + + if (startDate == null || endDate == null) { + statusMessage.setText("Please select both start and end dates."); + statusMessage.setVisible(true); + return; + } + + if (startDate.isAfter(endDate)) { + statusMessage.setText("Start date must be before end date."); + statusMessage.setVisible(true); + return; + } + + statusMessage.setText("Loading flight declarations..."); + statusMessage.setVisible(true); + mapboxMap.clearGeoJson(); + + String startDateStr = startDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + String endDateStr = endDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + + flightBlenderService.getFlightDeclarations(startDateStr, endDateStr, 1) + .subscribe( + response -> { + getUI().ifPresent(ui -> ui.access(() -> { + List declarations = response.getResults(); + if (declarations == null || declarations.isEmpty()) { + statusMessage.setText("No flight declarations found for the selected date range."); + statusMessage.setVisible(true); + return; + } + + statusMessage.setText( + String.format("Found %d flight declaration(s)", declarations.size()) + ); + statusMessage.setVisible(true); + + // Add GeoJSON from each declaration to map + declarations.forEach(declaration -> { + if (declaration.getFlightDeclarationGeoJson() != null) { + try { + String geoJson = objectMapper.writeValueAsString( + declaration.getFlightDeclarationGeoJson() + ); + mapboxMap.addGeoJson(geoJson); + } catch (Exception e) { + // Skip invalid GeoJSON + } + } + }); + })); + }, + error -> { + getUI().ifPresent(ui -> ui.access(() -> { + statusMessage.setText("Error: " + error.getMessage()); + statusMessage.setVisible(true); + })); + } + ); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardView.java b/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardView.java new file mode 100644 index 0000000..6b5d8a4 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/NoticeboardView.java @@ -0,0 +1,276 @@ +package com.flightspotlight.view; + +import com.flightspotlight.component.ApprovalDialog; +import com.flightspotlight.component.StateUpdateDialog; +import com.flightspotlight.model.FlightDeclaration; +import com.flightspotlight.model.FlightDeclarationResponse; +import com.flightspotlight.service.FlightBlenderService; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.data.renderer.ComponentRenderer; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import reactor.core.publisher.Mono; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * Noticeboard text view - displays flight declarations in a table/grid. + * + * Phase 2.1: Noticeboard Text View + * Phase 4.1: Approval/Rejection Actions + * Phase 4.2: State Updates + */ +@Route("noticeboard") +@AnonymousAllowed +public class NoticeboardView extends VerticalLayout { + + private final FlightBlenderService flightBlenderService; + private final DatePicker startDatePicker; + private final DatePicker endDatePicker; + private final Button searchButton; + private final Grid declarationsGrid; + private final Paragraph statusMessage; + private Integer currentPage = 1; + + public NoticeboardView(FlightBlenderService flightBlenderService, + @AuthenticationPrincipal OidcUser user) { + this.flightBlenderService = flightBlenderService; + + setSizeFull(); + setPadding(true); + setSpacing(true); + + // Title + H2 title = new H2("Flight Noticeboard"); + Paragraph subtitle = new Paragraph("Operator and Status details"); + add(title, subtitle); + + // Date range picker + startDatePicker = new DatePicker("Start Date"); + endDatePicker = new DatePicker("End Date"); + searchButton = new Button("Show Schedule", e -> searchDeclarations()); + searchButton.addClassName("primary"); + + HorizontalLayout dateLayout = new HorizontalLayout(startDatePicker, endDatePicker, searchButton); + dateLayout.setAlignItems(Alignment.END); + dateLayout.setSpacing(true); + add(dateLayout); + + // Status message + statusMessage = new Paragraph(); + statusMessage.setVisible(false); + add(statusMessage); + + // Declarations grid + declarationsGrid = new Grid<>(FlightDeclaration.class, false); + declarationsGrid.addColumn(FlightDeclaration::getOriginatingParty) + .setHeader("Originating Party") + .setSortable(true); + declarationsGrid.addColumn(FlightDeclaration::getOperationTypeString) + .setHeader("Operation Type") + .setSortable(true); + declarationsGrid.addColumn(declaration -> declaration.isApproved() ? "✓" : "✗") + .setHeader("Approved"); + declarationsGrid.addColumn(FlightDeclaration::getStateString) + .setHeader("Operational Intent State") + .setSortable(true); + declarationsGrid.addColumn(declaration -> { + if (declaration.getBounds() != null && !declaration.getBounds().isEmpty()) { + return "View"; + } + return "-"; + }).setHeader("Live View"); + + // Phase 4.1: Add Actions column with approve/reject buttons + declarationsGrid.addColumn(new ComponentRenderer<>(declaration -> { + Button reviewButton = new Button("Review"); + reviewButton.addThemeVariants(ButtonVariant.LUMO_SMALL); + reviewButton.addClickListener(e -> showApprovalDialog(declaration)); + + Button updateStateButton = new Button("Update State"); + updateStateButton.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_TERTIARY); + updateStateButton.addClickListener(e -> showStateUpdateDialog(declaration)); + + return new HorizontalLayout(reviewButton, updateStateButton); + })).setHeader("Actions").setWidth("300px"); + + declarationsGrid.setWidthFull(); + declarationsGrid.setHeight("600px"); + declarationsGrid.setItems(new ArrayList<>()); + add(declarationsGrid); + + // Pagination (simple - just show current page info for now) + Paragraph pageInfo = new Paragraph(); + pageInfo.setText("Page: " + currentPage); + add(pageInfo); + } + + private void searchDeclarations() { + LocalDate startDate = startDatePicker.getValue(); + LocalDate endDate = endDatePicker.getValue(); + + if (startDate == null || endDate == null) { + statusMessage.setText("Please select both start and end dates."); + statusMessage.setVisible(true); + declarationsGrid.setItems(new ArrayList<>()); + return; + } + + if (startDate.isAfter(endDate)) { + statusMessage.setText("Start date must be before end date."); + statusMessage.setVisible(true); + declarationsGrid.setItems(new ArrayList<>()); + return; + } + + statusMessage.setText("Loading flight declarations..."); + statusMessage.setVisible(true); + + String startDateStr = startDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + String endDateStr = endDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + + flightBlenderService.getFlightDeclarations(startDateStr, endDateStr, currentPage) + .subscribe( + response -> { + getUI().ifPresent(ui -> ui.access(() -> { + List declarations = response.getResults(); + if (declarations == null) { + declarations = new ArrayList<>(); + } + + if (declarations.isEmpty()) { + statusMessage.setText("No flight declarations found for the selected date range."); + } else { + statusMessage.setText( + String.format("Found %d flight declaration(s)", declarations.size()) + ); + } + statusMessage.setVisible(true); + declarationsGrid.setItems(declarations); + })); + }, + error -> { + getUI().ifPresent(ui -> ui.access(() -> { + statusMessage.setText("Error: " + error.getMessage()); + statusMessage.setVisible(true); + declarationsGrid.setItems(new ArrayList<>()); + })); + } + ); + } + + /** + * Show approval dialog for a flight declaration. + * Phase 4.1: Approval/Rejection Actions + */ + private void showApprovalDialog(FlightDeclaration declaration) { + ApprovalDialog dialog = new ApprovalDialog( + declaration.getId(), + declaration.getOriginatingParty(), + (isApproved, notes) -> handleApproval(declaration, isApproved, notes) + ); + dialog.show(); + } + + /** + * Handle approval/rejection of a flight declaration. + * Phase 4.1: Approval/Rejection Actions + */ + private void handleApproval(FlightDeclaration declaration, boolean isApproved, String notes) { + statusMessage.setText("Processing review..."); + statusMessage.setVisible(true); + + flightBlenderService.reviewFlightDeclaration(declaration.getId(), isApproved, notes) + .subscribe( + updated -> { + getUI().ifPresent(ui -> ui.access(() -> { + Notification notification = Notification.show( + String.format("Flight declaration %s successfully", + isApproved ? "approved" : "rejected"), + 3000, + Notification.Position.TOP_CENTER + ); + notification.addThemeVariants( + isApproved ? NotificationVariant.LUMO_SUCCESS : NotificationVariant.LUMO_PRIMARY + ); + // Refresh the grid + searchDeclarations(); + })); + }, + error -> { + getUI().ifPresent(ui -> ui.access(() -> { + Notification notification = Notification.show( + "Error: " + error.getMessage(), + 5000, + Notification.Position.TOP_CENTER + ); + notification.addThemeVariants(NotificationVariant.LUMO_ERROR); + statusMessage.setText("Error processing review"); + })); + } + ); + } + + /** + * Show state update dialog for a flight declaration. + * Phase 4.2: State Updates + */ + private void showStateUpdateDialog(FlightDeclaration declaration) { + StateUpdateDialog dialog = new StateUpdateDialog( + declaration.getId(), + declaration.getStateString(), + (newState, notes) -> handleStateUpdate(declaration, newState, notes) + ); + dialog.show(); + } + + /** + * Handle state update of a flight declaration. + * Phase 4.2: State Updates + */ + private void handleStateUpdate(FlightDeclaration declaration, String newState, String notes) { + statusMessage.setText("Updating state..."); + statusMessage.setVisible(true); + + flightBlenderService.updateFlightDeclarationState(declaration.getId(), newState, notes) + .subscribe( + updated -> { + getUI().ifPresent(ui -> ui.access(() -> { + Notification notification = Notification.show( + String.format("State updated to: %s", newState), + 3000, + Notification.Position.TOP_CENTER + ); + notification.addThemeVariants(NotificationVariant.LUMO_SUCCESS); + // Refresh the grid + searchDeclarations(); + })); + }, + error -> { + getUI().ifPresent(ui -> ui.access(() -> { + Notification notification = Notification.show( + "Error: " + error.getMessage(), + 5000, + Notification.Position.TOP_CENTER + ); + notification.addThemeVariants(NotificationVariant.LUMO_ERROR); + statusMessage.setText("Error updating state"); + })); + } + ); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/OperationStatusView.java b/java-frontend/src/main/java/com/flightspotlight/view/OperationStatusView.java new file mode 100644 index 0000000..1579604 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/OperationStatusView.java @@ -0,0 +1,181 @@ +package com.flightspotlight.view; + +import com.flightspotlight.component.MapboxMap; +import com.flightspotlight.model.FlightDeclaration; +import com.flightspotlight.service.FlightBlenderService; +import com.vaadin.flow.component.html.*; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.BeforeEvent; +import com.vaadin.flow.router.HasUrlParameter; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; + +/** + * Operation Status view - displays single flight declaration details. + * + * Phase 3.3: Operation Status View + */ +@Route("launchpad/operation-status") +@AnonymousAllowed +public class OperationStatusView extends VerticalLayout implements HasUrlParameter { + + private final FlightBlenderService flightBlenderService; + private final String mapboxAccessToken; + private final H2 titleLabel; + private final Paragraph statusMessage; + private final VerticalLayout detailsLayout; + private final MapboxMap mapboxMap; + private OidcUser currentUser; + + public OperationStatusView(FlightBlenderService flightBlenderService, + @Value("${mapbox.access-token}") String mapboxAccessToken, + @AuthenticationPrincipal OidcUser user) { + this.flightBlenderService = flightBlenderService; + this.mapboxAccessToken = mapboxAccessToken; + this.currentUser = user; + + setSizeFull(); + setPadding(true); + setSpacing(true); + + // Title + titleLabel = new H2("Operation Status"); + + // Status message + statusMessage = new Paragraph(); + statusMessage.setVisible(false); + + // Details layout + detailsLayout = new VerticalLayout(); + detailsLayout.setSpacing(false); + detailsLayout.setPadding(false); + + // Map preview + mapboxMap = new MapboxMap(); + mapboxMap.setAccessToken(mapboxAccessToken); + mapboxMap.setCenter(-79.378161, 43.659752); + mapboxMap.setZoom(2.0); + mapboxMap.setHeight("400px"); + mapboxMap.setWidth("100%"); + + add(titleLabel, statusMessage, detailsLayout, new H6("Flight Path"), mapboxMap); + } + + @Override + public void setParameter(BeforeEvent event, String uuid) { + // Validate UUID format + if (uuid == null || !isValidUUID(uuid)) { + showError("Invalid operation ID format"); + return; + } + + // Load flight declaration + loadFlightDeclaration(uuid); + } + + /** + * Load flight declaration by UUID + */ + private void loadFlightDeclaration(String uuid) { + statusMessage.setText("Loading operation status..."); + statusMessage.setVisible(true); + + flightBlenderService.getFlightDeclarationById(uuid) + .subscribe( + declaration -> { + getUI().ifPresent(ui -> ui.access(() -> { + displayFlightDeclaration(declaration); + statusMessage.setVisible(false); + })); + }, + error -> { + getUI().ifPresent(ui -> ui.access(() -> { + showError("Failed to load operation: " + error.getMessage()); + })); + } + ); + } + + /** + * Display flight declaration details + */ + private void displayFlightDeclaration(FlightDeclaration declaration) { + detailsLayout.removeAll(); + + // Title update + titleLabel.setText("Flight Declaration: " + declaration.getId()); + + // Details + detailsLayout.add(createDetailRow("Operation ID:", declaration.getId())); + detailsLayout.add(createDetailRow("Operator:", declaration.getOriginatingParty())); + detailsLayout.add(createDetailRow("Submitted By:", declaration.getSubmittedBy())); + detailsLayout.add(createDetailRow("Start Time:", declaration.getStartDatetime())); + detailsLayout.add(createDetailRow("End Time:", declaration.getEndDatetime())); + detailsLayout.add(createDetailRow("Operation Type:", getOperationTypeLabel(declaration.getTypeOfOperation()))); + detailsLayout.add(createDetailRow("Approval Status:", + "1".equals(declaration.getIsApproved()) ? "Approved" : "Pending Approval")); + detailsLayout.add(createDetailRow("State:", declaration.getState() != null ? String.valueOf(declaration.getState()) : "N/A")); + + // Show GeoJSON on map if available + if (declaration.getFlightDeclarationGeoJson() != null) { + try { + String geoJsonString = declaration.getFlightDeclarationGeoJson().toString(); + mapboxMap.clearGeoJson(); + mapboxMap.addGeoJson(geoJsonString); + } catch (Exception e) { + showError("Failed to display flight path on map: " + e.getMessage()); + } + } + } + + /** + * Create a detail row with label and value + */ + private Div createDetailRow(String label, String value) { + Div row = new Div(); + row.getStyle().set("padding", "8px 0"); + + Span labelSpan = new Span(label); + labelSpan.getStyle() + .set("font-weight", "bold") + .set("margin-right", "8px"); + + Span valueSpan = new Span(value != null ? value : "N/A"); + + row.add(labelSpan, valueSpan); + return row; + } + + /** + * Get operation type label + */ + private String getOperationTypeLabel(Integer type) { + if (type == null) return "Unknown"; + switch (type) { + case 1: return "VLOS"; + case 2: return "BVLOS"; + case 3: return "Crewed"; + default: return "Unknown"; + } + } + + /** + * Validate UUID format + */ + private boolean isValidUUID(String uuid) { + String uuidPattern = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"; + return uuid.matches(uuidPattern); + } + + /** + * Show error message + */ + private void showError(String message) { + statusMessage.setText(message); + statusMessage.getStyle().set("color", "red"); + statusMessage.setVisible(true); + } +} diff --git a/java-frontend/src/main/java/com/flightspotlight/view/SpotlightView.java b/java-frontend/src/main/java/com/flightspotlight/view/SpotlightView.java new file mode 100644 index 0000000..87ec534 --- /dev/null +++ b/java-frontend/src/main/java/com/flightspotlight/view/SpotlightView.java @@ -0,0 +1,145 @@ +package com.flightspotlight.view; + +import com.vaadin.flow.component.AttachEvent; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.BeforeEvent; +import com.vaadin.flow.router.HasUrlParameter; +import com.vaadin.flow.router.OptionalParameter; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.flightspotlight.component.CesiumGlobe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Spotlight view - real-time air traffic visualization. + * + * Phase 5.3: Spotlight View + * + * Displays real-time air traffic observations using Cesium.js. + * Integrates with Vaadin Push for server-initiated updates. + * + * Based on Node.js views/spotlight.ejs and routes/spotlight_noticeboard.js lines 224-302 + * + * URL Parameters: + * - lat: Latitude (optional, defaults to 28.5) + * - lng: Longitude (optional, defaults to 77.1) + * - view: View mode (optional: "3d" or "map") + */ +@Route("spotlight") +@AnonymousAllowed +public class SpotlightView extends VerticalLayout implements HasUrlParameter { + + private static final Logger log = LoggerFactory.getLogger(SpotlightView.class); + + private CesiumGlobe globe; + private Div statusDiv; + private double latitude = 28.5; // Default Delhi coordinates + private double longitude = 77.1; + private String viewMode = "3d"; + + public SpotlightView() { + setSizeFull(); + setPadding(false); + setSpacing(false); + + // Title + H2 title = new H2("Real-time Air Traffic Spotlight"); + title.getStyle().set("padding", "20px"); + add(title); + + // Status message + statusDiv = new Div(); + statusDiv.getStyle() + .set("padding", "10px 20px") + .set("background-color", "var(--lumo-primary-color-10pct)") + .set("border-left", "4px solid var(--lumo-primary-color)"); + statusDiv.setText("Initializing real-time tracking..."); + add(statusDiv); + + // Cesium globe + globe = new CesiumGlobe(); + globe.setSizeFull(); + add(globe); + + setFlexGrow(1, globe); + } + + @Override + public void setParameter(BeforeEvent event, @OptionalParameter String parameter) { + // Parse query parameters for lat/lng/view + var queryParams = event.getLocation().getQueryParameters(); + + if (queryParams.getParameters().containsKey("lat")) { + try { + latitude = Double.parseDouble( + queryParams.getParameters().get("lat").get(0) + ); + } catch (Exception e) { + log.warn("Invalid latitude parameter", e); + } + } + + if (queryParams.getParameters().containsKey("lng")) { + try { + longitude = Double.parseDouble( + queryParams.getParameters().get("lng").get(0) + ); + } catch (Exception e) { + log.warn("Invalid longitude parameter", e); + } + } + + if (queryParams.getParameters().containsKey("view")) { + viewMode = queryParams.getParameters().get("view").get(0); + } + + log.info("Spotlight view initialized: lat={}, lng={}, view={}", + latitude, longitude, viewMode); + } + + @Override + protected void onAttach(AttachEvent attachEvent) { + super.onAttach(attachEvent); + + // Set globe camera to requested coordinates + globe.setCameraPosition(longitude, latitude, 10000000); // 10,000km height for overview + + // Start scanning for observations + getUI().ifPresent(ui -> { + ui.access(() -> { + statusDiv.setText("Scanning for air traffic in the area... (60 second updates)"); + }); + }); + + // Note: Real-time updates would be pushed via SpotlightPushService + // which would poll Tile38 and push updates to this view + // For now, this is a placeholder for the full implementation + } + + /** + * Method to receive real-time updates from SpotlightPushService. + * Phase 5.2: Vaadin Push Integration + */ + public void updateObservations(String observationsJson) { + getUI().ifPresent(ui -> { + ui.access(() -> { + try { + // Parse observations and update globe + // globe.updateObservations(observationsJson); + statusDiv.setText("Last updated: " + + java.time.LocalDateTime.now().format( + java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss") + ) + ); + } catch (Exception e) { + log.error("Error updating observations", e); + statusDiv.setText("Error updating observations: " + e.getMessage()); + } + }); + }); + } +} diff --git a/java-frontend/src/main/resources/application.properties b/java-frontend/src/main/resources/application.properties new file mode 100644 index 0000000..569479d --- /dev/null +++ b/java-frontend/src/main/resources/application.properties @@ -0,0 +1,59 @@ +# Flight Spotlight Java Frontend Configuration +# Phase 1: Java Frontend Skeleton + Authentication + +# Server Configuration +server.port=${PORT:8080} + +# Application Name +spring.application.name=flight-spotlight-java + +# Vaadin Configuration +vaadin.servlet.production-mode=false +vaadin.whitelisted-packages=com.flightspotlight + +# OAuth2 / OIDC Configuration (User Authentication) +# These values should match the Node.js environment variables +spring.security.oauth2.client.registration.flight-passport.client-id=${CLIENT_ID} +spring.security.oauth2.client.registration.flight-passport.client-secret=${CLIENT_SECRET} +spring.security.oauth2.client.registration.flight-passport.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.flight-passport.redirect-uri=${SPOTLIGHT_BASE_URL}/login/oauth2/code/flight-passport +spring.security.oauth2.client.registration.flight-passport.scope=openid,profile + +spring.security.oauth2.client.provider.flight-passport.authorization-uri=${OIDC_DOMAIN}/authorize +spring.security.oauth2.client.provider.flight-passport.token-uri=${OIDC_DOMAIN}/token/ +spring.security.oauth2.client.provider.flight-passport.user-info-uri=${OIDC_DOMAIN}/userinfo/ +spring.security.oauth2.client.provider.flight-passport.jwk-set-uri=${OIDC_DOMAIN}/.well-known/jwks.json +spring.security.oauth2.client.provider.flight-passport.user-name-attribute=sub + +# Flight Passport M2M Token Configuration (for Flight Blender API calls) +flight.passport.m2m.client-id=${PASSPORT_BLENDER_CLIENT_ID} +flight.passport.m2m.client-secret=${PASSPORT_BLENDER_CLIENT_SECRET} +flight.passport.m2m.scope=${PASSPORT_BLENDER_SCOPE:blender.read blender.write} +flight.passport.m2m.audience=${PASSPORT_BLENDER_AUDIENCE} +flight.passport.m2m.token-url=${PASSPORT_URL}/oauth/token/ +flight.passport.m2m.token-cache-key=blender_passport_token +flight.passport.m2m.token-cache-ttl=3500 + +# Flight Blender API Configuration +flight.blender.base-url=${BLENDER_BASE_URL} +flight.blender.ping-endpoint=/ping +flight.blender.declarations-endpoint=/flight_declaration_ops/flight_declaration + +# Redis Configuration (for token caching) +# Note: REDIS_URL takes precedence if provided (format: rediss://host:port or redis://host:port) +# SSL is automatically detected from the URL scheme (rediss:// = SSL enabled) +spring.data.redis.host=${REDIS_HOST:localhost} +spring.data.redis.port=${REDIS_PORT:6379} +spring.data.redis.password=${REDIS_PASSWORD:} + +# Cache Configuration +spring.cache.type=redis +spring.cache.redis.time-to-live=3500000 + +# Mapbox Configuration (for future phases) +mapbox.access-token=${MAPBOX_KEY} + +# Logging +logging.level.com.flightspotlight=DEBUG +logging.level.org.springframework.security=DEBUG +logging.level.org.springframework.web=DEBUG diff --git a/java-frontend/src/main/resources/static/images/favicon.png b/java-frontend/src/main/resources/static/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7607d0a2cc95030c972c0f2047073d056188fd95 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIICy0X`wFM^0b*`t|F|&HG-wdb8``shf8nE?B)o_mCmT*g#Jg#}JM4$pS9S zJSm(O1_v0qBN_x1bq#V@TzD_C_{_M-!qjt-rA08!K(n!t$uHWa*_W5OwUNcgp+)iJ sMPF7Ag)J;hzAQo(XAX3*bv+hiIJ!}}hIz$ADWKsDp00i_>zopr08ofOHvj+t literal 0 HcmV?d00001 diff --git a/java-frontend/src/main/resources/static/images/logo_transparent.png b/java-frontend/src/main/resources/static/images/logo_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..10d7e312235308ade1d6f17557fcc82a5e02ee17 GIT binary patch literal 5561 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 literal 0 HcmV?d00001 diff --git a/java-frontend/src/main/resources/static/images/logo_transparent_sm.png b/java-frontend/src/main/resources/static/images/logo_transparent_sm.png new file mode 100644 index 0000000000000000000000000000000000000000..d6e8998ce8d4cf1940f4c23b7ce6c80a7308d434 GIT binary patch literal 8324 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 literal 0 HcmV?d00001 diff --git a/java-frontend/src/main/resources/static/images/spinner.gif b/java-frontend/src/main/resources/static/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..2d888c70b06602fdc1c00a3b7a44f6097508fa62 GIT binary patch literal 109248 zcmdSBc{tR4|MzbhjD2QogTc%g`_kCA7R%W8trQhf5yn_TXQi1j7+Z}c*$qi56(LJ1 z##mD!l|ms~J6g3kfA6dLUhaM=kxWkceFD#3+m_U z=la6+k3gYN!GDR+|8azWMMOkIOiWB%TpR|2!QpTO0wM9wNdAhHl$5lzw2X`l5{Z

g(cpxxx1XW(^|9%XODa`ybyN|M)iHg06Fw2%;S7 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 S?e-yddKw`kR=L;{VpZ8^@XCI zCdd-W?H8$hqPPRC2C_s{EL9Evx$3Z!$ap{fLq`8c{Eaey32Y!OSF;af$9g8 z;d++n#0AQd)clHQ>D|9gIYgu>kS~#zpGM!Dbn1=b+vvwbM>UA!V#-?4AWJkEr4X6_ z9b}0vPY&CjNS$5J5^1;ZJ(|e7zU}i-%O)sdecMNeyDyGhAsLc6j@Fk$fKoh zD@+38xf#EVWA+!k!k+RK3WJ1N?{!bM9wx@N&UMC~@G0-dBA&Ea63Qc1k%Um*=pxx& z#mi4cj_LWO5kq^Iyk*_yI`*HjS4vB?EnOXsIp-xS?Jm1IX&O)5bfd%?^U-9k%Jo={ zRi|9=;21Oh?eS$S?o9%!N5!zXr03w;o6J6}5`oFjN8Ea{r0bN7GGG7k$;rCj9i@p9 zJSVKn)Aofs)Cpu5P46*7%?^CciIZuf&Pu@9(pL~Jqb8enS7s^-!k6b!tipU-# z3>)+yoU^w)Vl>&hlMf%;EGa&Qo47MabS>3-HH|epiP=Ow24eNmgLoKLbdX!^p{j$? zAs#Oi^!&7Bvr}dwcCdapy<@^m zou3aTkFk4cwCqPcH0yg5oi*y_8Y4N)k54L&+h2;rO}8$&`x%84_qr)kowbY>g55gC z1nQdF^SxEnCL~tu5)p@VV%ozJ^AxqbPUazUS@Ovw!7Dp^>`D<lyS!`C558-9+4Q&ELACaCc{QH}EoG?tV=}*R8}yTyq?mfB$?b+Tj5Kx>6-UAh7b{8ht+H*+ZJYR7KL}Bnr#w#f=cPp zHZ06<<_U7t{k#(JQn9uAxAzsD{5DRF4a9rwb+~5h58YNFD9udayJOEVi*NwkElx=o?^d)M&1{qTz!EnOG$C2~XGY`fjXBbebr#1q#CWQx8RjW===nLfg(vO1gsJYC<@*yfXLX$-NqeM3?myC2nZ?P136N_n$M zLQGWLR#^h3Y>o1qH^h~k!9_=B4y%}-nj}X*p(J0aE;c*5VmglUEjBn=IE&kpZZ#X1 zh)qcl4~}V0?xqCaL|(GmOK6>1qLyiy;g1}Qce$8PK6k~;%Wf~s9Cr0sg}NDxIv(Yk zm9c7od0TBe5ha6Wx>YG9JPR(18q279Z{QK6W}|W0#KTr!34S|8{_37IGJ>CPZT_&e zDfj3@hFOhb&V$E8(rJDxtXiB@tkiBr*XD4xV@C+x%Y=S;E`WMXD(5k+j%y+%lCIqW z@JaAUs%u#sHMxtKnR~sych7VC!Iq)}D)c1ni6hs=qTP-ip4A*8D~WX<6M1Yeh<1)} zGP3bm+epzFcAV?Q6*b6_{1mhH#x;^Rlwci=PUo>47WB5-G^>UPlM(KIR{XwRnr2GN zk6*HVJV%I373Sr_qJxH#WpyzsoiXvh!xcrtNb2Dpw>&*HIu5K(`cPfl5t|ogw^Qpb zAFzo==vM7Aab^1Z?!_a?z0ru%(X0y7+l*z)fZU1!T-=ku$4AM%e7E&#TL_Gp*7}<{ z{4m&(|Gf^LcKoFpldfpm(C~}?v~4cT_o2Esxw=~-li58L_GeU@ug8zNMT&H8HpA0BpwIm7I~Tj+oZAuNPykN#=tCdi=AC-i{!`SXKV6#Rrh z6p$wXP6#r8mO_Dz4_$hK(@tRKgP;&lJ%No6PB;Nxf?zsy#R-&7VA?~GbZBkzmoWM- zN+-w!fk-;QOR)db-QE3*-ucU2CkO-q!sKjDLXZiifgsN0XY=zSToz1qh{6TNXHf2H~y>?cZQnofM?3YdRFMT zx9}dx8J(H538k$no}hs96T85!(Rq!m$7l+9W-W~t)C}EKEZ0*iqmZ^B@!|Xp_%Lg5 z=F55V?W=2tg4{}PpM183kZ=ugx&Q0u?uV>B8|uX`JzV;5TVzhf_(v=&qw;Cgtsl6) zbBF;Q{%E21;XOeJk+HrF!aEmo(M1&IeZrUB3v-7QHCCQ#ghmDsj+_`HJYlHZMC?Hq zYN_lu&AQrm zpPZTqQBdxsLkJc>a}umaGCdAYD_9&D;Xa{(OBz!)rd77*ncZn92-~vQ;-+p*Q;DfG z3v4eym+OBN5S`9JkKv~s(1LCartJlU?Je{4ut2Bdv0@(FVl*;Jrt@udK4LkN7=ocE+uTRGh_t8i@FO{L>x{ThfyrRklfs#WB*EB(?}R!M87C69lzm9_1YtG@3?)L+KMHN?T{FX!<$H1n=fWrp~Sb&4o= zrkXeAwCBm)+t3IY-60`jQz^9~o~);XsirfiM7Q_<>t9WrlUDVFjdJyWOj zgIdbog!+2hw|(T()!p>_qAra^)KB3U@mt+k7v3%^H41}X zgp0cvcTx95rIAeM(z5TDDeJ0Q`1kr5@-Hgwh*0b=sPi)+>Y5bX4*ugi-qv z((M-E$i~(L*}Ximo{uRhw}$DPju5)Xn|-tOhKr6E#oOm359ya3K4_oSooi(+s;obq zGIMnj)AJh6Q}yhnZQ+8n9CMWD0UDbgw4`|(xfmYztF(=r$lchdOQf$!&YQTBV`uVx zE|F57Q(=5g>EELbhc!qxi_J0!wJ@dn=@f*#Ek@%_w;)ywsbl z{G`+c2JWb!)5N;uPD#iKNso($wiTS5wb})$*iKeHeyj3*=o)u>du`Ti=YFJH{MB$j z)Ily>O!gl`?kqoBcL84c4kt##DN@abfx{QJ6pdXFxMrmowcoPkMJX=mL#g1KVVOk! z(b3#q9{D?gvWM9`mE4(08(ny8qQAw7_Hj`y-816#isElbxrd9LqVKzX>JI!4S2Oj7 zc(gwk0(M(pnCFcdo-HxaUib=;H2Y@XnJGE(>AAQ|Km5Xshu~IAtoq$XgM1Y}%w1Jk zxYuDR$eOlzE9*CXd}hV|ep-CSlJtWfmWau1G`hsBDZ{|kgj zTld)4sdz)X#8p;m2M>%_&bGBm`+2X})_7Sj9Uw$rv5v`L{+0dF6-s!vE<~1#hxkH5 zBt=h^@|w28<9u)Lx)A9jvO}`EA?QO|wlK*4d{U>Nl>N%Q8n(FY2SVf_ckCXqt>xAF z((9#0#ex-2l(wJj(*mVN_~Zcm=j_)BAKrGjYJAf-DXk_=03osoU47bO51pRmeQpY& z@&PSZ>$5}QbC%1JREe$2Z`(sCUx|&(-D_ga(wI^{qaoGrQVT_g6-`Tu^e%)g&Bk8N z#0y5UzeLZ)S5KNg3aiC|+V+A}7BV!y5!AM`0++0V&R6k2OGy#)+l|Mi6;UL@u?+~N zi;R&(z1sX0q;i^`pS9c`tnaf}zp(>7vD(abrY{!V*+&TAYR`|cdmiepb3`)0T>5<3 zytBpy8aF-KiMH#ZrYIht{Hu_lX^5P;HLOUsvhrl+~^T^ zlSw&wmS$_M;x0LeUmg`7^yLnAW7Fnj6-@%?Juz7>AMaImo+%1)w9tFh*|~F=Ksn~S z2t-G<`$1xAr;#Vx?;4@aJ8AN`T6dzP5Wj_Evd~2{Vi|s9`kMZ5S_ds|RrxB-YPzwP zX5JXITSHAqCBb0@({m^&UFem$!$l!}pS2TyLMm_Um7ndTw9K@)&}nY>+5Oo!y->Di zw?7My&eL9I?#<-3O_afFqjjGKOF0QCsC3*gZMMrm#P={p0`SwfHD{t__-pF6*^V{o zbB_u(zW~EP2WR7=)iuu$rd5Bl+n-2#bJHI|wNcu{YP(|c*kz!}hGcTP=?v|ZZO!aK z`r{hW{V?jqI{&x9RkoW@_-14WMxlB4{9V(sSvarV=v9M`&m&1whiTy$zwE>M*|#t0 z!JM~eM!*aoyZSt#UN_8e8!(X0j)n2135C@j6jHNE5AMHd7CqZ%@P}O=ihBJP#x}LMX}1{<#@=9M20JOIySe$HBYZl9x*NGA zM4t0ErFuJVLnl~zIOEP-ACSJgAbNE2rGWn;^7Bsf4!t*Jn5MpHnG-9fQ~5sUx7hGe zm$F6uW?x!Iwj!wv@!r87^(_5*J}QD~ojn9I96zjN?_Y0a)Bjwosk(%?KQ!@uu#R$e z^MK$sWctTixKCMF!gx?;tlKTd*_(4Q>U%NG58Y^4s@mjFw3alU%W+3q^^hc*d5cdj zZ`@67W!vYpJUr=x(!^c|>B8MhP2-D)zwc|L$vD7&`%+2QP{2j>F@4FujEvZUbi{RCDC5fOuCy5yu|6>-M| z?=Yo~UN2$pEE5y?m@K4m7=3VF87*7RBwcOE71q)z6A7WyrlVXE7Df$@9Z@xRiQY8b z=v*QE`f2b%#%Np(#!yf4&<n!xUFES?JBo!Naa5O!?er z%{9`zX9u)nb_s@-zhrT-_B(VV^FG|Hw~cXnq#Cogyf`sHciljr>9^c(%$v0;@g@gQXC6NqwWXPP zOi1x?ZGIy_G0ohzKVwZ*+3}ge)}k@-tXTti`=J7Zar^9-|zJrz6y);>|VWi z`oTza>B>sv2a7f|tMb0b^Cli{cjc`!x1Sy@bZAvuB5b~QN>La=xYpnJCZWn{)A3W{ z77i@6Z9kF-YGLz5#otlz=2^YyeM@FKIL3m=8$3@imr$lzpy+o+n3gfDLokK=jE@h$ zEh4k@25lU@y2KPPe2BeBW8)WTe2PNF@J*z4KNA*A>Pr#AcBw;-!HX1r@bR9H$o1?V z`Cm6f^Iz^WKuCxpp?OJQMuP`}JKzDR zgt!vQjsvs=k3*L~FOx%b$?>fJkt=_>tmG6Ear%fr6+1Nk1Fk6nLIQZ?+)HwdX`mJY zsSuzdNQwiU5IU9wJO~&Npx@7-AIOgeln3HI&;#NjcmVo14_r-hTG{~yLT#78vxZVZ zKwt#sHF(hKIAmc%!{gBEI42$ig~-9)IN(G8i9lTh{x!fuP%#9~CpnAaz^#T(CV`L$ z-Ae)z1P=;}10)0woJ@iTvO(Z+@Q{-e;`rK}j5zcGU?cRP{1EsFAn_Ll{Q2wu93K4T zUjpzj@4p=n&5tI`XY^r|9SX?p>t*W|mI0Ubr0CGe@W_aKAZneZ6em0iG)0xFVXnp3(W_(=GbpLT>YV*}8q@TWO26%sIzG zLw=p7?XK*u9*eJ^Z*Q(jDBtE+hFkjZ`+Ict*-raESAAT|UR^S4t6HBKKQn82kY9w2 z6cyU}B^RAbnXo6|*UQ%NTJx7k?(1dizV~5;%3TEu^M@pLb|oM;ja%m+2!+bbl%wxh zY^?oK+q1oa0S1K$lmIh+Voumfzu6U9D3X}+9dEfoCChnSow#!WCXwh#PO`{TN{bbe z507@&aL|(EmChXM9@ffUa<+&U+!t-<-pE^}fIa1J?V@qsseTr#W_$b|_FFIxuA+XD z+=GI-;9t4k@ko7o65c?$ujI5KVd=K}xnbOKUFQx`WVn%jvc%ew4Zg}D+lO00$)RQW z2`X(V42gbm(x=ZQGlM@y;q)g)l8iiypQp_FB$?-=D}ykmTpW6W7wkokz6_Tc(iU>= zY&Cl#WmV8FhswrTAcdygT{4uWEus@$3N-2qL`sE-{Q^_L!-B5Y!=wtt%#YW5$?V{E zy&RUrdmtf9#u)QZ%imgpyl>kaIWX*wV`f~K%Xfcj*m;j~YFPT}7?OW@Ipm?%P5Be- z=B-kvJX1X;(`rMQi*Ab{h>I2isGgZ6e@7Qt3AS2tFzi~$;pckGA#H&MHem%LVO=4K zyei!x2d6XoLR_^z3uLvPp&fPDE2#pz-0n57S5`kHbb3wUO^UClFwX-;;DUh=XAkHj`-OX z0nZ;haE22pA|ggu-KB~nLeCRs_U*4ntocj6A9k43_>w+;#}X;~9jOp;tQz&^Ip(b{ zen%9y?#!1Rv0DOjsU2|hf|^Br_ba}R-|O-;7PM5>QYVB(&oV`^Z!CE!tw>VD{v@UG zGKp606w$zryt57=%5O%6SFV!Mr5E9Jus<`k&%9OZCgs%CoJ^^`D={+}At`DJ$S~3B-i3&ACm5t!+QeNvsDb8YsN%tCLQ_A)DlwD z2FmJ}WKQI24KVk?womWoo)JGKz@HV5k&x>q&}|CsEU}_I8y|=XRX%V?7T%V^8$dMv zQbjS)GD9-P{po&&>w*>*pWPt?Dm%}w(lLIfqx2}BjdBE-rYsXC zL(C2)dB>KL3Nyd&OJlgpCp`o@~ zbp!I9^yrS0*Lrt-xl2UAJry-%4}0@AM{Rhg|7_zQZ?klU>d(Jp{4S|+xcf)PXE=2s zh=4DCqNq{QkUrLN;)-d4v7^qJ!Av@?Tg}$PzC(y?lnVaug_tt9ZV#Y0rol=2%vnDS zt&PsogU9M>5k#Y(EvV@^Cs1mN1zof{{GQTd@Os4p0W^m6iCmUbz&s{rmO+BqztBAW z^%p!8=HTI38%{Un*na{Ifdl6Cr)t0Lr69RObJjDAR{0}K!?D42F5e! zql9)OA^rmmLcm}KQ5w)S3FJX2O7ru4B#;LI5dwo56bEq>LeM_Rp+j)a$myMg=nw!R zhY>k|2+<)pb_8??J!oYT2#MfvOz6Lp;d;addjF^;{^CIYOp4IwUnuSW$X9@d)&Koy zxIgtV1?f_1on!m@Pc*cM`{aY3C;Yj?)4-~K#>_WQ?cd^_rHa0!_~#Cf5#@qToczp) z-58dzY2%-18Xk-}NYez3Rq_KgMDj@a+gu;8kM?2g8`yGH*py}eTjT+2YcMH3Gj7DT zGwAnAs$=##$es{1tBkxL8&0xZe$`@C)>roZ8D47Wy6DlBs#D$m%m9$4*^!^9^1B%R z9qz%lCg%3~A@iUE6?cA2Yl;xle{-5F?)}UcAtREq6we}>A*yiHK#ha5rm<0Vvy9pS zt-hFgW3#!qnrKs|&WbZ#E?y}voI!dbr$bKAppTPJG|bIgBss zW1@BVPTr5!;a*YSu!IG1{@-`JH8})lx@=4f3enQLCrTKK^%cAKfNw-}(|JrhrN!;* z9$Cc2CB2Z4Et1LZxCg@Z>7-8dXISR}v*qbVqiAPO0h704_gb5OutYU?W8zB>rW#As zICSa7%ShgpbCJbt<=$^SP;`eYDRdemG3D5d!fad%*q;X%K(cM-Mm6YVn{y-=&@Qsd z+lxo6kc>?|H+;(JWsy_+u z(0aUqs82o5*o0MV5eq!)M7yTU{$`f4y!HEbucwcDRzHl{QVU-_O+VE1mHxL=EBw=o zK2gTId8yG+B$ z*}o0;v2;-Jyvx3a$V1k$`};)kwQDF0)tHrYT3%@vdopdiFqu5>8BoL_%0Uu7O%$GT-*i!-4VJtICf{)L)xn3OvD-wo7?j!jN>#por-N{M3n{vq zo3hOn!@i&&Gu2HENU@)Se2qGI!|UV{x(MHJ4BK#lh=)mv2juNMm_=lC#BB6CaSG!u z`I=a{=Kz8>%WEP@N2e~VQH+rU0c)iaR74aE^~thiF=t2WvygUSCT$o`G>v+>)nY@+ zy}}Jbeit8i@*I0NO26yA0lPP!8Xniy_;JAY_OrQ8uNzIsdvrs?-&efd9^}brtfrs6 zD7o>pcficY-M)95rJ`|eS-;uLem1a*kcZ_gJ$;BPzWenUub6%6@)+v!HH*FaIQQP=GZQPjTwG#4=*boUh}NTH2|zNfIXhWoeH?q#nK* zXQ50OMp!%1Ma#0^;VwS9|I*<0*MHhgnWMpp?sc#O3b~ zP$YN|MFM^V@CXH4fMy8nXdoB@as(DMI86l12;64iGXqG3oMfm%5`ZDdvshSI06YW_ zsDj{uE=lm9<3q652p({o!GqE+5IO$5qQfCaj@t}UEdUiET7<3*AA_2Wpnm(AeQ8;k#krQNQ*+3jXuLJ?G$8 z6CvJndR~ghmw}QU8$Y02qt^p3DK<+oYLEB+_S-vR*ozY=b?f!< z98Hb+q$pkoSZ6e<;Uu`fT+y3j|(Lm!E{?8zbfu@?W5Qg8m%OIp4>! z2|BwTcp(KvWJ{^MVa3XwTfa#xD$~hTFZ0o{Q9L(`w-nWhV#e=mGDgqAjf(1fm2bMa zB`O{qi}IBg+!LE5#(y&^HbB01rB_B?y2?Ux=kmlQ!2l<}5mIQgwSu^N{jm3j3(3qh zbvkpm0w!+T0sb?ZWB3BQX6t$LGuzoPclo!qMLp_zA!N9~Te0XQ*OaOzGwI8-{{0)) z-aZ`0s+7fjrjU%a8*(C2G#(11CefK2ySMZpTv}FrsfYaSkKYp;tf$$!keR2kV^y0LI9A0*!OMM4q4TD%@t#B$|BL>sH#d8peBL}zw;7B^Kk9q6efP;c zg|a{NkW|ykL08R@#|N|DZVfrL_gdpy29oOc{A&6p?dkBC-?vQmJ%%@+E~G0pKdgy# zXrSGZo_a7vGI1!F-P!*|=LO-;;T@$4=?|`$%%8RQo`&~+ z*RrAJ)qJ^~(HpGx*4r-oP~JF_M$BtY5fbT5bDy^46Y{j#SSV_iIZcifvqw_pFSe%z z%^G;UDY6-VCAEh+q?G(Nxx<2_qpuOWm~r#_=go@a|TrRE8n`z1^*`oyo{quId~ zTojh%yg>kI-s%l4&zLlLQ5H}@BOBP2K z20w}FPEuMjNDL^o_%>v`c_;7LD>j94M4is5=ROxiH}g_a{MO6~F2h`s5=AXqMv{af zY`iWkBmbJ^9=T-jrC7NPF6ww7TbT31@R`zqag zU=pUemAmF;Tif1iHN4aL*P~B#$BCaZU2s~BFQ{>_a?)mbJ8jc;eC1KRm2Ka)fi#xx z`6Zg+`iK8f$6<%NRZ$zZUPltg#pESCL^t2ys`3t+b;^w(n9K(ToOsVArE}j=`Lb3g zv8&Yixz7h}jR+oH?=+-V(b(oKWJfivM^bV|9SqfC3&pU@@@8Qw5)rFHLQAG2sckUK z-T>7zpM2zx2)}|KV=*X(BZ-#wcq918bl53LdD&?*OluBvqd_r3Z`P+8p6h?Yquy@0 zbrx>*g)GmzjIfLB*jsDON78XdT77R9@vv5BEIgL;#x7WD5!TFHD_s>Q?k?~R=ABo@ z9hnk(IJ^I4NNn$^JiPqk@hul|noLBFf-}p#jMm7@)H_1CY{b2y-e|P1HhIoke z-##7SaVg>z`t6mVjpu{ATD?3pz6In~QhM{g87l7)?gZr}aSNFRZk)21`A)qO0l^&pm&tUc98Bv|Ix2`;zII?KdM5!t+RYLvoLB4IhF-qqJ`W;MRsL+TGVVCKP;=M(1 zqaia?V-&wRg&_L3!xdRg_y38IC;$nGEx3OT7eaa?Bq2f&2>6eaV}at$kgp8c$v@*D zM1-6|$X_gEPHN@f%d2oM5+Q-{=ha0JUjZ};I1;cUphj?)2pnbbKt2UXi$AN2kgoW1 z76sB0LC6^bL7*c3f`A+fgg_9SAVT+tKsJPw!=LXD!Q~+&9&#!uIgLGl6hTrN3@k#W zJ%9^A`6N&j!TlkymO(linzsQtmTT9pfs8b`MFbjSQ&SVLnL(Nb)Rl7>k&|WtmLyG@16~g}v zCjN`K(6;tr!f>IQoB@3{#>{zXy-$dz{1VA3$I1Ps|PF_>)mdKAMCv{ZYFaL-1_(wvrlbV=iP{F zo;X;zv3v_Vukx4_YoXI{J0e6e?JT#%*DJ#to((1a5x_XP*~%*^l=5_+Y!AU}TzAc{ z1C&6Xu%A<}=?NG2y=Zcl()=MBoa#!P>Gy@P-MbyCOemJvA z-uhX_RpF3{nACiWxwxVNl+C%8?Dd74;<=*Z=(MW&L*kk}HUjG1q#S|Ya(4C|)_M8G z19{PZyqBpiS!^Mn;5L##s%Ru9nKst>C4vHSdas-Zs2v4~=N5(y_1GeU~( z%9HUOXHrPga)M`D$?586G6D}T{CTL8C5aOZt5s? zyCVRG+1z^4H^t>U$qW1JDd!I`&nJwjnrBGxo^zK|&yi9Q5c4~E#D^H{Lo}jiD1Hhy zMDC2{RgNLqCcrwwRY|8a@#j7k3Ak2qNqV8L-EHmt3mTI-FNqV!W$NsmrAVBRRd zoz)d<*h^rJX7p1%^&HbhZ{5J1oWDJH>ZD!4V))CKo0iFRYG{;*Ye$QX?>D=|S2sJ~ zXdW-j;}7XPkI}r{a^$wH!Gidk_o~OUWS+HEHdY-8!(MWEd-Y_ov8~Q4gRT!MAtf@{ zu{>-d)z;rTP4(7~SreNJuMT#8QS5Ws{EhIUBoDC|UzSA0vp0-r9G&``^Vh2d*Unj$ zBcA?ki_VQ{4c{o)N%lunbLy5C@WVp)dvRf%O^AK|LbvK;__Ryv=Yp3AdPBkk+pN)_ zUt1niie!EabG?n-!Z=ym9xbRs-|`3>9EbYKClD3`mnHk3Uc2SY*q{)~oOvF|6h;>w z(TJzSHToIu2_TsJOyp8*oblTx2Ij5?pl~nYeDr4}X8mQ*B$k-Uq2T8h*rE^I zSOaHCFXnlLs~w5k9V~WrwV2(g=t%USwrAQdTWFPzYF#cOWCcWbY|Kwf^0It&-rdYX zc+HP@w^q40SCR-SODfYfSyXE)bBBxPT<=15+eOE<&9OqbXY|A3v7&!WJ7!h*B}Q2u z-0&5XxQT0+`+ah;S_SR?Ar@{&QF96M#U%IE-wDn`WnAcu>;x_B+>LjvS*DAsh1Fgj zM#F1=tksvX8J9QJGx@$YhV@;t#zTcxP8L}Gje`PQ(XKfYbd>sw^QcNfqIw8F-|3+- zyTfQ^QC20&?JL7|z!kPREQr{!(5WzxZhSdI-PGBMqHe#EXfd|5jn-_778_%bN$(Vk zNxUzxtMN6aNv&cpUm#kWRA@VIPje52poOkxIv)ox;IPj_RwZCJpIb`UB*JK zx8^$bbcTDaHpH1m{1K}(^7={7_bBx9%)QF3yPw|G^2Ow}dXk!l_O9AS->y3ni%-<@ zlH-D1x}qMdP*<%e*;XvNC<<2@FY>_Ta0xX?6>TD?GBulbnrhaO@kaH|@LW1Nybgx$ zzAS?kDi%2A!OJ*AN^+ufpiUW?OO(~|%CosPYEQId%G&sJ7ur#&Unm*`n-^+(zxI0! zHvAJV-2J~aMDzbqB;=@q>r&vKFbH^%^ME!8J#dH!-6H<%Gjr|?p(8^e7(#>FKr{T= zZ3feaAj161Ng>EDbFwK=iW%sN00V)R2r%%U4julPS6)X#AQ(cL;V*#|P?7`r%$$%4 z5DCEpc=)r`3=R?hsoM-t@fTVIvLVQ+96$co^dXQ9!8sz(2|@Y;<4FT?f+d^P3g9tOopnxO8-rintX9#T?a*`+6zhsEU;W%;_XY8JVnv3SKr1NeHTHvCW^`gLFC*D z8g?c(IM9D1k^?DdZF?J9BGZUEv z!YD^kbQ8JKYK`?U%HN8sPFw7ZUJs+}MJj(lZ+HR1C?_P>!zia*z``~;tudM=y=EBEMM|?@$6|ZW0JNQ#1Mr9(&m%S zCeV7L+O)Wzr5r-);M7jn@8ZE8_<~2^6P*Z&YW+2#wxld0zh2zgz5o)c1G$i-{)rm82^`SQ?RX@{PmZ{G0XPf&7Li5FQ*O}(VL zvFv6iPPU{`P|;=eP+ZiE-286QgvAvX)pPevpUQkK=gvW;4zUw)u~&QeBWu2Jc?dH+ z4udetQ7sjD{8BVo*;`??zTdIf2``WMJ;w^+y(8?kD*+C=aIU^|nB!3-7mrXPoJqszii%30Eh}VAUNxRpM@$y;$i!KGY%TrZ6}YReQ(e;hd-R z_cdN2slm;dx*aWU!?Gd?@-^w|5hDD|PLpDtN}5`;O^9P=lvcj1%m=q#n%t6d z%0l1fJ0qR6g1Y>4Vy6xMnB!R?{{8{O8!!?T_aSIhspG~_-H=WSUbfbH)Ge{*CUQ8; zAsDf%Z%nr!^h)qS9~0NXrk2^nN7}`q^T8=Bxl!@vg3F)mQf9L{r#5kaC=T8_I+M0` zvq`w*gk9C_IQ!{Fe8^t@KQL$0lA0b~F|y5`efjyB>KrXEGo$qRwP1?>`Lwc|3A2}9 z_044&zE*tKaO1FUiK7dqDdg=f>!9hEJ&TIM_w?TrHiqU6w8l{Sf^B#=xR9C#f?KVk z=insM48F(vx5}a(e%m0CnyVTOj1c{zlu0*WUj9!Fr8HhnH^7 z;LceTftl?e*yTHqvETC$6FWs4xyVXF^}0Tk44RBNX$gPZr#)DC*)%>3W>{K{Gq((v z5jJ>Ez9~iLm-1sw)%h|6c0Ucyjw8e7lsz`;Az;Bgu-)t-TnnoxID=*$lzuk%!3 zVfsjtlAo;aovB6<)f$w!x8p35%_zChjP`ZWTE2ewxzBkwo30t!{<_jdGD?AVUv%6^ zY*!oA$k9|l4gOWq(Th)L@GIL{vbcJX*IBL2Bj(snOYE)*GyQYf{a~G4R#FR1D$;s% z*#8xFcT~^jG^_xAjK^{hpl7wussjyX@$=W@n2UF6Tjk{St!0mBb zF|m7A+|p$>(93Ok^r>GJ&-9|mu9VX_8RikwTqm((8}#B*URH|nq#hbG+_-AHiAXk1a2G0=0IIM$I8K zGQ{qvRK?i$?R=Q)*u-}hp*rd5%iN-4h-9ENXKb~Z%B_YL246Z4IadmpGv>-AA4g$$ z&5(_n4=^!Yo$hb1^XTHponz{;_%~V%o~a7B8;YVxi~P;Se%>bX3t5!K#Va?#m8R9k zy@6T8P+RmxG27T~rM>?|BozGnwc$Pv7~cE0w}uD=0$|`jzW?%l;lC#i0{Y_s;LnK@ z=;9DyAb1?1@aK{naC!*c9Rd;rPBM6a2RTRx1(=~TL?9GGS|K1r=mAcIP>~Zu0q2R} zFcF|4kPCsa3=~7)Cj&4985F>Pz&!@CAjEwf^aJ`Jc;MC$dK{sULwuZYGSCPAQ5obM z7ycPc{wtaMvq}hV4FMYhl@N5^081Gh9YTIGh^KJ;WPpv3jSR|fAjJ^O)j;qFw8G}* zW`K<#v*!9{umxNz$ z?v7w&7^WI?kJ`CCELD$2+7;SMc4gV@m-_JC-#QTvn!5$pd|nKz4(P`OcBGp(zI_x@ zZJitYji_m5==1hT0asU!!wvUaYD()d6eC2YfTNC>{CW&!yp4$caN%k%h@p7it9&aP zv-aYpqMMo)LKQoZu=ato)t;a}|PGhE0O_*;(sEq=Q+ZkcquE zTS!AB3!cm$*f1r$8xe|BDPdT%)Qf{ckxKC_GxG$`r2v#zaSqEuF_+}wOu7&-JRaj= zZG%!tpB#cK-wAWGS4r^nrANCBDZkWlXO~A~V|k6zG&4NwqtCfWA{BF}!9%gf)e1tb zH~g@PzL&qtDZa`sr`P(D_*WaG07)+}l<&-$`zKvhcGpQHR2c?$kWR|@(=R)$-4TMp zPAV^aePdtZajQVMe)@1a{v7pH>@|Y}E9GHXsJ-c`JAxi{1ojUylEkJQ&>J1@1k@MA zJ?eK$mL56N7TBco17CoBKByNTmQg0-s{L5ru(S02V`Yo5*fBv-^=HclWVLU1v9^Le zspus;Ir&3x+trFgrp1Mtut~z*fj+iujo{FHS;^~-SAW|Ro{Z5nS_x|Al6Qa`J8jJ54lzD^vNo8>C?<(S@h*8McN78PHN})9p|;`TXRL9 zEfbcMUpjCaBe{^Ip@+XWrP_Nwpyr zasJ>cmje!}Ep@Ez#7ixao!@wqf60i?$*%N*vo!~XWLnIY)=j^?m3;dX_WD`@ZdF?e z8Qed*KR&#S>a}uRs6~|cd(Nl$)h{2#_rH6&Ci8UX-NjuFbDc8BhLUy;ffLHVevAd_ zQ+Mu-*=2-x9sDWKUZAmZ-lwHx^5-D0Z1Jt@r*GfdoQHQ?M*Q3q53d()5OWsMi`BLK-S~XSZ z40{QI==lv9`imhb{5Z{pwY?A_Kll(#BJ+du)eI7n@lTJJ(ga3(DiUV?U1!T*R`UdRs8*GY$^z4+B2c6ROeDQP<-7FM4!D4QFr)pD#rS52c7o zJx^9mS1l6cuu^?6^-#2sA?6`K3w=$p*aNRq5IlxS*YAT13>;6a459Au;CO3eo#0fp zf-SDO{LrRK`qpO;vxME=mYR!YoNA^W7jY-Jm0q|Ot1kGcRgMS5nzFcU3L|j!EV|@Z zH$~OnQnf1_2hZ3#a1;G$88XS;xQ4hrzl(6zID~RL{ZfLd@e##VN_*9D84118{z&Vr zehtPOjEuE6f22QM*_gAI)=U&HY_q_QIdIoY_NJd4UUxe;i=xR{m+*2H1;S*g`96HJ z^xJ|#9`-s8W70Owy>_O8S(i8MaoT)KqrcgNf}J7+q`9< zSVV_bz<;~Tp%2_wu%_5Ht;Vm6tIK0s#rKv}sl^`H%=MFSh4Ld-kZ%n=a5RvZ%85VA zH|5nCeuBu~W>@PT5W*M)QH6Micc;rt5jByvV!lJ@aHHCUAG5ZS%&Mv&vk-3UD%i0r z=@wG?-_QdNusZj5U6{)b<7>@v0_SAqb&Ts68eLvn711y$WGKRsWfXC-stdc6oCdgAf#K>VkX(ne^#uC>xTS4!0@lzHiF#ne;zdazle%|rAy!}v&r!f zIr0CI6xlK&#E$?T+2%4>NQ9(A2pI8rJfKCkX!s{K{1Xyx^pTsa0ZYF5hCa?12-9JFN4xDFqA<>1v~(W><0?W?9d@_mVu`XAQ8C9?0uW=?rz9U zhO|ZCCId+k5)+~0hHO0%k`(`1V}bl-sKWvZEReGdGKk;-kOYDws4;`bjv+#}@}D5_ zKV#2KxS)%}RSp(0NmT}1tX$xFum2Qt@$b`&L+ zR3uk7zJFqZ)+`p>?0R;x&GM-PNj?5aTyT2yovd2JRVkZ`U-5algU_^m;i;(duP;e-by!f> zfFJkmr!*WQAlx$kmmBAPx}+V^`;1X8&D-*}PP6wZZQsurOXmjXcwyTmFnpM47FKG( z6gg}zn_x{LsZ@HnifGluP)KN(X^vO!k%N30njzgRZd|2nLn=PmOLRQZdAYRQ+}YNQ zkD%y@x-TfMRX32Mmw>WHac|L;%-72cinW0b^`f%}u1n2A~Kj{y7Gb(n)(!<3v z^@1{^I+^1R#pslWb2exfd)^Lw#>26clF4Bmn+iS7PTnZn6@;2?-cYT)Vr9SnfbjaH z=SYBUtF3O4z>bF#6rCX3`NV6w7-D5uAqW4SwEjh}%XJzUtS4sh?p#usdMoOx&1L1N z?nX_)-{@hvVJ#HRb39fF%^Fk`G3+9%WdzgbJ@bM5l4pLVl~BhdhWJevP|)qvp_F?n zoMq-ACq!aUD(w$yYdWxpL<^NgF6fg(+?_l|z%^X2)E*^I0XZe~`L4P^>nsfvp}o2^ z%-Z(sPd)2&4!`a}#b?K1+Qp|PQPX%SXI9q?HzT9gCQmZNnU$LFBy6m@iV3!8H+h1q!B@n2 z9ZmPLy0ePqZAVa#AA?m*4^oXntnFn&u=Ue%=XJfi4%kz~<)j2`wXbq`hfK4?UMbJ> z26rH}be{E}Uo6k0#Ed)9X5g>4IjYnTd4~0GGg-Do))U} zdW@>Jch5PoxccP9HnUtCszRd&PnI7(37L14ELU;k4r{D5vVR=``>>M;5WtD!V^ zvf3;(ooV{H(7R+S=Pm#^MG|o&)i=ocQgg=dL4az4qvQN$Bpwjtf*K>QoB9GiuLf~ zA->@P`fV>$45m?9bu&q?L+!NvV2F5<*T)SA516P#HIq}FU~YcpLj zcc^#Ww7qukr}>N!9d2WE2kN3%HyJ@p-OW-Kc6VJTE}Wao(bd~k)F3e^5ZOkT(=ogm zGNTwk_~bqw1wxQ zchFTZ-*qfa&GuCcWyF*v-*DF^i1>Z_WOsi8} z_9WO3z7-E9e4z2ZN_g_1Uy_#lp8g62GY}thZALcyCYliwdkKfF== zZE$|akMED-gc?}9c7H5uud&Yq{;8t!Zz4x_MTPA|vk?-qp&@dF!ij8b{1Y>RDI4}y zB7jGbMg(djWI6+W1k4C5W(W^~#k{e!0w59M#6Q(l05C$#2=;9tZ4q*u*()~y6`__2 zU_($yfpkXTGDGhVvGMO8|BxL2c7%l;PGqMM**#{!jM352Y+wZ1B6Khi?A|~#+7K;5 zx34!Iz6MS-xX%JYh`^6#=MX{5nJp~dx^)Yxr~oG#Xp6vuW^0SUab_!md-(s8V?0ux+)y=KW}T_#+22q-TIoWHYe^gZV4GzsH^=28xDR>hvWvk&EqJMk){K`|y%oeeJ>oHP8K<$V$L&-|W8<(zfe^;vY3%O^!Xi9EE(~PZ5U50rL@rSt6?Zz32 z2*2-&*QmJRqcSKoH zJ2kAltm5Ss1!0}`w1c`)tb-3 zx@9hf4Bwb$OVaaFfWD5mZ}A#T>JeG~>oG@}^7ll2f=E|5j!X)@%Ne^L+}W+pRBxGhl5|Ju!O=yf=$bA=WNa&b{sEyQUQ4Pg`q2W`Gcdv3|^sLHm z9j0li9Ca!PyxDYiSYx&+kukUl<6Usm*oA)WsOd_KqtZ=8prgvos{tz(x0@&POKhy* zmyVvd*gh=P^u;g2;?%g|t0y87J_V9q=D6fQ{Dt>J_pd%I)*8Y$wX;mjgj$|- z&U0z{6ri?Ne@5ii+FUFf-?JbBJD^d!OUwbk#;8Lxa_%G<<9F( zeO%PB`FxRW*OX5*r}3LAE9@3|XyALQPx&-g)r!-MQQm}jw?`(7JMjMLreUfr>{Ee$ zxnF==W}g~sUPd_NywSN;J7jM3<(I7U##f|A1KUi+nikKSSTxwdkNB8QmYa&V*14AVZ5(%P%dr{ci}deF}}*za|fc(Xr#;JUNWcu+fsz#L=XQK zM_kCsA>`F6n!`;5Cn54P{gr8Zv~yj;x6_@Q3qw8_x7Nc5Krn~oFTo3*^f zK-$@Xg5mzifpN2*=`y@%&aM3!htgVpDBx6=Mc($wL{l2B6WW11s-VKH>rQ^x|64rk z2~Lt0XM5uE(=7c#6?kN`zpcL`x}+jSp26vx)qUP+?DtvbBE)zL)BdJ64(IMYOB2gwt;4bF9K0Ia zb~i+SrybP6?3`MAV(_EGR#kF!ayIM@94lz2QFCzWjSLS@sgoT#=-4_?`W)pW|0 zqf`3m9OD`WJ}Oq0>&L1e>ZDxo*~_d)KdNR^-fmCluf1PJ3NgI9WJYVxwMaFs5x;kD zkt*Dtg{*|F8X8-7**}^?XvZwK6=ZN?{7Y0F$~muzyx9&So>V1 ziF9q(5nR4~&2FsZ=7Yynd96CHIl5#kMBs`GUOXcKRDrM|E;PD?KN?NP;q)?GMsEG{Lh~Qw zIK)1=((!Mzr2!nW(}@5G0S!Vu6=?7f*w2sy4GkUw6B_U#hzOPvp&~O>PhnqL zfy`#mPyvVtENAclB?3eQsYSqtwY9bEk_wO&K~H64kr_l5**z6@NrfF#gu2X-ExmCw z8yuJZYeNNyh`^9$d(!_j!N?9S0(9Jni2of_!+-5}09s?hzulI0y%C`48gD6rr!+K_ z+)xp%*yBDb8I!@fI(ZXeDqf9JaZqV=d)D!s-z7m}M^3;@(d7%*y_+35>STV7b9J_hparU>8m7#7DC4l^oiR|GLzfw$6CFJ_3(^jS*Ox5b7`<;nC8WDiQt!Z&`f*inXK?UdJj`FJXj-SN5}6nQg_GE{!h! zL3&gz5MI?DOzfd2i>#d(I>6LftiR8@lCg})(xvLbW4ATXlx^$N%@idM^x*ii=ea3( z+k%ylV&15$i%d0Ap;VVf*zGQ|*nC~CpUt&xq5C7w)UTwlp8az0x4F*9^;Af$&q9hk zJK^O~u9k)}k96|7J)$Bibe~bFMq(C;T+c{BDLhjyRU@z8wZM3aR^ADqs3m+dynGeE z7b`E;T5ORN4(G9!%?pDq76m871;c4pavIqRG6z;{6 z?wzI5=Qg>9ZGEv?VtS=a@FRs{@w~?%`p|1;QhWlT{bsW)iZyFe z>Yx}LfEF_%%zNC*cpUNpow=YoPpb9qy5T#^j~C=cnE0O6IL6Y**ReC%`QxlS!!mRK zvZ<-TL0f!WudMPoPUJ?I(0*r8w^$JKu^R${hKewCBC1dKu$kNIG5YfDUG>o&SZs`0OH$<*w+FJeYRPNYxU(O?-PY zSM)wBiT(bLTZETL>X%iDCP|~Yro?P9n_RV+)eX7#3w0DBlI>J)$VHwN|bt!9}YKBS!&9Ndsl+`DEG`RiY zxJH+}e*C;i8qIzUc$sG3EkqZ@9+msUvYxq7f5uSwx3iKjuG=CFzWhe-Ui3v}m7dSc zhcP$;+l1L{{9` z{iaKdBgyc+b_4sC(LOr4eCvCU`fZgJUO1~;ssjs*+L|xx4>M?}#RexGl~z9{X~=NZ z42`-%eYgF-ShBlTu%E-zSFNa8-k3zR`&YEe#bXohw5(l}> zcjnDtMn@X>3_D!5D6PhCi_+&eZC<>4x|8eFy)J%>1Qk3lSCA<`!vSnqVRF*!6~kg~VKsJARe3Y&9$a_gRNK{;dIDkZISn zeNVD!R|(S6;X}x-TDXy%&wZz@+;~(S@0JELeu@`SBog$Ux~6$8Tsy=TMv^RjXPXc5 zZye7S`?D=AML_@EjDGR&1Vna01+tjgMP`78>{udXG_%Xg8=ES?aR#wOfO_mjM96Cf zpML}pjY0endVu^kKAx0TDtV2yA7jtqiKlKqzGQlz~CI1Qj*h|^a1mea_BA~#HAP8xQ83j9Jpv7 z+?bO;AQhOg?JVQ<)!S9qXW*J&V`MbGJeuj0?b7l2+3@hyfcbiJY3o@3>qn|T`Cgq} z>7F0eLuP2bC3h}9wU@gpxwg>w9`2>{U|n^g>Ag^Fq&bcze)hB6YpYe@?!lWVzd@O6w(4_~e_I@f%aW6`zp#t{!3VIrH$Y zUr$|r(hk^^a?mXKe&1fv?ay`F*IE{b8-Kbuvo%&8l2I=k#y zY^~mql4I~4qKB6q8OpSXg#)4U#c=DBPYwBaj^m2q)xnEW*Kp{gvWs^D3n+`e#v-F;G0jjhp9QkQL|P4Z~92~*1}D%bFM zNv^owapfm^&)3!PS33%Mlye>w#wowwE61x`jEW+a>PQij(-coLr@ONmh~r8uai7pr zLb=z2HCbb@<4oxXa$*F*_CoBA%~C5QCmj{*q;MT0Uh;v3$r;Jz5^-K|Q!1LiL=bhE z5|el`yD#)>j{fmbd8hH-@ElfnL}-@&!u?r1qO3glhHx&=al@)CuH(V_#u1@%6?2C} z)dd#%!hck(91msoUkC~HETH#ZUCqnvkUq@ZZENMU^xZNgB_5vBJ00xz+r!eK`>2zP z*S3Y^+OIl&zV$xKO6tnzG@jKal+UpTMl~%$HMyI)FEEl#+?OAsDn|8l)pA+ldi2Y#`Iz^k>$|-}-OogK|5$Lrz?E-Q2ZKu%uRUMn_3#K%-bxiat zNxE7Td37XrZ)$g~FGYkLQOPzr!FAGKMEgpiPwWI0w%aK|LA9$x?Mg1@gh%GPC0u939miQaE};5HI$m+L zY9ydetXrH%nCfQP6)vYa4dM0LqZGz00{7%TFHQrg zTb|C6^AI3(Et~VbjHd<5Sc+%XJmOgu=_#`IiAKzFY|cO>3t9K_JZM3vFL4ZfvnHuN z97~sg1&CSrJDQh-lNBw>IgRE*@cF$o25N{tJlIr4XIt@J<0nkKq~^ zkw%9;)t}Sx^>{QtJO(K{mCu1fO!}Gknv7Mfp$J?uhO96==ClP zM|+YUX51IeW)SLql8I-e0$U_~6{%nn%sd>~Fyk66kNYnn)evJY~sE#Ba9~ zL*rdHnL96?G~&$mc!1sY?fLwg7phOS+hc%#N^l8t?FOc+V%+AbjL74yrL0bw#UF~O z6|)`9>v%7X_`p%Phs>f>JfnlYQz^k>oP8g&?RCSmJ2kqkXux{*XT!MI94tKDQ z#XjLggxMelv9;KqH&-Xg)aj!aqP+_XcbwEP_U6;|ry^GKbA);GsY z5i+AQBt-s>jP3t6WQ3e&$aV&wz;=ctM#yjmpKPFHqa-x04QLYLNDx>AC<&R*042eL z)I{hL5)^@^$R5Yp7-|GkA|xULYJ?VW{xqNnco9NIU`<00WEr6`MaY^4LL+de*-P9& zZUmu5kY)tFG$2S|N&^F$tsR0&3z$Cy_A(Skq|s>I_fnEqKWpG*#YB>W;1QlnX6atwLG@Btn1i8bFD>m#BGkfU}${m9C3Lr(WYqK$K z2*Dzx7jB%)-WWXuJ~PCEe=?T;^A7W$aPWVZ(*9e0KS0L#{~Ba$yz=zAS7(adz6I+d zhl8D$N$D)QwAq!B$j0`jYiAA9=MEb<-PE4cW~}V#O z52=}YnUNip`iGV%jr&aI2~qwyEzDe*`Qv`s3bgfl+&@uz8< zc;9E?rsbhk6thT`R=-rGLJk_Pn4{#DoJC^Tdcs z$)%gb)PQ{bym<; zeci}J-&5ALx;31KQIOZT`dAJXM!5eW(x+CxD^knC%I~;tjc;6mWQoU$s-BcUv9dnZ zKrvb&X&k$0eQF_a{pg`z)@$XJ@QrpMuO^j!MwFQ_>5h4PV7({uTOM}53O=D; zp6{D{tNcrBd#n8Purf@Cj6dcg6N!>Ff|GALCY7CtK6VpMeoW!DI<=b;^@T(-a@loC zX^-DLDJ*Vk|HPPC45e=D$g*PO?LB^Lq>MnL-oeQu%Z8+r!QXnKB+KkYoKW%aYmi%S zj}()8bc~nAAD&S|Oho#Z6>iTWE8l%4BvWdhn0mD<_#PrR%#=}}e%04{?7Lea)35MT zHQjykyFzdmy7CR~@-$VV<)YC9Yls1*oDziuJ+u<(&6I!W9{|Ga4h7KtV_!1Cm|MKoDMJZU3#u&dr@cB&?{5}e?< zJeK0A-XfxMVp$d|UKZ+cPaVY_$vj6m1h+1c)LyX2Jc46{xP?veKFiyRIDD8&Vd8sg zNhP>#d-3#XY(iICZ~6F9i(|Spc|RiKd`Y$@DW+cy*u!vid%31rw?fwzta28wT4l%~ z&u3V;Nw27KQM9?+KIR zHtg+-wTi>s;sQ2=(!(#;$#{f7ri~nG@g*z?Ep$l22Ak;W^$o=teu0!AXEElr958*U zv4rlWZo3a>eZ)9cM>`229>2OvMSQcoZoEZX;)l~#qCV)_6u(W=ymr{`%|TCHM8gpV z<>>pRobA%lC(9kjO8iX^+whO0$FwjV0=a{AB1`FyjI&|F+h%n{#!ct!+-8Nmt&hYe zFDD8Q1fUb6&AlSG3h)jVxnU{^7ILlWj@u=u0>`B-C@%#>EmzHij;wZwcGYZ>T4C|I zSeq-HyFZ`Cm_;!i=omgKpLK83aPGr~3Zn;$_NUfHSwf9fR(5YG1#4qf_KlZa9=vr9 zR{MuQ`RIS9O&%VezqYEuSN>Xk`4_(Orya(P@ClJ8aJku=-0Z+31e|~{0a!vFHAIz= zb_r~1@PL8|-bS+;Do_K}5>hH5nG$rU{~Ct`%n5NOJO9WgOlW!sL?D5i4H`1Q#D+K% zWF9vj_xqC&$<`v-(~3Zp1brCrz-edp18GO_0GEQvMfMFqAXx%W8`NV!B?h?JkZK8e z*MLPKy%IX}41$e7tOP6yjq8AfBYRv2XqS+24OL=*cMU{L2vEUs7hqz8$Hr8Uf&`cf z9+ZOI$g6B;8vrUKWkTI*NVo)IB~T~-WnJ>W6=y=f44}=6|E07kZ_`xs?1um7WkH#v z@1N|`?r8{1m2jZJH8`G^+C(^~wnX{KA$yE2E3`4vOx2e3CHcG3pHBOS$P$&T-;0cs0?8w5&gM8RfGXU_p{fg zjclms7cG4na|WGYUS`f@CATPN_s78UClR^(o7Oj}`$w?^Wj2;ZZxA7uG>NFt$QIdND7gM?C=%hUU9CWMLJo){5cxY+Vgu$!vc1Svpjc zgP7{>v(C$lS$b4(eYwxnI{h#;44tOq8RoSqAokM%v2`pz6ahVO~RMqi*{ zeqXeHhS_wbK|4lc!Rnd%?}DY2Tg%=h`$WEFWS}(QS9*AEsM?*nsB~F5>>>hNK}C zD-&(Y21h9xzhl(gs}>)JP}G0tj_O~J)hML*$Kv2t17|*-!y#Yx_7&`J!i9z@3iS4c z9LQJR8}?G5H?~M2S9w@dK`7&8r5kCX-7hMHzf#4IRD)KS8kxQ7GotzXml_$ww?0oM z^4q1H$usTDhGQoS=XQOLUCP6CJ-D@<`He)Gin0!p>*W1TdQ@?LS5PD-ZDepcTXV8& z`SmHvm&NXM*@5LvCq{6;HyTW6>_{>CMY=V1uPD2$PVnLc{#kqM)Xhyhe$MY9e3FRC zV&yNiC-JD6$i(0+^9~T~w|UwmW7ox+I&?Zb)H1ry^wj3*{qz%4H|uz1_zFg^>k_)^ zbN2TUrHk3dvpe6-@_(PsG{|0pMXpa`IAPCA`O!hVajM^?VjjNE33og1q|rVen`3&x zYkGR8e+cQ_dZ$U_vGfDlq3D?A=^Uk>yvMaeu`x?oIkM}O0ouc3XY?33YI40|x|<%x znuZ`726eU*(@9D~b9y(HVa~sc+S$@{TUY3DX}-6lI+#+KJLG!#z93UI&Nj>M94eQf z-#*{h6PC!87f0KyJ9EYr-BN2xJTsdtV`q<{ap|^4dgNIu&5T+VEPUD-%VG2NgLzNE zc)!`tSID(Ch8XwyJonwv6lLREMRG52Vp6v+IeSp+e=m&Nz2*CBz8gOLU*#D>+o%at zp{&F!aC3es8nrtTYso7bqL#CM%c1@xN?`MTMzUkwTmn74ugK#akvpEOQyhuGeJu%y-p-a{XeY+~3SK7!{6KYD;k#Z!6>MvTBUIDh!pXfJVa0~7BUU0u(yCsiMY7uD{ zC&i)+w-u8PYPrF^xZL67EeTQ>gC!9mGxf{{tjHFfB~SllZ1o(?KHsj5zqgjbN~23A zas>Jx<#<>Im7q(M_RlF%UdLsL08?jCg5U<6^(Z=m&)RZU)-OFFds~LIOtv=y%2|B_yz~o zdhuVkC{6hM;A89)o#AWg9XrNTCqvx2JMkEDSq7 zZ|5`}_TdZg8L!>lrsTQLdsz6WA$$Wk;EfE?iDv?hM?a0uRo7t_% zq)Pr|@%3=8VVOEzo66C28B=Lptv0II-Dw`8Mh7bLbhvfUC#0iAhiG2<<*L8YX3N^+ z+H;LHes=HnO|Cs@e%N>|>cPAHKh~bM34J}x&QebQ&m{U!+c*Cjbg~;a5P(7gC{(&( zS8o7}LOcpOIDb+?K?esswh{`(EWz+2)ZPXfD9}BjxgUU}07gMz5^yJAP3Vvy$VmdB z6QayN37SyX=07v@2n_Rol(aoI9*g;>`AOiGLz9m{3$Wj7A-6)0v?gY>Y^ia^X zffjs#Fbds;VV{`>3Mts}fsVoe3!PoS0oSF$18OOFP{0z>Ou@1bxHQeS)%SXb0g1N%e?y|415F@H8S<*X$mZs?=Pf)Y+dC%pp9;Z4X`l9{@K9$kS9uh(eVcWK zcBdqlo*ZtjLt16T^YBw{{Z=%%b1_^&lUIMYZP}h1qK9SX)n%*qTo9eQU6DCnca`JH z_EC|bDf`o(5=jBw!BcLo!hU@N_dmT1&$$uv+fQTMqN@7do%@Mmk(pO~*N^SgdNmfs zjO^L@QFp)}UmrcDc%R~wovL`LXI~0NF$!0@OZp8_>0tJV`I+hmwJ8pa@Sbi>p9dYA zXhjC1S!zEcD!5d97WLo>MWU;F;;OTklfn*s8G$p>x>+f^)8zcRXkMH*xKk5jhey>m zt?!Cg_U0~wpW_Q@9^Le+8#Co{>u206k*#S=>d94VBT~rNxl~Nz*R8l`E~P6BFX4t~ zT?mH9+-C{ALTxjjy=AQ{uWL+gG?%uan~&eA1KeEj)THq_9OD9GZ%U~6n!~8FMzNVB zM&qv09!d0*ws}_^me6B+#g>FgKa@9ur(tlCdlb-HsFJeix1yOk>n3r0&)lxCOudB; zHI>UJcvVU8GIHBi;0X%3rOxVF4$}8+>d#IbkUjP*Wm-tv5{6YfRsx$PXl0mT#DWBq zS&EtnrHYeT%?5}BVnaD1!Eo-fvra9h7im+9$>sGJ`Kp4jiU0a-*1Zbjnoqv%uxjq{ z2J7EN=Kdz->2Vm{+d+-w1Ai|E`m+W6kk zGiO*9aN6RN#)gw2tT~S}j*lYvXimUVr%gR+R3BAAn9r==iHM-F1XXSkF)7CzX)L5l zwcd(nTlz`n%&$XnQbYL1#9N-?l7bZNyq6L%noW*HiFQ-vR;$*oU;S{N8X z%pn#WL9-k`#7!G07Oy)g^|U+wE+LPIQ4N1hoQWVj*u5>Gq+Gl;=Jmj1uQL`lePe4} zmu^L0*=c9q_xoHtZ2Zn8?_YfiJZPFO`@;>02K&=ekvMb!VL` zB0{90rHM`>ZucVHUpvU#TvNM~cMnOc@REYUl$PfDopjGLv~DIYreBlz+jHUFOkFss zT~o>Fk+jF`4)~$CT>&RB3H48yV*P$T0i0ZQTHQ<&2Y;Rx(twKTO4kLCbYg)Y1pZ4#SR1rnMaBo(Osdp4J# zX>X--CgZ9Cb@N1M0n5m`!J!idq43m+RSCDj_nAiD`57-(vbAfgP8!xpGF*oc*Woa$ z8@H4D*N(?ik0^_6Hg~ul)bCA`j7r5KHpu7;hJ zsPpGZpX-xmWVKhr<86#*XYE}v3UUufYVGT_%IClVyf>nV!w#amHZjDxG}bHxEQqEu z4Q}EGaKcuEAAK4@=|i~kJ*wA5lv5Q9vwo}N!wcTmJ}Z^((h z^!p82r7bA?C4uoZQ;Sx;&R|VWIkL%xeO>L>(Dnq0&&Q=Ga+q%8HBK+y_++V0*Bx8p zx{l3vAa=i4H73ScEbM!g{OMP;zNQzK--Uz7Eh{TM+e9o9>cq~HeL1_4>zwk$lM3`f z80vtr)CqGhxS7|L0io~R2bHH;Tysy*$WzT-&cgy3dn~X*<|5d04jgb}(+=104}2=v znIt3Q65f+RgZu%kcm<9UK6i#W8e5XQnRjjEf31505{+Tkz5g?tlK%p5LO-(6YyT@` zLUJdhbpmsJV_|1wRp)ONoj*!CP)`SHc?0!x3~0>2NX~C1MP3yS7QI@yzv zY#SaBCa~dw*2z9N2q7i7EyK=fZrm6LNC{@ffdC54j6-WcKnaBi6BNBSHg_Pl1VSfd z#zTHQBy>V5=lmbHtl1GxFa-o21et8GgvvV`6;DX?{8Me`|CKHO-Ng8R`0oSY{P%B4 zg7VJu{=XaTZ0Yk)!1=ej_rT5TFYm>1@$;NL{AiN2_Z8Z7sPZZnbugOr_(rrYtMR;4 zOY)Ygu-60rn_Uc-^oMyKWpft=TEGloL_7J$$UCkE&vq&MM>#Zzk0BKS~5#~sI;t1BZ%eP0ZcArQI%a1)B%KLfW&5JMN zJO~86~F;Bj`gbeJ|+c*fi6*vO)1OMDj7(C}Kw zlXg1N!Ajz5;7HvamfoBz+T?I(KvWHjw%T17&hint?)n8Oq88bP*uAT&s#DFi1QnmM zYe~E$Ni(-JTSc`hx5%zJe8HP9KSa5Y7uOWWZ%?b@lW~cuBFNdb^qS5}du=zv3bu?p z@g*B~!6eo7)2lFct*g@*#LmP^Bt8Y$Opz@ky_eT5Dm|BFYaZPkujM-&7N2A>=!-~Z zWOwr=U+9G=B^_cZCnkk*Z(1r;%BA%Na>gRhCIf61Sar67d4L&T31W zTrb<)_~|!&_UKj48VxPYBh$Q6DOQ;(uo}J$pv1~ zo$jq|aWhI9zo_2Vg5RropHvT3FHs3;RQDFw$e8pJ4byyzMut|DBNC%~8+jAmD&}|- znZ3#(@n@>&iF90y>VXyIw9?f_U0UmmuETMHID0W!@0H?0sPqs)LG>MlpA}lnFAaBPdjd)rE%d zp;Xjk!NVR;D;>+a4#ww=?@eB(^Ii1qycZcLz!E0ZO-zSJ#9}dmggzvzDB=>f1WE9Y zzMz3Bj$_t;pE>lMWGid!>v_$Z0V8W$Tc>!ITQ9)KTde(8JPNIQxyTuYjAYLX*13x$ zv!1{UN+@zHMeN9=fnWT7miVsm$QjH`9p2ls-qX*zw}y1cu!`(df=?s9I8$PmnHm;$Rc=g8@S* zMAR$3CyV)lv=Fl%Jj0o2*v>@wRH1@qSaixX>Wo3JK#(I>qUjj(v%vz_MXv$XW?YF` z^PD7JKb`wA6N|{^2-Ey7-AgHvbT$?;9Xr~G-A3_Ur7wn!+G#zed$cK=mj34Y^qk|bO~l?P88R1!PQ~`Q#nlMbD_<+ zZ28pF{;Vq{bzs=#@GuvuIuPGFU7pmzA*p(jz)LSA;B3bOY!Imq{r8zjgVte~xG8cj~ZBMf02XKC{cwxxghVf?4F>h0e zWTr~p`X0yMDmwHeM2A$u&6yfSpHrr*dqvIa6q0Fi#ueoqyLDw=?LN%uC^6i2p3Iq$ z?;}ok7wFE@rEuP1G7+8C3-I@H{hxgrsN3XH2sA<^UBOa3;W7hp4UN=jlPK%1e-A#N?waS2{Y`;u^N8~2#_R;0l`Z@;3*&^|MLKr|%A?Vy)jd&oVN6Oib zGrYIFy^`tJ7W_ihx&M_k8^1mErB*M`kF@$%;I9F84Oi#alLo7pDxGc8rQ zY0?{ILcJUItXt9Ac~()uN4uukJi+~#yzEa**U4l{#h+FO-@vIN?MnliFzQ8xmN$GA za-g*~%%>mnxI>%O>6g7N-A%j{`TGNHv z8WF%#K&8Nehg425GX#}}fY8ad*+HlicttK* zpcw>pf`IW3GM;RGlRYp5-M|Li30d)=F%INTXu=%y#)0t;=o6AVLHrXCC$QFmhzZK! zAnXZ*OTe07JP7o|p~xp7PN*La-7#d3Pl5y}Xo&+U6Ikw0@DrHofG?p=(0`;_LY*K8 zD>tf^e>B8bKrIM7AYVcXCc7&RRe_-T&!0SYb`+HThyRE^{|}+?zvjmQdS?E2&^pg2 zo|_7kSrUT5gTJcDch7x1U`abq8S!2X7PL!F7V;S$UuO0azdK#4;r(s*wgUAl>2rB1 zg+m2V&1z|zT{n%DJg#lZ;}3=fK7Sf_Z!~J8Zf;&Jm09E-nu(1hnD+t zm+0aNvbB2o%ZH);xBF}i8rFXZy(*fkR+x=dSKX;+yEYjSRwtadusuLBzIQ*7a>T!K z`Rm>aE@fX)7#GYOE#(xD@y^5p!<3}(150%4WJ5S_ zjnWPQoXTy@zv!5esVH3>H|}z&*-sH?JQG)P{J>tOdA#*hxm&zh?>7nc zAVUVjZkv7;@r<_#twh*hJIy=cU^Uent6zQOq9`(DZ@-{mn68-0vuA!V>LJY$7J>re zobkS_2+R2BE^*t7>mM)a%(-Reim(Jv-uCs63#s}tdn%z#-zrWcq|{ki1-<~cJw74D z>mH;ponn)zZ(X%7M}HLz>P$qrxjVtfeQkEZtr_l#a6a=(s)1oguN}Q3!&1`VMzQ2V zB7W?b*AP5|Io(_sS5|b>XuT@edWeV;W+hDP>|sc{dfyMDB@1n%bd9@6)sUPp1-6J4~_?>^-vr`)|y z^E4d?Y0tRvr&!PHcjkG>Ybh$ao~Op2a+8C{{gONr#xKCh0u@mQJzFZQmPvN^F1UMc zPmSUtZytZDyzU8`Ok!UBnC9c6#fc1}#GT^ib0am4ClP1A+PPCSzFrNH(&W76j8gxq z87QT}D6mM0)tZV(iB&0FNv@1a8~IMUCLZ)u`D=F^s%z16tINpZ>o`=+Y@OSMJ0I;l zM})ura&vk5G3_W5_4U2&1-Y*es7$G^?IfwMA5VBt`WH`lyWIMCBnH*J_{rgE=hy8t ziehmO(dGGULNo=(xh^Nw`|*-E|Ma5Cgh!i7v3wQI;iE2>XQG#)ZWaC1Vs*R6u8f)w z)wrY1%>U8ena4D}?|mLhftIx`W#8JemAxP;C?b@yw1Ae1EQ(qX5fo%cL{wS|v@8l` zN0x$sh=`!5sOX_k5ClDJDk5sBD2vMh4CX>0}Ka9~BlV3ve z%6t2`jXtem$3DNfJL+peJSBNU{@`OfN2VLjb*Tc?4BoZeQn^w_^D=bQ4>TVYYWH}62e{^Vnv+ogjr$JU95IuwzCGHX#s!0j|=6`BJK}Q+5>-P{#mQ6|VtimGO@$$d*-n zmlu6VLX$RQP)H6;7R=<2&p<@EA6RCS&o9*V(%G_G_LAvbdriG6q&t`3HZx?#RYNpwN8Bc&r}CCI4x^Yy@ECM9H@)EClX+SgL$r(ezo6p_7dQt=c_p|M;s zw#YTis(Y2^X#SqU)a+;@P_a?_LR!-EUisSk&mRfe!A);iRoP@9B394nVw#Sw^mdqv zspqAzw&Z_!uywF2^`1^9p>6UeGM=m2yrnXQAdtK8AtFxpA(=T)Sb{!}WoYJ$^gf=m zm&?X|Iikul6eB;|C;e!kDh;b-H%=c|9vKX;9rTL|45iXwDXq-FemVUrV;P}_!Pc11QiY& ztq*|$2nxJ^Hhc*Xsjy%KfGA+S3#CuMbO+#5xK9LVnt+rkOmqT}3B0vMbiiC+^Vfyu z`agEkg<2;-o&a;MA3_(_i~!UG!kz$U3Y~R8=mcmJSUwl}>wv}nkN&!_gainnK-hE5 zWnYs)h2}cotP7P;;MyWUq(E04P(J|*1$57~xuUfpbYTuucv&0p*MaQkFQY|(^eHsg zg}Ns&T?9y9706kJ-4L_8P%OI|BC_;4JPOe)cJR zgEyOh{T%G7YQN>;^RtEhWxJyxJ1DE#blk)8nhAvsxNkzZ8&rCI1#}r`K#U zcUZyZN@7xfI6&=lV!q z9P<)Ct=+n@1eeiv!dHFciACR}go)csSkY~JX^#-TGB`EGbdi6EHo)ko%?(W#Ubyp8 zB+EUMe@py8mP%R~#&R3&wZXB}aZQB975V|C)g${T<%v=E!I0A#RRz$~x%Zdq<8}{j z)H~tSz65QGwV)rsnu#N{FULZ~2ua7=mTD8zWV~II99wzPxl=S$Ked$s({3=fA-=kp zc>?}g%j61NTPM%(az&8w&x|sesy@yu8RII1c4KKlR1>K@P<>oyL9?2DZjM#2iZiQs zNh49}4R}Z2aqqY@Dd~C{vJ!jm z38Jzrf=l8mOjEN7}E8J*){N{{RA};S@S9JcK!v@5h`jxKejjds9y}6Ig8uD|8 z0ucstPnsuWGo&x=m+3zRYa!Ow7lER#@m2zfjrG%_QPuS;ox~%TA1oiu$^)$RckIK& zh`Bw_J`x#hmxgdgy<{zMN4?LLaAmejGtvI|=AOrMEwo|nxssJVho{hVO6SY)NbHd- zJ{bu81*i1Vdy{YI2-Ah31vT|K(JI8$l<7k0eJ-?cSh>kDyJJ*AvH-br8yoTnE!be2 zAB(XIfn+NQbXxM0vTX}TvVQBmZn0DoVab30Sj|_E{v|)avW~Lzsh5EDr8n6#)}g!d zju;);!Z^bXI(FJxxeHbwPEjeg!*^g(nx z+pS-3{>_G>EYS8Z`2Hcmh+eU6wk^p-x>WFHd$9`M^P;h|;{3r4#UJPrUoLt2eOtcj z(MOQ~QYAXUyOx>L{UC9}38!vB*Tt3r(e0X4C66h^O8F4H31{9vsgs)LIcQ>M-(2Mr zPwV1`btr5Wl{>xTOM%soX({W_$unM03t$bvk0Yz%J^6M4xfpB5>FYBJoY&TB#2dFk zv`%*x=I#A?5%TG-6mIx)rQtk7IgKZ;+zjgY+y9t33eY}1nA8D2VQkB_ndpw z+ud+J_(~^3VXum@cNIv6wtwSgTmTRGAd$hbOI6ybVGIX*I_HnlVTqLYFbSxlHx3DO z+wmBJDtoVqK$@n06c%it=r`|vC0$e$!RJGIf@_pA$2u_<=Zmzwyo_$(u%YswMRi)M z;EaORUK3t&#R=bcf0lHjMH-%^kNSi>66M#x#zv!1G%=JpA z&|Cj!g`)K#Phjo{5IfiNCg7V3VH4;P3D55UzpkfFVYYKUd#;Tkt(olWRnIlyQ@DgA zJmo07+$~(o1R6(xBI zzXyPS@xS_uB~OD*Qf`|{>b7oxVU1oWdRJl~0t~Zj(`6iCziV&7_v)tI2Tq54(9EJX zzOKK&I)-u|dpvou6ZDcgpz`2N-Pw5ZE0fu1ick2a%*SuO&QvN@JN*sok&nn7ZFX#t zdw3WeHg+=jdzRDN<#3%lCrEofe81Viw-GPEetIDveg4iM&iwI|%_ARtowBw=f15Wxo?Wv!7mox+=2Mk{i@5){p@M}I$GAo|++Wt#xX(a`j_5Bi;ny&Zb5+PGggTs46qha2uvdxt(0X&R(f?$STCj zviU01>YB_`D|)#HOY|2KD&;(``B4n}xsoZ^=u%zHB&<8YIBXH$-b%gPr~@htHuz!A z?7ewXwa$~M*L!l(z|vClC7zdD6X!!8)}a>YdlHk9rP$i@v-7o=w4yGR>#Q~=)$9O1)hONU1nH5FPqezK-#riw1!!3@in*9GVf~Od!!&!PlwM4;KNmBsmZiU{=YRL z>l2I{ZC;x&VxvF@^@DCxv}cF9_nV}+dQJ!P>ZY6$vl3RMP&;Sh&L1?cvMHB&3h!jZ z_tymx6Ge2Cof4bs84U^Nd>=F=q}N-eC7kkmKuoNu)lEs*pKnkcPpw&!y7V@+u3XO0 zhOO23N)b~bDO*8cCSa$+G_kXN36zWDaEsTs$Dwp~{|L+C_0jQ-{%nL`(n5P=AdWVC za$MZalf9*jsd4YcER`~GOBfaUm(T6R@vG-rn1<| zEzz#%!Axvp+0-Ag*B%wUGBJqSUpD*mp!Z?=(Et) z5@^zfH3Vpj1FC{r+AweA3QpU9JT;7>lU`&XGdv=jIBYiS_g?ee`0n-_31Rt?H{*Ae zRwU?7YhsxXB#dr5Cb}Qi9QK%&F?wE-@GZH0>4$?}r@}d8qfWYb6WC##=)li;v-NeX zyIyR)b~G}#!lW4tpY;PpH}{lLt`L-O4#`1WtJDuzM{ZgDwio(@cR}){D|su<_X&wv zSFSN5!SIZM>ZNt7DM@_Pvv)nWLQ+64STE=$C7*jMx4|igTcbEX`!t|ar1BtlDqvQ$ z;()zD1*R7qJ_~;l;J;DRzL%$*?IwR>E}HPM-F~0NEcF7FTeyLHk-4D?{DQYZ@+Q@M z85 z;?Fr<58?``}%XPUT)LJ&v?-Yu(YBr1Lm#ACZ#WD=!3ehUCQ7jG&2Q}qvt$-YqpLZie8HLD5Iz&8)jM;(Wid7|| z4!Xh9kPNjVZECE3YrPnxZOd?->#q6;5pfUyTuG%!MbX&gho+pKC$OY|ERzL-#{Q+J zdI@=k&Ao!XZvw%Nu1|CuXT%Wi3`eyZu|`eUSJVKUq_f7dPSbn}B6zc}79q>1CUTnk z2JhD&&r?jR7lT=E-J`$H32CRYY8pT~>Br2W!y zLEcVrM20gAFCP^Nl0U67aW6m1c*m8d5dR!849w+j^K7M$2pTkBs%($xsPj>Bc=`E` z@+KTk-PdlYeP!Io%B5a&FS8}R_|>!fT5Vki`Y^19)s_3k{G7uHQRyvOU)lpB--clv zo=N_(N%CvrV^sa2$y*in^4|!LO+GX~l5?ApI`a0Me)3VlcPr7WKRm%Z_u7`5_vSj~ zQ&Y}_x~ss;xEFR%#I-W|-rO5S$=e3ynf*XLS31X$X4Qw6XauD9#i~dq4qm;aMU#A4 zdP_3C7uU>=V%b7AwVA^lsi?CX-leqS;fWAOIgK)oqApjC(Lp$mj?Q#m;>x;=(PYNm zij`$1rQ)ocfMY%IJ&T0LbeoYmO`pr+tge?s446a5eNh!VXlQa|5;^y%}W=GokG3y zmtl4xd;-h~0H=^Se;MEuGUwW5pY`M^blQPR(c1ZN06T#P=%8```2hX|9$>!%HF!W4 z1s<@$1U$fp2P9H}OMwDBfTlu;RCw!07zY(LkA(Lj*Ef%Z=}>@Oft{wc&XEvY0n=Vs zI077dA-e+6P(T?K#z6r=6et`4Wq81=|HmdgK(N0YhZN!~VB-T%h_3*w0{tUE76p9! zH8u2?Ea-ZRe$9{ve)E4;L)ZW9zZyOP+Wd>kLhTRWBWm}bs$6B%nmkpto$DWwQ|M4MiYUPi>&Zsm> zz>m!hQybH7gdQtXSSq;_MBaRCCehu?>f`iq5e3T0WJf{FCV2saW!Z!Y3WpRVjuZVk}bKMu;Ir@};QbXt$V< zF(s*(_Qfi<(@a-9nhT65>8qWNOfxgPQR0Srlq9!$g9CT#Z zP*6LVc~MUkT@hDv7Caa4Rzn)TusX_`YjEXeHET9pr|b5`+fNxG_Kp_3eeZH1?AdIy zc9g?QNqr1(L-OXP%@?n0p1M|UG8&&>6-5YoF(fq_C-~@GPksind~<{s6!Wt7yr3j|l4UI^+y`C_|b9b=B@y60|St=6%+6l(GNjMA3z zBt0G{pBSS(HLGWSfecEA@oy?hlhUkiX`Q>xL=e@tb6_Pk2Hisws@kOjrkZU&?0k-X z_>jM@_SvBc3>H45fz_59o(MCjT|VET3<{fQ(Z4b1e_fs~uq2I69uK(Pb0rdi8%6)% zm_K4gQq1SeL)>G~WlPeluM8|5UlO@LPGRCOd0sM;2IJq=@929TA7G)ae6YlfH%}MR zhgLTY5N9TP`qPJ7PqCl%Y&(uNe^i@1+}Y!Bs;YaX+RG`XASq-z!j4mOJ&KXkMvU3T zsd0@tlmsEh9OcvyW40%?eUJEA;nf(Whpadd++#y$8;^eUMe!b;pd&0sJ^VRFqXC)2 zGLQNe_=}^Ly_{ogZ6WU?w$)ofU!9*WT8xM!6Ae2HDtD_+BaBp?-v(|-`=LSK;!R;a ziHrf=I*}gNGkbu98P%XPOCDPefd0sOBe|?`)rLuhLb69d@1ZVg9Y*`y679=(CQQfw z7D{tVd#0hA@Bq3x3Z=SHpK1IFl~jJwlH=xYU%u6TT6PpIO7mZAf1TgqaQBka6jL&f z9QNSUT~8%&0A1!CKU=BQK|3xOm4^>|Al+(#h{K`t_M5;qzq*`tqy8XB7T8Z5?W0Nm zzL<#&OP90aRz(%}sMyz?)5lFbhU{@D-od;mwTZ<@2*J5tS?bK`2fc8oFKkElq(TK2 zVmq+t3K&1j_}Ii*2s>6G_(#2sezLgFa$EV98HU0vO;o96G2^jkM6q=V>a*5yMJg|6 z%g5oqCEkerWtkL7Zu`Uz_I!o4FU*Js>pa=qqu{4<-6n8mijswPS!fr@3{bMO zaYiS8t59?QgvGI8P$h?*Wy$!hP!e~#7=x94lw7@$*e!2`1hI{}RVR?1>N!vae})4= z8}@7$bJ7Z4LX~Swh8lLjETHg@hTS0-ne+~pVKqUekX*;WIusnVav;P9a8Y5NY-$)r z8t)jkx79~6i0W`HzKW}9Uqw0l1*G8Ks}cM;S}}M+;(GkoWZ7>psq7s*m5cSO1l8b3 zm%ckh<6!&nKrft_E>jeCZj6WZrd1x`(-rGrPWnm~lBOMQw`jyk@0u2~nLR#uoydJY z1oX)J#N%6yU{C#mh6evA-Mq=NL;N5!H}&%!pAD{&rUx|^UVVF&3cpKqI`Q%7E7eAf z4PMXaOq@ChZ?)UHK6x(LQ(A1KU|J=M`GLMM7UcXRZ`-}%*Nm+s$?K;eikrqF6M>VD z%0x#6WyPqlW64j@@D#)?uc$zLv$*{IKGpJsrmQ~%bVv6m*%_?-|A1LU~GP-ljj@D}BM>Eb_zYXP_ZLKlzm?iC9 zS0*amv8H1_`d9kqYkc_&R<3WP|EF#KKYjD{jyb@ZC=?2C$pJM}SR4WbP2pzJFO?yn zFC@I{1N4Q~G)=%d2hbC6&jIKJh*Q{FU*8%60-OL}0!+CcD}Qm)e~~iR7u1Cr&Na9c zo{I$FQplHUm31Lp0!dB)D*>bw(j-8T06Plpb)l@eMvVY60{SM7 zg`@}wm%syHag7)Mw}VOlPg~~zS)Tt3d~;H&Qy;kk>%y3CM!!jgf)iUCUIx`aMyNsr z56g`=U)j@e7Yn_HNNIZgTl^n(sk@ndue;J$Aje9jJqzJXo$tHbtwS$Zo9iB}bGvOh zbRaz`Olw=wx#_CAGLxuBTbgD^wui&3hwj8{kJ%|asekl+)3NgMvU#mf-*0*BJw=i+ zI5tsN{jxv(d#K%rciGI)#a-!rmn4^G&fHx6jS##1eQ{pkGah|VYb$5T=F$+4S(35Rsk*QjWzT%KnqK zjZEF+xPc-%s@)bkVsiP$w4;G{53f5Za%@=7-Kx~56MZoHR!}UC%6{k8I!~>T^S?hk z9QRr?s;1o3B2Y^)aPc`s?sYf1-tA0MQ8zCA+7v5#$gsujf{7nZ6Mmh1hg~`J7FAVe zDEsDRwDBm^h{U8Szqw>64lmbSZLF(tjm5jGy(hYuOyjY)DtNPzX-kRhfG$~dBe9%N z)Yqan+cKdsPzY9zz;z%#x@Mag@NZxy{mx65L`zf53}seIfs;QE_d+YH-{;o0KbidG zayJJzk@h*B&=-lHdY_Bx3V;8HvzJWz!w!u@-Y*c4h?>606stzh)OH)g1*howo1Ny$ z2S>pXp0QcEvh7flMMAsGW+E8>T=JC*#Xu=Waxq#N*NPh0^TX^GYDn zfoD&fvYO_XL8Lv`dycN=RLpM@BW;;aJD4N?tVfK5_sXi#YlqW@hi`18PhD*^q#=+l zI)2n}bfs==_{`fe$%q4-(U|Z^6iXtaxq=ZBUMR@|M=&^6G2v$r{u}Dy_sK{_L9x4Bu4W3huR0+jm~U=tRGoba2d<%zHO}mLa-t_}K>N zbtzU;CU2D3Qc+#96_otu>((d3k6#5xgjezhv1!-E3)|&lHPkde`bM)RHICtR-(-(^ zEoT0}p56X^xY+GJPkp0Ih)kDgireUJby!xXU9AW=(z~1O@7ug-k^cMamuI$x%>mXQ zNWU8~dFqb$f`ClOtV~?lMh)kVAeB>-veIqN<-6<~Hd#=S?sLs$!=BSI?c5Z1eR#Qx zReJR7c~(f;^^_Zv`pWPr4_F8u|EgVb^T=MbN1ErxS2^|y&3%ONv=K4G$3Zb&T!(qE zg7mnFq{qgu;<0I6;=dEnTdX149)r*Bcazzhxp$yu@y|-PzH&M@#G+j}MyY!{z(?*; z6*t(M(pxrVw8gIZ>24~#&2=G2;iSJDA~HbH|3ie04z6H_Eko1ri-pl|Ii1HJcjsA| zJxHen$j2WO$@e=oEqx(qKxyD0fs+tnq^faBa8a4w9`~ z>fZ7@6iBOId9njtpa2Q+oH3fi=Il<$k5iJN&R^d}+j7#?K!=o16#s6M$$tD*g~qa9 zAPB~RlRtwoc4jD^MciVhLo3zpr+)^<%eoTfjx#9wLOV^sk!w-(u#XEOnt`-DpI*_6 z??gqVODGS&c67)GWcC?<5ciB67tgSn{Aqp;LGu9%y-MTn0+aZJ1;QXM3vjjXI{qJE zpLtmzeQ#dxnned4g{8gBZ0VMxeBmY(d=WF6;KF>m)Qrt&5QizW+};onJUWM`GL;PR z7{hBUq96qS>W0I>$+L^@Z;keTedzo6TH&pMw`M25_EY7ACx51a+df-G{{8;_1^)d7 P{{031{RRI2`2zm|rPQ}J literal 0 HcmV?d00001