diff --git a/backend/app/api/endpoints/aircraft.py b/backend/app/api/endpoints/aircraft.py index b6dba8b..0d702e9 100644 --- a/backend/app/api/endpoints/aircraft.py +++ b/backend/app/api/endpoints/aircraft.py @@ -33,6 +33,30 @@ async def lookup_aircraft_by_registration( return aircraft_list +@router.get("/public/lookup/{registration}", response_model=List[AircraftSchema]) +async def public_lookup_aircraft_by_registration( + registration: str, + db: Session = Depends(get_db) +): + """ + Public lookup aircraft by registration (clean match). + Removes non-alphanumeric characters from input for matching. + No authentication required. + """ + # Clean the input registration (remove non-alphanumeric characters) + clean_input = ''.join(c for c in registration if c.isalnum()).upper() + + if len(clean_input) < 4: + return [] + + # Query aircraft table using clean_reg column + aircraft_list = db.query(Aircraft).filter( + Aircraft.clean_reg.like(f"{clean_input}%") + ).limit(10).all() + + return aircraft_list + + @router.get("/search", response_model=List[AircraftSchema]) async def search_aircraft( q: Optional[str] = Query(None, description="Search query for registration, type, or manufacturer"), diff --git a/backend/app/api/endpoints/airport.py b/backend/app/api/endpoints/airport.py index 0b42211..a5e05d0 100644 --- a/backend/app/api/endpoints/airport.py +++ b/backend/app/api/endpoints/airport.py @@ -68,4 +68,39 @@ async def search_airports( (Airport.city.ilike("%" + search_term + "%")) ).limit(limit).all() - return airports \ No newline at end of file + return airports + + +@router.get("/public/lookup/{code_or_name}", response_model=List[AirportSchema]) +async def public_lookup_airport_by_code_or_name( + code_or_name: str, + db: Session = Depends(get_db) +): + """ + Public lookup airport by ICAO code or name. + If input is 4 characters and all uppercase letters, treat as ICAO code. + Otherwise, search by name. + No authentication required. + """ + clean_input = code_or_name.strip().upper() + + if len(clean_input) < 2: + return [] + + # Check if input looks like an ICAO code (4 letters) + if len(clean_input) == 4 and clean_input.isalpha(): + # Exact ICAO match first + airport = db.query(Airport).filter(Airport.icao == clean_input).first() + if airport: + return [airport] + # Then search ICAO codes that start with input + airports = db.query(Airport).filter( + Airport.icao.like(clean_input + "%") + ).limit(5).all() + return airports + else: + # Search by name (case-insensitive partial match) + airports = db.query(Airport).filter( + Airport.name.ilike("%" + code_or_name + "%") + ).limit(10).all() + return airports \ No newline at end of file diff --git a/backend/app/api/endpoints/pprs.py b/backend/app/api/endpoints/pprs.py index 5aad588..57eed55 100644 --- a/backend/app/api/endpoints/pprs.py +++ b/backend/app/api/endpoints/pprs.py @@ -56,6 +56,31 @@ async def create_ppr( return ppr +@router.post("/public", response_model=PPR) +async def create_public_ppr( + request: Request, + ppr_in: PPRCreate, + db: Session = Depends(get_db) +): + """Create a new PPR record (public endpoint, no authentication required)""" + client_ip = get_client_ip(request) + # For public submissions, use a default created_by or None + ppr = crud_ppr.create(db, obj_in=ppr_in, created_by="public", user_ip=client_ip) + + # Send real-time update via WebSocket + if hasattr(request.app.state, 'connection_manager'): + await request.app.state.connection_manager.broadcast({ + "type": "ppr_created", + "data": { + "id": ppr.id, + "ac_reg": ppr.ac_reg, + "status": ppr.status.value + } + }) + + return ppr + + @router.get("/{ppr_id}", response_model=PPR) async def get_ppr( ppr_id: int, diff --git a/web/ppr.html b/web/ppr.html new file mode 100644 index 0000000..acfd308 --- /dev/null +++ b/web/ppr.html @@ -0,0 +1,587 @@ + + +
+ + +Please fill out the form below to submit a Prior Permission Required (PPR) request for Swansea Airport.
+