2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								""" 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								Event  resource  for  the  OpenEventDatabase .  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								""" 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  json  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  re  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  falcon  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  psycopg2  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  psycopg2 . extras  
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								import  psycopg2 . errors  
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								from  oedb . models . event  import  BaseEvent  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								from  oedb . utils . db  import  db_connect  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								from  oedb . utils . serialization  import  dumps  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								from  oedb . utils . logging  import  logger  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								class  EventResource ( BaseEvent ) :  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    Resource  for  managing  events . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    Handles  the  / event  and  / event / { id }  endpoints . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  maybe_insert_geometry ( self ,  geometry ,  cur ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Insert  a  geometry  into  the  geo  table  if  it  doesn ' t exist. 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            geometry :  The  GeoJSON  geometry . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            cur :  The  database  cursor . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Returns : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            tuple :  The  hash  of  the  geometry . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . debug ( " Inserting geometry if not exists " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # Insert into geo table if not existing 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        cur . execute ( """ INSERT INTO geo 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                            SELECT  geom ,  md5 ( st_astext ( geom ) )  as  hash ,  st_centroid ( geom )  as  geom_center  FROM 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                    ( SELECT  st_setsrid ( st_geomfromgeojson (  % s  ) , 4326 )  as  geom )  as  g 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                WHERE  ST_IsValid ( geom ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        ON  CONFLICT  DO  NOTHING  RETURNING  hash ; """ , 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    ( geometry , ) ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # Get its id (md5 hash) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        h  =  cur . fetchone ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  h  is  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            cur . execute ( """ SELECT md5(st_asewkt(geom)), 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                            ST_IsValid ( geom ) , 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                            ST_IsValidReason ( geom )  from  ( SELECT  st_geomfromgeojson (  % s  )  as  geom )  as  g  ; """ , (geometry,)) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            h  =  cur . fetchone ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        return  h 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  relative_time ( self ,  when ,  cur ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Convert  a  relative  time  string  to  database - friendly  format . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            when :  The  relative  time  string . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            cur :  The  database  cursor . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Returns : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            tuple :  The  start  and  stop  times . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . debug ( f " Converting relative time:  { when } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        when  =  when . upper ( ) . replace ( '   ' ,  ' + ' ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        event_start  =  cur . mogrify ( " %s " ,  ( when , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        event_stop  =  cur . mogrify ( " %s " ,  ( when , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  when  ==  ' NOW ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_start  =  " now() " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_stop  =  " now() " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  when  ==  ' TODAY ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_start  =  " CURRENT_DATE " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_stop  =  " CURRENT_DATE + INTERVAL  ' 1 DAY ' " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  when  ==  ' TOMORROW ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_start  =  " CURRENT_DATE + INTERVAL  ' 1 DAY ' " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_stop  =  " CURRENT_DATE + INTERVAL  ' 2 DAY ' " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  when  ==  ' YESTERDAY ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_start  =  " CURRENT_DATE - INTERVAL  ' 1 DAY ' " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_stop  =  " CURRENT_DATE " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        m  =  re . match ( ' (LAST|NEXT)(YEAR|MONTH|WEEK|DAY|HOUR|MINUTE) ' ,  when ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  m  is  not  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            when  =  m . group ( 1 )  +  ' 1 '  +  m . group ( 2 )  +  ' S ' 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        m  =  re . match ( ' (LAST|NEXT)([0-9]*)(YEAR|MONTH|WEEK|MINUTE|HOUR|DAY)S ' ,  when ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  m  is  not  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            if  m . group ( 1 )  ==  ' LAST ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                event_start  =  " now() - INTERVAL  ' %s   %s ' "  %  m . group ( 2 ,  3 ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                event_stop  =  " now() " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                event_start  =  " now() " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                event_stop  =  " now() + INTERVAL  ' %s   %s ' "  %  m . group ( 2 ,  3 ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        return  event_start ,  event_stop 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  on_get ( self ,  req ,  resp ,  id = None ,  geom = None ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Handle  GET  requests  to  the  / event  and  / event / { id }  endpoints . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            req :  The  request  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp :  The  response  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            id :  The  event  ID  ( optional ) . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            geom :  The  geometry  to  search  with  ( optional ) . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . info ( f " Processing GET request to /event { f ' / { id } '  if  id  else  ' ' } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        db  =  db_connect ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        cur  =  db . cursor ( cursor_factory = psycopg2 . extras . DictCursor ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        try : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            if  id  is  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                event_sort  =  " createdate DESC " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                # Get query search parameters 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  geom  is  not  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Convert our geojson geom to WKT 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    geoj  =  json . dumps ( geom ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Buffer around geom? 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    if  ' buffer '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        buffer  =  float ( req . params [ ' buffer ' ] ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    elif  geom [ ' type ' ]  ==  ' Linestring ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        buffer  =  1000   # 1km buffer by default around Linestrings 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        buffer  =  0 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    if  buffer  ==  0 : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        event_bbox  =  cur . mogrify ( "  AND ST_Intersects(geom, ST_SetSRID(ST_GeomFromGeoJSON( %s ),4326))  " ,  ( geoj , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        event_bbox  =  cur . mogrify ( "  AND ST_Intersects(geom, ST_Buffer(ST_SetSRID(ST_GeomFromGeoJSON( %s ),4326)::geography,  %s )::geometry)  " ,  ( geoj ,  buffer ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_dist  =  cur . mogrify ( " ST_Length(ST_ShortestLine(geom, ST_SetSRID(ST_GeomFromGeoJSON( %s ),4326))::geography)::integer as distance,  " ,  ( geoj , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_sort  =  cur . mogrify ( " ST_Length(ST_ShortestLine(geom, ST_SetSRID(ST_GeomFromGeoJSON( %s ),4326))::geography)::integer,  " ,  ( geoj , ) ) . decode ( " utf-8 " )  +  event_sort 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' bbox '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search with bbox (E,S,W,N) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_bbox  =  cur . mogrify ( "  AND geom && ST_SetSRID(ST_MakeBox2D(ST_Point( %s , %s ),ST_Point( %s , %s )),4326)  " ,  tuple ( req . params [ ' bbox ' ] ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_dist  =  " " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' near '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search with location+distance 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # (long, lat, distance in meters) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    if  len ( req . params [ ' near ' ] )  <  3 : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        dist  =  1 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        dist  =  req . params [ ' near ' ] [ 2 ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_bbox  =  cur . mogrify ( "  AND ST_Intersects(geom, ST_Buffer(st_setsrid(st_makepoint( %s , %s ),4326)::geography, %s )::geometry)  " ,  ( req . params [ ' near ' ] [ 0 ] ,  req . params [ ' near ' ] [ 1 ] ,  dist ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_dist  =  cur . mogrify ( " ST_Length(ST_ShortestLine(geom, st_setsrid(st_makepoint( %s , %s ),4326))::geography)::integer as distance, " ,  ( req . params [ ' near ' ] [ 0 ] ,  req . params [ ' near ' ] [ 1 ] ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_sort  =  cur . mogrify ( " ST_Length(ST_ShortestLine(geom, st_setsrid(st_makepoint( %s , %s ),4326))::geography)::integer,  " ,  ( req . params [ ' near ' ] [ 0 ] ,  req . params [ ' near ' ] [ 1 ] ) ) . decode ( " utf-8 " )  +  event_sort 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' polyline '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Use encoded polyline as search geometry 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    if  ' buffer '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        buffer  =  float ( req . params [ ' buffer ' ] ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        buffer  =  1000 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    if  ' polyline_precision '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        precision  =  int ( req . params [ ' polyline_precision ' ] ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        precision  =  5 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # ST_Scale is a workaround to postgis bug not taking precision into account in ST_LineFromEncodedPolyline 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_bbox  =  cur . mogrify ( "  AND ST_Intersects(geom, ST_Buffer(ST_Scale(ST_LineFromEncodedPolyline( %s ),1/10^( %s -5),1/10^( %s -5))::geography,  %s )::geometry)  " ,  ( req . params [ ' polyline ' ] ,  precision ,  precision ,  buffer ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_dist  =  cur . mogrify ( " ST_Length(ST_ShortestLine(geom, ST_Scale(ST_LineFromEncodedPolyline( %s ),1/10^( %s -5),1/10^( %s -5)))::geography)::integer as distance,  " ,  ( req . params [ ' polyline ' ] ,  precision ,  precision ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' where:osm '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_bbox  =  cur . mogrify ( "  AND events_tags ?  ' where:osm '  AND events_tags->> ' where:osm ' = %s   " ,  ( req . params [ ' where:osm ' ] , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_dist  =  " " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' where:wikidata '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_bbox  =  cur . mogrify ( "  AND events_tags ?  ' where:wikidata '  AND events_tags->> ' where:wikidata ' = %s   " ,  ( req . params [ ' where:wikidata ' ] , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_dist  =  " " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_bbox  =  " " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_dist  =  " " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  ' when '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search with fixed time 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    when  =  req . params [ ' when ' ] . upper ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_when  =  " tstzrange( %s , %s , ' [] ' ) "  %  ( self . relative_time ( when ,  cur ) ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' start '  in  req . params  and  ' stop '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search with fixed time (start to stop) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_start ,  unused  =  self . relative_time ( req . params [ ' start ' ] ,  cur ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    unused ,  event_stop  =  self . relative_time ( req . params [ ' stop ' ] ,  cur ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_when  =  " tstzrange( %s , %s , ' [] ' ) "  %  ( event_start ,  event_stop ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' start '  in  req . params  and  ' stop '  not  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search with fixed time (start to now) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_start ,  unused  =  self . relative_time ( req . params [ ' start ' ] ,  cur ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_when  =  " tstzrange( %s ,now(), ' [] ' ) "  %  event_start 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                elif  ' start '  not  in  req . params  and  ' stop '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search with fixed time (now to stop) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    unused ,  event_stop  =  self . relative_time ( req . params [ ' stop ' ] ,  cur ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_when  =  " tstzrange(now(), %s , ' [] ' ) "  %  event_stop 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-16 00:46:09 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								                    # Return events that are currently active (current time is between start and stop) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_when  =  " now() " 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  ' what '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search based on "what" 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_what  =  cur . mogrify ( "  AND events_what LIKE  %s  AND events_what LIKE  %s   " ,  ( req . params [ ' what ' ] [ : 4 ]  +  " % " ,  req . params [ ' what ' ]  +  " % " ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_what  =  " " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  ' type '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Limit search based on type (scheduled, forecast, unscheduled) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_type  =  cur . mogrify ( "  AND events_type =  %s   " ,  ( req . params [ ' type ' ] , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    event_type  =  " " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  ' limit '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    limit  =  cur . mogrify ( " LIMIT  %s " ,  ( req . params [ ' limit ' ] , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    limit  =  " LIMIT 200 " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                event_geom  =  " geom_center " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                geom_only  =  False 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  ' geom '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    if  req . params [ ' geom ' ]  ==  ' full ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        event_geom  =  " geom " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    elif  req . params [ ' geom ' ]  ==  ' only ' : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        geom_only  =  True 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        event_geom  =  cur . mogrify ( " ST_SnapToGrid(geom, %s ) " ,  ( req . params [ ' geom ' ] , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                # Search recent active events. 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-16 00:46:09 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								                if  event_when  ==  " now() " : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Use @> operator to check if events_when contains current time 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    sql  =  """ SELECT events_id, events_tags, createdate, lastupdate,  {event_dist}  st_asgeojson( {event_geom} ) as geometry, st_x(geom_center) as lon, st_y(geom_center) as lat 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                FROM  events  JOIN  geo  ON  ( hash = events_geo ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                WHERE  events_when  @ >  { event_when }  { event_what }  { event_type }  { event_bbox } 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                ORDER  BY  { event_sort }  { limit } """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    # Use && operator to check if events_when overlaps with event_when 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    sql  =  """ SELECT events_id, events_tags, createdate, lastupdate,  {event_dist}  st_asgeojson( {event_geom} ) as geometry, st_x(geom_center) as lon, st_y(geom_center) as lat 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                FROM  events  JOIN  geo  ON  ( hash = events_geo ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                WHERE  events_when  & &  { event_when }  { event_what }  { event_type }  { event_bbox } 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                ORDER  BY  { event_sort }  { limit } """ 
 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								                # No user generated content here, so format is safe. 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                sql  =  sql . format ( event_dist = event_dist ,  event_geom = event_geom , 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                event_bbox = event_bbox ,  event_what = event_what , 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                event_when = event_when ,  event_type = event_type , 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                                event_sort = event_sort ,  limit = limit ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . debug ( f " Executing SQL:  { sql } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                cur . execute ( sql ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								                resp . text  =  dumps ( self . rows_to_collection ( cur . fetchall ( ) ,  geom_only ) ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								                resp . status  =  falcon . HTTP_200 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . success ( f " Successfully processed GET request to /event " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                # Get single event geojson Feature by id. 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . debug ( f " Fetching event with ID:  { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                cur . execute ( " SELECT events_id, events_tags, createdate, lastupdate, st_asgeojson(geom) as geometry, st_x(geom_center) as lon, st_y(geom_center) as lat FROM events JOIN geo ON (hash=events_geo) WHERE events_id= %s " ,  [ id ] ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                e  =  cur . fetchone ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  e  is  not  None : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								                    resp . text  =  dumps ( self . row_to_feature ( e ) ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								                    resp . status  =  falcon . HTTP_200 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    logger . success ( f " Successfully processed GET request to /event/ { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    resp . status  =  falcon . HTTP_404 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    logger . warning ( f " Event not found:  { id } " ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								        except  psycopg2 . errors . InsufficientPrivilege  as  e : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            logger . error ( f " Permission denied for database table:  { e } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp . status  =  falcon . HTTP_500 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp . text  =  dumps ( { 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                " error " :  " Database permission error " , 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                " message " :  " The server does not have permission to access the required database tables. Please contact the administrator to fix this issue. " , 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                " details " :  str ( e ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            } ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								        except  Exception  as  e : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            logger . error ( f " Error processing GET request:  { e } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp . status  =  falcon . HTTP_500 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  dumps ( { " error " :  str ( e ) } ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								        finally : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            db . close ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  insert_or_update ( self ,  req ,  resp ,  id ,  query ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Insert  or  update  an  event . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            req :  The  request  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp :  The  response  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            id :  The  event  ID  ( optional ) . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            query :  The  SQL  query  to  execute . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . info ( f " Processing  { ' UPDATE '  if  id  else  ' INSERT ' }  request for event { f '   { id } '  if  id  else  ' ' } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # Get request body payload (geojson Feature) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        try : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            body  =  req . stream . read ( ) . decode ( ' utf-8 ' ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            j  =  json . loads ( body ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        except  Exception  as  e : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            logger . error ( f " Invalid JSON or bad encoding:  { e } " ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  ' invalid json or bad encoding ' 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            resp . status  =  falcon . HTTP_400 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            return 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								        resp . text  =  ' ' 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								        if  " properties "  not  in  j : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  resp . text  +  " missing  ' properties '  elements \n " 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            j [ ' properties ' ]  =  dict ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  " geometry "  not  in  j : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  resp . text  +  " missing  ' geometry '  elements \n " 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            j [ ' geometry ' ]  =  None 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  " when "  not  in  j [ ' properties ' ]  and  ( " start "  not  in  j [ ' properties ' ]  or  " stop "  not  in  j [ ' properties ' ] ) : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  resp . text  +  " missing  ' when '  or  ' start/stop '  in properties \n " 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            j [ ' properties ' ] [ ' when ' ]  =  None 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  " type "  not  in  j [ ' properties ' ] : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  resp . text  +  " missing  ' type '  of event in properties \n " 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            j [ ' properties ' ] [ ' type ' ]  =  None 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  " what "  not  in  j [ ' properties ' ] : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  resp . text  +  " missing  ' what '  in properties \n " 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            j [ ' properties ' ] [ ' what ' ]  =  None 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  " type "  in  j  and  j [ ' type ' ]  !=  ' Feature ' : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  resp . text  +  ' geojson must be  " type " : " Feature "  only \n ' 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  id  is  None  and  resp . text  !=  ' ' : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            resp . status  =  falcon . HTTP_400 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp . set_header ( ' Content-type ' ,  ' text/plain ' ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            return 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  ' when '  in  j [ ' properties ' ] : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_when  =  j [ ' properties ' ] [ ' when ' ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  " start "  not  in  j [ ' properties ' ] : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_start  =  j [ ' properties ' ] [ ' when ' ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_start  =  j [ ' properties ' ] [ ' start ' ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  " stop "  not  in  j [ ' properties ' ] : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_stop  =  j [ ' properties ' ] [ ' when ' ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            event_stop  =  j [ ' properties ' ] [ ' stop ' ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  event_start  ==  event_stop : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            bounds  =  ' [] ' 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            bounds  =  ' [) ' 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # Connect to db and insert 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        db  =  db_connect ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        cur  =  db . cursor ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # 'secret' based authentication 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  ' secret '  in  j [ ' properties ' ] : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            secret  =  cur . mogrify ( "  AND (events_tags->> ' secret '  =  %s  OR events_tags->> ' secret '  IS NULL)  " ,  ( j [ ' properties ' ] [ ' secret ' ] , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        elif  ' secret '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            secret  =  cur . mogrify ( "  AND (events_tags->> ' secret '  =  %s  OR events_tags->> ' secret '  IS NULL)  " ,  ( req . params [ ' secret ' ] , ) ) . decode ( " utf-8 " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            secret  =  "  AND events_tags->> ' secret '  IS NULL  " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # Get the geometry part 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  j [ ' geometry ' ]  is  not  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            geometry  =  dumps ( j [ ' geometry ' ] ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            h  =  self . maybe_insert_geometry ( geometry ,  cur ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            if  len ( h )  >  1  and  h [ 1 ]  is  False : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								                resp . text  =  " invalid geometry:  %s \n "  %  h [ 2 ] 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								                resp . status  =  falcon . HTTP_400 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                resp . set_header ( ' Content-type ' ,  ' text/plain ' ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                return 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            h  =  [ None ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        params  =  ( j [ ' properties ' ] [ ' type ' ] ,  j [ ' properties ' ] [ ' what ' ] ,  event_start ,  event_stop ,  bounds ,  dumps ( j [ ' properties ' ] ) ,  h [ 0 ] ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  id : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            params  =  params  +  ( id , ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        e  =  None 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        rows  =  None 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        try : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            sql  =  cur . mogrify ( query ,  params ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            cur . execute ( query . format ( secret = secret ) ,  params ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            rows  =  cur . rowcount 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            # Get newly created event id 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            e  =  cur . fetchone ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            db . commit ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            logger . debug ( f " SQL executed successfully:  { sql } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        except  psycopg2 . Error  as  err : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            logger . error ( f " Database error:  { err } , SQL:  { sql } , Error:  { err . pgerror } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            db . rollback ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            pass 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # Send back to client 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  e  is  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            if  id  is  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                cur . execute ( """ SELECT events_id FROM events WHERE events_what= %s 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        AND  events_when = tstzrange ( % s , % s , % s )  AND  events_geo = % s ; """ , 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        ( j [ ' properties ' ] [ ' what ' ] ,  event_start ,  event_stop ,  bounds ,  h [ 0 ] ) ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                if  rows  ==  0 : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    if  ' secret '  in  req . params  or  ' secret '  in  j [ ' properties ' ] : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        resp . status  =  ' 403 Unauthorized, secret does not match ' 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        resp . status  =  ' 403 Unauthorized, secret required ' 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    return 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                    cur . execute ( """ END; WITH s AS (SELECT * FROM events WHERE events_id =  %s ) SELECT e.events_id FROM events e, s WHERE e.events_what=coalesce( %s , s.events_what) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        AND  e . events_when = tstzrange ( coalesce ( % s ,  lower ( s . events_when ) ) , coalesce ( % s , upper ( s . events_when ) ) , % s )  AND  e . events_geo = coalesce ( % s ,  s . events_geo ) ; """ , 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                        ( id ,  j [ ' properties ' ] [ ' what ' ] ,  event_start ,  event_stop ,  bounds ,  h [ 0 ] ) ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            dupe  =  cur . fetchone ( ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  """ { " duplicate " : " %s " } """  %  ( dupe [ 0 ] ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            resp . status  =  ' 409 Conflict with event  %s '  %  dupe [ 0 ] 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            logger . warning ( f " Duplicate event:  { dupe [ 0 ] } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  """ { " id " : " %s " } """  %  ( e [ 0 ] ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            if  id  is  None : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                resp . status  =  falcon . HTTP_201 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . success ( f " Event created with ID:  { e [ 0 ] } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                resp . status  =  falcon . HTTP_200 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . success ( f " Event updated with ID:  { e [ 0 ] } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        cur . close ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        db . close ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  on_post ( self ,  req ,  resp ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Handle  POST  requests  to  the  / event  endpoint . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Creates  a  new  event . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            req :  The  request  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp :  The  response  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . info ( " Processing POST request to /event " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . insert_or_update ( req ,  resp ,  None ,  """ INSERT INTO events ( events_type, events_what, events_when, events_tags, events_geo) VALUES ( %s ,  %s , tstzrange( %s , %s , %s ) ,  %s ,  %s ) ON CONFLICT DO NOTHING RETURNING events_id; """ ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  on_put ( self ,  req ,  resp ,  id ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Handle  PUT  requests  to  the  / event / { id }  endpoint . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Updates  an  existing  event  ( acts  like  PATCH ) . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            req :  The  request  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp :  The  response  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            id :  The  event  ID . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . info ( f " Processing PUT request to /event/ { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . on_patch ( req ,  resp ,  id ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  on_patch ( self ,  req ,  resp ,  id ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Handle  PATCH  requests  to  the  / event / { id }  endpoint . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Updates  an  existing  event . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            req :  The  request  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp :  The  response  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            id :  The  event  ID . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . info ( f " Processing PATCH request to /event/ { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        # Coalesce are used to PATCH the data (new value may be NULL to keep the old one) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . insert_or_update ( req ,  resp ,  id ,  """ UPDATE events SET ( events_type, events_what, events_when, events_tags, events_geo) = (coalesce( %s , events_type), coalesce( %s , events_what), tstzrange(coalesce( %s , lower(events_when)),coalesce( %s , upper(events_when)), %s ) , events_tags::jsonb || ( %s ::jsonb - ' secret ' ) , coalesce( %s , events_geo)) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                              WHERE  events_id  =  % s  { secret }  RETURNING  events_id ; """ ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    def  on_delete ( self ,  req ,  resp ,  id ) : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Handle  DELETE  requests  to  the  / event / { id }  endpoint . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Deletes  an  existing  event . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        Args : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            req :  The  request  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp :  The  response  object . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            id :  The  event  ID . 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        """ 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        logger . info ( f " Processing DELETE request to /event/ { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        db  =  db_connect ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        cur  =  db . cursor ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        try : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            cur . execute ( """ INSERT INTO events_deleted SELECT events_id, createdate, lastupdate, events_type, events_what, events_when, events_geo, events_tags FROM events WHERE events_id =  %s   """ ,  ( id , ) ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            rows_insert  =  cur . rowcount 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            # 'secret' based authentication, must be null or same as during POST 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            if  ' secret '  in  req . params : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                cur . execute ( """ DELETE FROM events WHERE events_id =  %s  AND (events_tags->> ' secret '  =  %s  OR events_tags->> ' secret '  IS NULL) """ ,  ( id ,  req . params [ ' secret ' ] ) ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                cur . execute ( """ DELETE FROM events WHERE events_id =  %s  AND events_tags->> ' secret '  IS NULL; """ ,  ( id , ) ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            if  cur . rowcount  ==  1 : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                resp . status  =  " 204 event deleted " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                db . commit ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . success ( f " Event deleted:  { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            elif  rows_insert  ==  1 :   # INSERT ok but DELETE fails due to missing secret... 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                resp . status  =  " 403 Unauthorized, secret needed to delete this event " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                db . rollback ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . warning ( f " Unauthorized deletion attempt for event:  { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            else : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                resp . status  =  " 404 event not found " 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								                logger . warning ( f " Event not found for deletion:  { id } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        except  Exception  as  e : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            logger . error ( f " Error deleting event  { id } :  { e } " ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            resp . status  =  falcon . HTTP_500 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:54:04 +02:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								            resp . text  =  dumps ( { " error " :  str ( e ) } ) 
							 
						 
					
						
							
								
									
										
										
										
											2025-09-15 23:25:11 +02:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								            db . rollback ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        finally : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            cur . close ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            db . close ( ) 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								# Create a global instance of EventResource  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								event  =  EventResource ( )