codie.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. import geojson
  2. from fastapi import FastAPI, Depends, HTTPException, Request, Form, status, Body
  3. from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
  4. from fastapi.staticfiles import StaticFiles
  5. from fastapi.middleware.cors import CORSMiddleware
  6. from fastapi.templating import Jinja2Templates
  7. from sqlalchemy import create_engine, Column, Integer, String, Date, Identity, Boolean, Text , TIMESTAMP
  8. from sqlalchemy.orm import sessionmaker, declarative_base, Session
  9. from geoalchemy2 import Geometry, Geography
  10. from geoalchemy2.types import WKBElement
  11. from geoalchemy2.shape import to_shape
  12. from starlette.middleware.sessions import SessionMiddleware
  13. import secrets
  14. import uvicorn
  15. import logging
  16. import requests # Import the requests library here
  17. from typing import Annotated, Dict, List #import typing
  18. from pydantic import BaseModel
  19. from datetime import date, datetime
  20. # --- Keycloak Configuration ---
  21. KEYCLOAK_SERVER_URL = "http://165.22.75.145:8080" # Double-check this! Changed to http
  22. KEYCLOAK_REALM = "Generation"
  23. KEYCLOAK_CLIENT_ID = "web-app-pw"
  24. KEYCLOAK_CLIENT_SECRET = "fQGWt8HSPn65cCKTOzE5FigqZhf8QTYW"
  25. KEYCLOAK_SCOPE = "codicefiscale email openid ruolo"
  26. # --- App Initialization ---
  27. app = FastAPI()
  28. # --- Logging Configuration ---
  29. logging.basicConfig(level=logging.INFO)
  30. logger = logging.getLogger(__name__)
  31. # --- CORS ---
  32. origins = [
  33. "http://localhost:3000",
  34. ]
  35. app.add_middleware(
  36. CORSMiddleware,
  37. allow_origins=origins,
  38. allow_credentials=True,
  39. allow_methods=["*"],
  40. allow_headers=["*"],
  41. )
  42. # --- Session ---
  43. app.add_middleware(SessionMiddleware, secret_key=secrets.token_hex(32))
  44. # app.add_middleware(SessionMiddleware, secret_key="your-secret-key")
  45. # Template Jinja2
  46. app.mount("/static", StaticFiles(directory="static"), name="static")
  47. templates = Jinja2Templates(directory="templates")
  48. # Configurazione del database PostgreSQL
  49. #SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@165.22.75.145:15432/GenerationDAITA25"
  50. SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@165.22.75.145:15432/backend"
  51. engine = create_engine(SQLALCHEMY_DATABASE_URL)
  52. SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
  53. # Funzione per ottenere la sessione
  54. def get_db():
  55. db = SessionLocal()
  56. try:
  57. yield db
  58. finally:
  59. db.close()
  60. Base = declarative_base()
  61. # Definizione delle tabelle del database
  62. class TabellaEdifici(Base):
  63. __tablename__ = 'edifici'
  64. id_ = Column(Integer, primary_key=True, name='id_') # Chiave primaria, autoincrement
  65. id_codice_fiscale = Column(String(16), name='id_codice_fiscale')
  66. id_edificio_osm = Column(String(30), nullable=False, name='id_edificio', unique=True)
  67. indirizzo = Column(String(100), nullable=False)
  68. codice_catastale = Column(String(50), nullable=True)
  69. coordinate = Column(Geometry(geometry_type='POINT', srid=4326)) # SRID 4326 è lo standard per latitudine/longitudine
  70. poligono = Column(Geometry(geometry_type='POLYGON', srid=4326))
  71. tipo_edificio = Column(String(100))
  72. stato = Column(Boolean, default=True)
  73. data_installazione = Column(Date, nullable=False)
  74. data_cancellazione = Column(Date, nullable=True)
  75. def to_dict(self):
  76. result ={
  77. 'id_': self.id_,
  78. 'id_codice_fiscale': self.id_codice_fiscale,
  79. 'id_edificio': self.id_edificio_osm,
  80. 'indirizzo': self.indirizzo,
  81. 'codice_catastale': self.codice_catastale,
  82. # 'coordinate': coordinate_json,
  83. # 'poligono': poligono_json,
  84. 'tipo_edificio': self.tipo_edificio,
  85. 'stato': self.stato,
  86. 'data_installazione': self.data_installazione,
  87. 'data_cancellazione': self.data_cancellazione
  88. }
  89. if self.coordinate:
  90. shape = to_shape(self.coordinate)
  91. geojson_str = geojson.dumps(shape)
  92. result['coordinate'] = geojson.loads(geojson_str)
  93. if self.poligono:
  94. shape = to_shape(self.poligono)
  95. geojson_str = geojson.dumps(shape)
  96. result['poligono'] = geojson.loads(geojson_str)
  97. return result
  98. class TabellaTFO(Base):
  99. __tablename__ = 'tfo'
  100. id_ = Column(Integer, primary_key=True, name='id_') # Chiave primaria, autoincrement
  101. id_codice_fiscale = Column(String(16), name='id_codice_fiscale')
  102. id_tfo = Column(String(30), nullable=False, name='id_tfo')
  103. codice_catastale = Column(String(50), nullable=True, name='codice_catastale')
  104. operatore = Column(String(255))
  105. piano = Column(String(50))
  106. scala = Column(String(50))
  107. interno = Column(String(50))
  108. id_edificio = Column(String(30), nullable=False, name='id_edificio')
  109. data_installazione = Column(Date, nullable=False, name='data_installazione')
  110. data_cancellazione = Column(Date, nullable=True, name='data_cancellazione')
  111. class TabellaUtenti(Base):
  112. __tablename__ = 'utenti'
  113. id_ = Column(Integer, primary_key=True, name='id_') # Chiave primaria, autoincrement
  114. codice_fiscale = Column(String(16), name='codice_fiscale')
  115. nome = Column(String(255), nullable=False)
  116. cognome = Column(String(255), nullable=False)
  117. email = Column(String(100), nullable=False)
  118. ruolo = Column(String(255))
  119. stato_utente = Column(Boolean)
  120. class TabellaLogEventi(Base):
  121. __tablename__ = 'log_'
  122. id_ = Column(Integer, primary_key=True, name='id_') # Chiave primaria, autoincrement
  123. id_edificio = Column(Integer, name='id_edificio')
  124. id_tfo = Column(String(30), name='id_tfo')
  125. id_utente = Column(String(16), name='id_utente')
  126. data = Column(TIMESTAMP, default = datetime.now(), name='data')
  127. categoria_modifica = Column(String(255), name='categoria_modifica')
  128. descrizione_evento = Column(String(4000), name='descrizione_evento')
  129. class Assistenza(Base):
  130. __tablename__ = 'assistenza'
  131. id_ticket = Column(Integer, primary_key=True, autoincrement=True)
  132. nome = Column(String(50))
  133. cognome = Column(String(50))
  134. email = Column(String(100), unique=True, nullable=False)
  135. data_richiesta = Column(TIMESTAMP, default=datetime.now())
  136. descrizione_problema = Column(String(4000))
  137. # Base.metadata.drop_all(engine)
  138. # Base.metadata.create_all(bind=engine)
  139. class Credenziali(BaseModel):
  140. username: str
  141. password: str
  142. ####
  143. accesso_modifica = "RSSMRA80A01H501Z"
  144. ####
  145. # funzioni per ottenere i dati dal database
  146. def get_all_buildings(db):
  147. buildings = db.query(TabellaEdifici).all()
  148. return [building.to_dict() for building in buildings]
  149. def get_buildings_by_user(db):
  150. return db.query(TabellaEdifici).filter(TabellaEdifici.id_codice_fiscale == accesso_modifica).all()
  151. def get_specific_building(db, building_id):
  152. return db.query(TabellaEdifici).filter(TabellaEdifici.id_edificio_osm == building_id).first()
  153. ############Funzione per filtro ricerca
  154. def filtro_ricerca_indirizzo(db, filtro):
  155. return db.query(TabellaEdifici).filter(TabellaEdifici.indirizzo.ilike(f"%{filtro}%")).all()
  156. def filtro_ricerca_id_edificio_osm(db, filtro):
  157. return db.query(TabellaEdifici).filter(TabellaEdifici.id_edificio_osm.ilike(f"%{filtro}%")).all()
  158. def filtro_ricerca_data(db, filtro):
  159. return db.query(TabellaEdifici).filter(TabellaEdifici.data_installazione == filtro).all()
  160. def filtro_ricerca_tipo_edificio(db, filtro):
  161. return db.query(TabellaEdifici).filter(TabellaEdifici.tipo_edificio.ilike(f"%{filtro}%")).all()
  162. def filtro_ricerca_id_codice_fiscale(db, filtro):
  163. return db.query(TabellaEdifici).filter(TabellaEdifici.id_codice_fiscale.ilike(f"%{filtro}%")).all()
  164. def filtro_ricerca_totale(db, filtro_indirizzo, filtro_data, filtro_id_edificio_osm, filtro_tipo_edificio, filtro_id_codice_fiscale):
  165. query1 = db.query(TabellaEdifici)
  166. if filtro_indirizzo is not None:
  167. query1 = query1.filter(TabellaEdifici.indirizzo.ilike(f"%{filtro_indirizzo}%"))
  168. if filtro_data is not None:
  169. query1 = query1.filter(TabellaEdifici.data_installazione.ilike(f"%{filtro_data}%"))
  170. if filtro_id_edificio_osm is not None:
  171. query1 = query1.filter(TabellaEdifici.id_edificio_osm.ilike(f"%{filtro_id_edificio_osm}%"))
  172. if filtro_tipo_edificio is not None:
  173. query1 = query1.filter(TabellaEdifici.tipo_edificio.ilike(f"%{filtro_tipo_edificio}%"))
  174. if filtro_id_codice_fiscale is not None:
  175. query1 = query1.filter(TabellaEdifici.id_codice_fiscale.ilike(f"%{filtro_id_codice_fiscale}%"))
  176. return query1.all()
  177. def get_token_from_keycloak(username, password) -> Dict:
  178. """Retrieves access and refresh tokens from Keycloak."""
  179. url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token"
  180. payload = f'grant_type=password&client_id={KEYCLOAK_CLIENT_ID}&scope={KEYCLOAK_SCOPE}&username={username}&password={password}&client_secret={KEYCLOAK_CLIENT_SECRET}'
  181. headers = {
  182. 'Content-Type': 'application/x-www-form-urlencoded'
  183. }
  184. try:
  185. response = requests.post(url, headers=headers, data=payload, timeout=10)
  186. response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
  187. return response.json()
  188. except requests.exceptions.RequestException as e:
  189. logger.error(f"Error getting token from Keycloak: {e}")
  190. raise HTTPException(status_code=500, detail=f"Failed to get token from Keycloak: {e}") from e
  191. def refresh_token_from_keycloak(refresh_token: str) -> Dict:
  192. """Refreshes the access token using the refresh token."""
  193. url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token"
  194. payload = f'grant_type=refresh_token&client_id={KEYCLOAK_CLIENT_ID}&client_secret={KEYCLOAK_CLIENT_SECRET}&refresh_token={refresh_token}'
  195. headers = {
  196. 'Content-Type': 'application/x-www-form-urlencoded'
  197. }
  198. try:
  199. response = requests.post(url, headers=headers, data=payload, timeout=10)
  200. response.raise_for_status()
  201. return response.json()
  202. except requests.exceptions.RequestException as e:
  203. logger.error(f"Error refreshing token: {e}")
  204. raise HTTPException(status_code=500, detail=f"Failed to refresh token: {e}") from e
  205. def introspect_keycloak_token_request(access_token: str) -> Dict:
  206. """Introspects a Keycloak token to verify if it's active."""
  207. url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token/introspect"
  208. payload = f'token={access_token}&client_id={KEYCLOAK_CLIENT_ID}&client_secret={KEYCLOAK_CLIENT_SECRET}'
  209. headers = {
  210. 'Content-Type': 'application/x-www-form-urlencoded'
  211. }
  212. try:
  213. response = requests.post(url, headers=headers, data=payload, verify=False, timeout=10)
  214. response.raise_for_status()
  215. return response.json()
  216. except requests.exceptions.RequestException as e:
  217. logger.error(f"Error introspecting token: {e}")
  218. raise HTTPException(status_code=500, detail=f"Failed to introspect token: {e}") from e
  219. def get_user_info_from_keycloak(access_token: str) -> Dict:
  220. """Gets user information from Keycloak using the access token."""
  221. url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/userinfo"
  222. headers = {
  223. 'Authorization': f'Bearer {access_token}'
  224. }
  225. try:
  226. response = requests.get(url, headers=headers, timeout=10)
  227. response.raise_for_status()
  228. return response.json()
  229. except requests.exceptions.RequestException as e:
  230. logger.error(f"Error getting user info: {e}")
  231. raise HTTPException(status_code=500, detail=f"Failed to get user info: {e}") from e
  232. def logout_keycloak(refresh_token:str):
  233. url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/logout"
  234. payload = f'refresh_token={refresh_token}&client_id={KEYCLOAK_CLIENT_ID}&client_secret={KEYCLOAK_CLIENT_SECRET}'
  235. headers = {
  236. 'Content-Type': 'application/x-www-form-urlencoded'
  237. }
  238. try:
  239. response = requests.request("POST", url, headers=headers, data=payload)
  240. response.raise_for_status()
  241. return response.json()
  242. except requests.exceptions.RequestException as e:
  243. logger.error(f"Error logging out user: {e}")
  244. raise HTTPException(status_code=500, detail=f"Failed to logout user: {e}") from e
  245. @app.get("/access")
  246. async def access(request: Request):
  247. return templates.TemplateResponse("index.html", context={"request": request, "title": "Accesso"})
  248. @app.get("/")
  249. async def home(request: Request):
  250. return print("Ciao")
  251. #return templates.TemplateResponse("index.html", {"request": request})
  252. @app.post("/login")
  253. async def login(credenziali: Credenziali = Body(...)):
  254. """
  255. Logs in a user by retrieving tokens from Keycloak.
  256. Stores the tokens and user info in the session.
  257. """
  258. try:
  259. tokens = get_token_from_keycloak(credenziali.username, credenziali.password)#request.body.json()["username"], request.json()["password"])
  260. #RedirectResponse(url="localhost:8000/callback")
  261. # Save tokens and information to the session
  262. #request.session["access_token"] = tokens["access_token"]
  263. #request.session["refresh_token"] = tokens["refresh_token"]
  264. #print(tokens["access_token"])
  265. # Get user info and save it to the session too
  266. user_info = get_user_info_from_keycloak(tokens["access_token"])
  267. #request.session["user_info"] = user_info
  268. return tokens
  269. except HTTPException as e:
  270. return JSONResponse(content={"detail": e.detail}, status_code=e.status_code)
  271. except Exception as e:
  272. logger.error(f"An unexpected error occurred during login: {e}")
  273. return JSONResponse(content={"detail": "An unexpected error occurred"}, status_code=500)
  274. # @app.post("/login")
  275. # async def login(request: Request, credenziali: Credenziali = Body(...)):
  276. # try:
  277. # tokens = get_token_from_keycloak(credenziali.username, credenziali.password)#request.body.json()["username"], request.json()["password"])
  278. # RedirectResponse(url="localhost:8000/callback")
  279. # # Save tokens and information to the session
  280. # request.session["access_token"] = tokens["access_token"]
  281. # request.session["refresh_token"] = tokens["refresh_token"]
  282. # # Get user info and save it to the session too
  283. # user_info = get_user_info_from_keycloak(tokens["access_token"])
  284. # request.session["user_info"] = user_info
  285. # return RedirectResponse(url="localhost:8000/callback")
  286. # except HTTPException as e:
  287. # return JSONResponse(content={"detail": e.detail}, status_code=e.status_code)
  288. # except Exception as e:
  289. # logger.error(f"An unexpected error occurred during login: {e}")
  290. # return JSONResponse(content={"detail": "An unexpected error occurred"}, status_code=500)
  291. # @app.post("/login")
  292. # async def login(request: Request, code: str = Form(...)):
  293. # token = keycloak_openid.token(grant_type='authorization_code', code=code, redirect_uri=os.getenv('REDIRECT_URI'))
  294. # if not token:
  295. # raise HTTPException(status_code=401, detail="Login fallito")
  296. # return RedirectResponse(url="/inserisci_dati")
  297. # @app.get("/inserisci_dati")
  298. # async def inserisci_dati(request: Request):
  299. # return templates.TemplateResponse("form.html", {"request": request})
  300. # @app.post("/salva_dati")
  301. # async def salva_dati(request: Request,
  302. # id_codice_fiscale: str = Form(...),
  303. # id_edificio_osm: str = Form(...),
  304. # indirizzo: str = Form(...),
  305. # codice_catastale: str = Form(...),
  306. # coordinate: Geometry = Form(...),
  307. # poligono: Geometry = Form(...),
  308. # tipo_edificio: str = Form(...),
  309. # data_installazione: Geometry = Form(...),
  310. # stato: int = Form(...),
  311. # db: Session = Depends(get_db)):
  312. # edificio = TabellaEdifici(id_codice_fiscale=id_codice_fiscale,
  313. # id_edificio_osm=id_edificio_osm,
  314. # indirizzo=indirizzo,
  315. # codice_catastale=codice_catastale,
  316. # coordinate=coordinate,
  317. # poligono=poligono,
  318. # tipo_edificio=tipo_edificio,
  319. # data_installazione=data_installazione,
  320. # stato=stato)
  321. # db.add(edificio)
  322. # db.commit()
  323. # db.refresh(edificio)
  324. # return RedirectResponse(url="/salva_dati", status_code=status.HTTP_303_SEE_OTHER)
  325. @app.get("/visualizza_pratiche")
  326. async def visualizza_pratiche(request: Request, db: Session = Depends(get_db)):
  327. return get_all_buildings(db)
  328. @app.post("/visualizza_pratiche/modifica")
  329. async def modifica(request: Request,
  330. building_id : str = Form(...),
  331. indirizzo: str = Form(...),
  332. codice_catastale: str = Form(...),
  333. tipo_edificio: str = Form(...),
  334. db: Session = Depends(get_db)):
  335. pratica_da_modificare = get_specific_building(db, building_id)
  336. if pratica_da_modificare.indirizzo != indirizzo:
  337. pratica_da_modificare.indirizzo = indirizzo
  338. if pratica_da_modificare.codice_catastale != codice_catastale:
  339. pratica_da_modificare.codice_catastale = codice_catastale
  340. if pratica_da_modificare.tipo_edificio != tipo_edificio:
  341. pratica_da_modificare.tipo_edificio = tipo_edificio
  342. db.commit()
  343. return RedirectResponse(url="/visualizza_pratiche", status_code=status.HTTP_303_SEE_OTHER)
  344. @app.post("/visualizza_pratiche/elimina")
  345. async def elimina(request: Request,
  346. building_id : str = Form(...),
  347. db: Session = Depends(get_db)):
  348. pratica_da_eliminare = get_specific_building(db, building_id)
  349. pratica_da_eliminare.data_cancellazione = date.today()
  350. db.commit()
  351. return RedirectResponse(url="/visualizza_pratiche", status_code=status.HTTP_303_SEE_OTHER)
  352. @app.post("/visualizza_pratiche/ripristina")
  353. async def ripristina(request: Request,
  354. building_id : str = Form(...),
  355. db: Session = Depends(get_db)):
  356. pratica_da_ripristinare = get_specific_building(db, building_id)
  357. pratica_da_ripristinare.data_cancellazione = None
  358. db.commit()
  359. return RedirectResponse(url="/visualizza_pratiche", status_code=status.HTTP_303_SEE_OTHER)
  360. def aggiungi_prova():
  361. with SessionLocal() as db:
  362. edificio = TabellaEdifici(id_codice_fiscale="RSSMRA80A01H501Z",
  363. id_edificio_osm="ED006",
  364. indirizzo="corso italia 1",
  365. codice_catastale="1244",
  366. coordinate="POINT(12.496365 41.902782)",
  367. poligono="POLYGON((12.496365 41.902782, 12.496365 41.902782, 12.496365 41.902782, 12.496365 41.902782, 12.496365 41.902782))",
  368. tipo_edificio="commerciale",
  369. data_installazione=datetime.strptime("12/11/2021", "%d/%m/%Y").date()
  370. )
  371. db.add(edificio)
  372. db.commit()
  373. # aggiungi_prova()
  374. def test_json():
  375. with SessionLocal() as db:
  376. return get_all_buildings(db)
  377. print(test_json())
  378. uvicorn.run(app, host="localhost", port=8000)
  379. # uvicorn main:app --reload