Compare commits
7 Commits
v0.0.36
...
33ce291b09
Author | SHA1 | Date | |
---|---|---|---|
33ce291b09 | |||
4e07c10969 | |||
bc63b57154 | |||
fa083a1080 | |||
c448e2dfb7 | |||
d9061388dd | |||
e764393706 |
@@ -25,8 +25,6 @@ jobs:
|
||||
ls -la
|
||||
# only install dev-packages
|
||||
pipenv install --categories=dev-packages
|
||||
pipenv run pip freeze
|
||||
|
||||
working-directory: backend
|
||||
|
||||
- name: Run linter
|
||||
|
@@ -25,7 +25,6 @@ jobs:
|
||||
ls -la
|
||||
# install all packages, including dev-packages
|
||||
pipenv install --dev
|
||||
pipenv run pip freeze
|
||||
working-directory: backend
|
||||
|
||||
- name: Run Tests
|
||||
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -9,18 +9,16 @@
|
||||
"name": "Backend - debug",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "uvicorn",
|
||||
"env": {
|
||||
"DEBUG": "true"
|
||||
},
|
||||
"args": [
|
||||
// "--app-dir",
|
||||
// "src",
|
||||
"src.main:app",
|
||||
"--reload",
|
||||
],
|
||||
"jinja": true,
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
"cwd": "${workspaceFolder}/backend",
|
||||
"module": "fastapi",
|
||||
"args": [
|
||||
"dev",
|
||||
"src/main.py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Backend - tester",
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"cmake.ignoreCMakeListsMissing": true
|
||||
}
|
@@ -14,5 +14,6 @@ EXPOSE 8000
|
||||
ENV NUM_WORKERS=1
|
||||
ENV OSM_CACHE_DIR=/cache
|
||||
ENV MEMCACHED_HOST_PATH=none
|
||||
ENV LOKI_URL=none
|
||||
|
||||
CMD fastapi run src/main.py --port 8000 --workers $NUM_WORKERS
|
||||
CMD ["fastapi", "run", "src/main.py", "--port", "8000", "--workers", "$NUM_WORKERS"]
|
||||
|
@@ -25,3 +25,4 @@ pymemcache = "*"
|
||||
fastapi-cli = "*"
|
||||
scikit-learn = "*"
|
||||
pyqt6 = "*"
|
||||
loki-logger-handler = "*"
|
||||
|
11
backend/Pipfile.lock
generated
11
backend/Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "bb22b4e28c7aa199c94b688ad93d3ab0ccf1089a172131f4aec03b78e7bd7f1c"
|
||||
"sha256": "6edd6644586e8814a0b4526adb3352dfc17828ca129de7a68c1d5929efe94daa"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -507,6 +507,15 @@
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==1.4.7"
|
||||
},
|
||||
"loki-logger-handler": {
|
||||
"hashes": [
|
||||
"sha256:aa1a9c933282c134a1e4271aba3cbaa2a3660eab6ea415bad7a072444ab98aa8",
|
||||
"sha256:f6114727a9e5e6f3f2058b9b5324d1cab6d1a04e802079f7b57a8aeb7bd0a112"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '2.7'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
"sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e",
|
||||
|
Submodule backend/deployment updated: 718df09e88...904f16bfc0
1094
backend/report.html
1094
backend/report.html
File diff suppressed because one or more lines are too long
@@ -1,6 +1,5 @@
|
||||
"""Module allowing to access the parameters of route generation"""
|
||||
"""Module setting global parameters for the application such as cache, route generation, etc."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
@@ -16,21 +15,6 @@ cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache')
|
||||
OSM_CACHE_DIR = Path(cache_dir_string)
|
||||
|
||||
|
||||
# if we are in a debug session, set verbose and rich logging
|
||||
if os.getenv('DEBUG', "false") == "true":
|
||||
from rich.logging import RichHandler
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[RichHandler()]
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
)
|
||||
|
||||
|
||||
MEMCACHED_HOST_PATH = os.getenv('MEMCACHED_HOST_PATH', None)
|
||||
if MEMCACHED_HOST_PATH == "none":
|
||||
MEMCACHED_HOST_PATH = None
|
||||
|
58
backend/src/logging_config.py
Normal file
58
backend/src/logging_config.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Sets up global logging configuration for the application."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def configure_logging():
|
||||
"""
|
||||
Called at startup of a FastAPI application instance to setup logging. Depending on the environment, it will log to stdout or to Loki.
|
||||
"""
|
||||
|
||||
is_debug = os.getenv('DEBUG', "false") == "true"
|
||||
is_kubernetes = os.getenv('KUBERNETES_SERVICE_HOST') is not None
|
||||
|
||||
|
||||
if is_kubernetes:
|
||||
# in that case we want to log to stdout and also to loki
|
||||
from loki_logger_handler.loki_logger_handler import LokiLoggerHandler
|
||||
loki_url = os.getenv('LOKI_URL')
|
||||
loki_url = "http://localhost:3100/loki/api/v1/push"
|
||||
if loki_url is None:
|
||||
raise ValueError("LOKI_URL environment variable is not set")
|
||||
|
||||
loki_handler = LokiLoggerHandler(
|
||||
url = loki_url,
|
||||
labels = {'app': 'anyway', 'environment': 'staging' if is_debug else 'production'}
|
||||
)
|
||||
|
||||
logger.info(f"Logging to Loki at {loki_url} with {loki_handler.labels} and {is_debug=}")
|
||||
logging_handlers = [loki_handler, logging.StreamHandler()]
|
||||
logging_level = logging.DEBUG if is_debug else logging.INFO
|
||||
# silence the chatty logs loki generates itself
|
||||
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
|
||||
# no need for time since it's added by loki or can be shown in kube logs
|
||||
logging_format = '%(name)s - %(levelname)s - %(message)s'
|
||||
|
||||
else:
|
||||
# if we are in a debug (local) session, set verbose and rich logging
|
||||
from rich.logging import RichHandler
|
||||
logging_handlers = [RichHandler()]
|
||||
logging_level = logging.DEBUG if is_debug else logging.INFO
|
||||
logging_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level = logging_level,
|
||||
format = logging_format,
|
||||
handlers = logging_handlers
|
||||
)
|
||||
|
||||
# also overwrite the uvicorn loggers
|
||||
logging.getLogger('uvicorn').handlers = logging_handlers
|
||||
logging.getLogger('uvicorn.access').handlers = logging_handlers
|
||||
logging.getLogger('uvicorn.error').handlers = logging_handlers
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
import logging
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from .logging_config import configure_logging
|
||||
from .structs.landmark import Landmark, Toilets
|
||||
from .structs.preferences import Preferences
|
||||
from .structs.linked_landmarks import LinkedLandmarks
|
||||
@@ -11,17 +13,28 @@ from .utils.landmarks_manager import LandmarkManager
|
||||
from .utils.toilets_manager import ToiletsManager
|
||||
from .utils.optimizer import Optimizer
|
||||
from .utils.refiner import Refiner
|
||||
from .persistence import client as cache_client
|
||||
|
||||
from .cache import client as cache_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI()
|
||||
manager = LandmarkManager()
|
||||
optimizer = Optimizer()
|
||||
refiner = Refiner(optimizer=optimizer)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Function to run at the start of the app"""
|
||||
logger.info("Setting up logging")
|
||||
configure_logging()
|
||||
yield
|
||||
logger.info("Shutting down logging")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
|
||||
@app.post("/trip/new")
|
||||
def new_trip(preferences: Preferences,
|
||||
start: tuple[float, float],
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""Definition of the Landmark class to handle visitable objects across the world."""
|
||||
|
||||
from typing import Optional, Literal
|
||||
from uuid import uuid4
|
||||
from uuid import uuid4, UUID
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
@@ -29,12 +29,12 @@ class Landmark(BaseModel) :
|
||||
description (Optional[str]): A text description of the landmark.
|
||||
duration (Optional[int]): The estimated time to visit the landmark (in minutes).
|
||||
name_en (Optional[str]): The English name of the landmark.
|
||||
uuid (str): A unique identifier for the landmark, generated by default using uuid4.
|
||||
uuid (UUID): A unique identifier for the landmark, generated by default using uuid4.
|
||||
must_do (Optional[bool]): Whether the landmark is a "must-do" attraction.
|
||||
must_avoid (Optional[bool]): Whether the landmark should be avoided.
|
||||
is_secondary (Optional[bool]): Whether the landmark is secondary or less important.
|
||||
time_to_reach_next (Optional[int]): Estimated time (in minutes) to reach the next landmark.
|
||||
next_uuid (Optional[str]): UUID of the next landmark in sequence (if applicable).
|
||||
next_uuid (Optional[UUID]): UUID of the next landmark in sequence (if applicable).
|
||||
"""
|
||||
|
||||
# Properties of the landmark
|
||||
@@ -52,7 +52,7 @@ class Landmark(BaseModel) :
|
||||
name_en : Optional[str] = None
|
||||
|
||||
# Unique ID of a given landmark
|
||||
uuid: str = Field(default_factory=uuid4)
|
||||
uuid: UUID = Field(default_factory=uuid4)
|
||||
|
||||
# Additional properties depending on specific tour
|
||||
must_do : Optional[bool] = False
|
||||
@@ -60,7 +60,7 @@ class Landmark(BaseModel) :
|
||||
is_secondary : Optional[bool] = False
|
||||
|
||||
time_to_reach_next : Optional[int] = 0
|
||||
next_uuid : Optional[str] = None
|
||||
next_uuid : Optional[UUID] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
@@ -139,4 +139,4 @@ class Toilets(BaseModel) :
|
||||
|
||||
class Config:
|
||||
# This allows us to easily convert the model to and from dictionaries
|
||||
orm_mode = True
|
||||
from_attributes = True
|
@@ -1,6 +1,6 @@
|
||||
"""Definition of the Trip class."""
|
||||
|
||||
import uuid
|
||||
from uuid import uuid4, UUID
|
||||
from pydantic import BaseModel, Field
|
||||
from pymemcache.client.base import Client
|
||||
|
||||
@@ -19,9 +19,9 @@ class Trip(BaseModel):
|
||||
Methods:
|
||||
from_linked_landmarks: create a Trip from LinkedLandmarks object.
|
||||
"""
|
||||
uuid: str = Field(default_factory=uuid.uuid4)
|
||||
uuid: UUID = Field(default_factory=uuid4)
|
||||
total_time: int
|
||||
first_landmark_uuid: str
|
||||
first_landmark_uuid: UUID
|
||||
|
||||
|
||||
@classmethod
|
||||
@@ -31,7 +31,7 @@ class Trip(BaseModel):
|
||||
"""
|
||||
trip = Trip(
|
||||
total_time = landmarks.total_time,
|
||||
first_landmark_uuid = str(landmarks[0].uuid)
|
||||
first_landmark_uuid = landmarks[0].uuid
|
||||
)
|
||||
|
||||
# Store the trip in the cache
|
||||
|
@@ -4,7 +4,7 @@ from fastapi import HTTPException
|
||||
from pydantic import ValidationError
|
||||
|
||||
from ..structs.landmark import Landmark
|
||||
from ..persistence import client as cache_client
|
||||
from ..cache import client as cache_client
|
||||
|
||||
|
||||
def landmarks_to_osmid(landmarks: list[Landmark]) -> list[int] :
|
||||
|
24
frontend/.github/workflows/build_app_ios.yaml
vendored
24
frontend/.github/workflows/build_app_ios.yaml
vendored
@@ -6,14 +6,17 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
# $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
||||
BUNDLE_GEMFILE: ios/Gemfile
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up ruby env
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.2.1
|
||||
bundler-cache: true
|
||||
ruby-version: 3.3
|
||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
@@ -31,16 +34,24 @@ jobs:
|
||||
echo "BUILD_NAME=${REF_NAME//v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup SSH key for match git repo
|
||||
run: echo "$MATCH_REPO_SSH_KEY" | base64 --decode > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
|
||||
# and mark the host as known
|
||||
run: |
|
||||
echo $MATCH_REPO_SSH_KEY | base64 --decode > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -p 2222 git.kluster.moll.re > ~/.ssh/known_hosts
|
||||
env:
|
||||
MATCH_REPO_SSH_KEY: ${{ secrets.IOS_MATCH_REPO_SSH_KEY_BASE64 }}
|
||||
|
||||
- name: Install fastlane
|
||||
run: bundle install
|
||||
- name: Install dependencies and clean up
|
||||
run: |
|
||||
flutter pub get
|
||||
bundle exec pod install
|
||||
flutter clean
|
||||
bundle exec pod cache clean --all
|
||||
working-directory: ios
|
||||
|
||||
- name: Run fastlane lane
|
||||
run: bundle exec fastlane deploy_release
|
||||
run: bundle exec fastlane deploy_release --verbose
|
||||
working-directory: ios
|
||||
env:
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
@@ -50,3 +61,4 @@ jobs:
|
||||
IOS_ASC_ISSUER_ID: ${{ secrets.IOS_ASC_ISSUER_ID }}
|
||||
IOS_ASC_KEY: ${{ secrets.IOS_ASC_KEY }}
|
||||
MATCH_PASSWORD: ${{ secrets.IOS_MATCH_PASSWORD }}
|
||||
IOS_GOOGLE_MAPS_API_KEY: ${{ secrets.IOS_GOOGLE_MAPS_API_KEY }}
|
||||
|
@@ -50,13 +50,12 @@ Secrets used by fastlane are stored on hashicorp vault and are fetched by the CI
|
||||
|
||||
## Secrets
|
||||
These are mostly used by the CI/CD pipeline to deploy the application. The main usage for github actions is documented under [https://github.com/hashicorp/vault-action](https://github.com/hashicorp/vault-action).
|
||||
**Global secrets** are used for both versions of the app (android and ios).
|
||||
- `GOOGLE_MAPS_API_KEY` is used to authenticate with the Google Maps API
|
||||
|
||||
**Platform-specific secrets** are used by the CI/CD pipeline to deploy to the respective app stores.
|
||||
- `GOOGLE_MAPS_API_KEY` is used to authenticate with the Google Maps API and is scoped to the android platform
|
||||
- `ANDROID_KEYSTORE` is used to sign the android apk
|
||||
- `ANDROID_GOOGLE_KEY` is used to authenticate with the Google Play Store api
|
||||
- `IOS_GOOGLE_...`
|
||||
- `IOS_GOOGLE_MAPS_API_KEY` is used to authenticate with the Google Maps API and is scoped to the ios platform
|
||||
- `IOS_GOOGLE_...`
|
||||
- `IOS_GOOGLE_...`
|
||||
- `IOS_GOOGLE_...`
|
@@ -4,17 +4,15 @@ PODS:
|
||||
- Flutter
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- Google-Maps-iOS-Utils (6.0.0):
|
||||
- Google-Maps-iOS-Utils (6.1.0):
|
||||
- GoogleMaps (~> 9.0)
|
||||
- google_maps_flutter_ios (0.0.1):
|
||||
- Flutter
|
||||
- Google-Maps-iOS-Utils (< 7.0, >= 5.0)
|
||||
- GoogleMaps (< 10.0, >= 8.4)
|
||||
- GoogleMaps (9.1.1):
|
||||
- GoogleMaps/Maps (= 9.1.1)
|
||||
- GoogleMaps/Base (9.1.1)
|
||||
- GoogleMaps/Maps (9.1.1):
|
||||
- GoogleMaps/Base
|
||||
- GoogleMaps (9.2.0):
|
||||
- GoogleMaps/Maps (= 9.2.0)
|
||||
- GoogleMaps/Maps (9.2.0)
|
||||
- map_launcher (0.0.1):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
@@ -74,9 +72,9 @@ SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
geocoding_ios: bcbdaa6bddd7d3129c9bcb8acddc5d8778689768
|
||||
geolocator_apple: d981750b9f47dbdb02427e1476d9a04397beb8d9
|
||||
Google-Maps-iOS-Utils: cfe6a0239c7ca634b7e001ad059a6707143dc8dc
|
||||
Google-Maps-iOS-Utils: 0a484b05ed21d88c9f9ebbacb007956edd508a96
|
||||
google_maps_flutter_ios: 0291eb2aa252298a769b04d075e4a9d747ff7264
|
||||
GoogleMaps: 80ea184ed6bf44139f383a8b0e248ba3ec1cc8c9
|
||||
GoogleMaps: 634ec3ca99698b31ca2253d64f017217d70cfb38
|
||||
map_launcher: fe43bda6720bb73c12fcc1bdd86123ff49a4d4d6
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
@@ -84,6 +82,6 @@ SPEC CHECKSUMS:
|
||||
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
|
||||
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
|
||||
PODFILE CHECKSUM: bd1a78910c05ac1e3a220e80f392c61ab2cc8789
|
||||
|
||||
COCOAPODS: 1.10.2
|
||||
|
@@ -8,9 +8,7 @@ import GoogleMaps
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
// load the key from env
|
||||
let key = ProcessInfo.processInfo.environment["GOOGLE_MAPS_API_KEY"]!
|
||||
GMSServices.provideAPIKey(key)
|
||||
GMSServices.provideAPIKey("IOS_GOOGLE_MAPS_API_KEY")
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
@@ -10,4 +10,4 @@ IOS_ASC_ISSUER_ID="sample"
|
||||
SIGNING_KEY_FILE_PATH="sample"
|
||||
SIGNING_KEY_PASSWORD="sample"
|
||||
|
||||
GOOGLE_MAPS_API_KEY="sample"
|
||||
IOS_GOOGLE_MAPS_API_KEY="sample"
|
||||
|
@@ -33,7 +33,7 @@ platform :ios do
|
||||
"flutter",
|
||||
"build",
|
||||
"ipa",
|
||||
"--release",
|
||||
"--debug",
|
||||
"--build-name=#{build_name}",
|
||||
"--build-number=#{build_number}",
|
||||
)
|
||||
@@ -44,7 +44,9 @@ platform :ios do
|
||||
archive_path: "../build/ios/archive/Runner.xcarchive"
|
||||
)
|
||||
|
||||
upload_to_testflight
|
||||
upload_to_testflight(
|
||||
skip_waiting_for_build_processing: true,
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -62,6 +64,16 @@ platform :ios do
|
||||
readonly: true,
|
||||
)
|
||||
|
||||
# replace secrets by real values, the stupid way
|
||||
sh(
|
||||
"sed",
|
||||
"-i",
|
||||
"",
|
||||
"s/IOS_GOOGLE_MAPS_API_KEY/#{ENV["IOS_GOOGLE_MAPS_API_KEY"]}/g",
|
||||
"../Runner/AppDelegate.swift"
|
||||
|
||||
)
|
||||
|
||||
sh(
|
||||
"flutter",
|
||||
"build",
|
||||
|
Reference in New Issue
Block a user