Skip to content

Commit 883d957

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into feature/text-extract
# Conflicts: # app/templates/partials/user_profile/sidebar_left/profile_card/user_details.html
2 parents 1cea209 + 9ace889 commit 883d957

File tree

18 files changed

+648
-4
lines changed

18 files changed

+648
-4
lines changed

app/database/models.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,20 @@ class WikipediaEvents(Base):
442442
date_inserted = Column(DateTime, default=datetime.utcnow)
443443

444444

445+
class CoronaStats(Base):
446+
__tablename__ = "corona_stats"
447+
448+
id = Column(Integer, primary_key=True, index=True)
449+
date_ = Column(DateTime, nullable=False)
450+
date_inserted = Column(DateTime, default=datetime.utcnow)
451+
vaccinated = Column(Integer, nullable=False)
452+
vaccinated_total = Column(Integer, nullable=False)
453+
vaccinated_population_perc = Column(Integer, nullable=False)
454+
vaccinated_second_dose = Column(Integer, nullable=False)
455+
vaccinated_second_dose_total = Column(Integer, nullable=False)
456+
vaccinated_second_dose_perc = Column(Float, nullable=False)
457+
458+
445459
class Quote(Base):
446460
__tablename__ = "quotes"
447461

app/internal/corona_stats.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import json
2+
import random
3+
from datetime import date, datetime
4+
from typing import Any, Dict
5+
6+
import httpx
7+
from fastapi import Depends
8+
from loguru import logger
9+
from sqlalchemy import desc, func
10+
from sqlalchemy.exc import SQLAlchemyError
11+
from sqlalchemy.orm import Session
12+
from sqlalchemy.orm.exc import NoResultFound
13+
14+
from app.database.models import CoronaStats
15+
from app.dependencies import get_db
16+
17+
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
18+
CORONA_API_URL = (
19+
"https://datadashboardapi.health.gov.il/api/queries/vaccinated"
20+
)
21+
USER_AGENT_OPTIONS = [
22+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5)"
23+
" AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15",
24+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0)"
25+
" Gecko/20100101 Firefox/77.0",
26+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) "
27+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
28+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) "
29+
"Gecko/20100101 Firefox/77.0",
30+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
31+
"AppleWebKit/537.36 (KHTML, like Gecko)"
32+
" Chrome/83.0.4103.97 Safari/537.36",
33+
]
34+
35+
36+
def create_stats_object(corona_stats_data: Dict[str, Any]) -> CoronaStats:
37+
"""Dict -> DB Object"""
38+
return CoronaStats(
39+
date_=datetime.strptime(
40+
corona_stats_data.get("Day_Date"),
41+
DATETIME_FORMAT,
42+
),
43+
vaccinated=corona_stats_data.get("vaccinated"),
44+
vaccinated_total=corona_stats_data.get("vaccinated_cum"),
45+
vaccinated_population_perc=corona_stats_data.get(
46+
"vaccinated_population_perc",
47+
),
48+
vaccinated_second_dose=corona_stats_data.get(
49+
"vaccinated_seconde_dose",
50+
),
51+
vaccinated_second_dose_total=corona_stats_data.get(
52+
"vaccinated_seconde_dose_cum",
53+
),
54+
vaccinated_second_dose_perc=corona_stats_data.get(
55+
"vaccinated_seconde_dose_population_perc",
56+
),
57+
)
58+
59+
60+
def serialize_stats(stats_object: CoronaStats) -> Dict[str, Any]:
61+
""" DB Object -> Dict """
62+
return {
63+
"vaccinated_second_dose_perc": (
64+
stats_object.vaccinated_second_dose_perc
65+
),
66+
"vaccinated_second_dose_total": (
67+
stats_object.vaccinated_second_dose_total
68+
),
69+
}
70+
71+
72+
def serialize_dict_stats(stats_dict: Dict[str, Any]) -> Dict[str, Any]:
73+
""" api Dict -> pylender Dict """
74+
return {
75+
"vaccinated_second_dose_perc": (
76+
stats_dict.get("vaccinated_seconde_dose_population_perc")
77+
),
78+
"vaccinated_second_dose_total": (
79+
stats_dict.get("vaccinated_seconde_dose_cum")
80+
),
81+
}
82+
83+
84+
def save_corona_stats(
85+
corona_stats_data: Dict[str, Any],
86+
db: Session = Depends(get_db),
87+
) -> None:
88+
db.add(create_stats_object(corona_stats_data))
89+
db.commit()
90+
91+
92+
def insert_to_db_if_needed(
93+
corona_stats_data: Dict[str, Any],
94+
db: Session = Depends(get_db),
95+
) -> Dict[str, Any]:
96+
""" gets the latest data inserted to gov database """
97+
latest_date = datetime.strptime(
98+
corona_stats_data.get("Day_Date"),
99+
DATETIME_FORMAT,
100+
)
101+
latest_saved = None
102+
try:
103+
latest_saved = (
104+
db.query(CoronaStats).order_by(desc(CoronaStats.date_)).one()
105+
)
106+
except NoResultFound:
107+
# on first system load, the table is empty
108+
save_corona_stats(corona_stats_data, db)
109+
return corona_stats_data
110+
111+
if latest_saved is not None:
112+
# on more recent data arrival, we update the database
113+
if latest_saved.date_ < latest_date:
114+
save_corona_stats(corona_stats_data, db)
115+
return corona_stats_data
116+
else:
117+
return serialize_stats(latest_saved)
118+
119+
120+
async def get_vacinated_data() -> Dict[str, Any]:
121+
async with httpx.AsyncClient() as client:
122+
headers = {"User-Agent": random.choice(USER_AGENT_OPTIONS)}
123+
res = await client.get(CORONA_API_URL, headers=headers)
124+
return json.loads(res.text)[-1]
125+
126+
127+
def get_vacinated_data_from_db(db: Session = Depends(get_db)) -> CoronaStats:
128+
# pulls once a day, it won't be the most updated data
129+
# but we dont want to be blocked for too many requests
130+
return (
131+
db.query(CoronaStats)
132+
.filter(func.date(CoronaStats.date_inserted) == date.today())
133+
.one()
134+
)
135+
136+
137+
async def get_corona_stats(db: Session = Depends(get_db)) -> Dict[str, Any]:
138+
try:
139+
db_data = get_vacinated_data_from_db(db)
140+
corona_stats_data = serialize_stats(db_data)
141+
142+
except NoResultFound:
143+
try:
144+
response_data = await get_vacinated_data()
145+
insert_to_db_if_needed(response_data, db)
146+
corona_stats_data = serialize_dict_stats(response_data)
147+
except json.decoder.JSONDecodeError:
148+
corona_stats_data = {"error": "No data"}
149+
150+
except (SQLAlchemyError, AttributeError) as e:
151+
logger.exception(f"corona stats failed with error: {e}")
152+
corona_stats_data = {"error": "No data"}
153+
return corona_stats_data

app/internal/on_this_day_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ def get_on_this_day_events(db: Session = Depends(get_db)) -> Dict[str, Any]:
4343
except NoResultFound:
4444
data = insert_on_this_day_data(db)
4545
except (SQLAlchemyError, AttributeError) as e:
46-
logger.error(f"on this day failed with error: {e}")
46+
logger.exception(f"on this day failed with error: {e}")
4747
data = {"events": [], "wikipedia": "https://en.wikipedia.org/"}
4848
return data

app/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def create_tables(engine, psql_environment):
7777
profile,
7878
register,
7979
search,
80+
settings,
8081
telegram,
8182
user,
8283
weekview,
@@ -128,6 +129,7 @@ async def swagger_ui_redirect():
128129
register.router,
129130
salary.router,
130131
search.router,
132+
settings.router,
131133
telegram.router,
132134
user.router,
133135
weekview.router,

app/media/arrow-left.png

3.17 KB
Loading

app/routers/profile.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from app import config
1111
from app.database.models import User
1212
from app.dependencies import GOOGLE_ERROR, MEDIA_PATH, get_db, templates
13+
from app.internal.corona_stats import get_corona_stats
1314
from app.internal.import_holidays import (
1415
get_holidays_from_file,
1516
save_holidays_to_db,
@@ -70,6 +71,7 @@ async def profile(
7071
]
7172

7273
on_this_day_data = get_on_this_day_events(session)
74+
corona_stats_data = await get_corona_stats(session)
7375

7476
return templates.TemplateResponse(
7577
"profile.html",
@@ -80,6 +82,7 @@ async def profile(
8082
"signs": signs,
8183
"google_error": GOOGLE_ERROR,
8284
"on_this_day_data": on_this_day_data,
85+
"corona_stats_data": corona_stats_data,
8386
"privacy": PrivacyKinds,
8487
},
8588
)

app/routers/settings.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from fastapi import APIRouter, Request
2+
3+
from app.dependencies import templates
4+
5+
6+
router = APIRouter(
7+
prefix="/settings",
8+
tags=["settings"],
9+
responses={404: {"description": "Not found"}},
10+
)
11+
12+
13+
@router.get("/")
14+
async def settings(request: Request) -> templates.TemplateResponse:
15+
return templates.TemplateResponse(
16+
"settings.html",
17+
{
18+
"request": request,
19+
},
20+
)

app/static/grid_style.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ header {
116116
position: sticky;
117117
top: 0;
118118
display: flex;
119-
grid-flow: row wrap;
120119
margin: 0 var(--space_s);
121120
margin: 0 1rem 0 1rem;
122121
background-color: var(--backgroundcol);

app/static/images/icons/israel.svg

Lines changed: 47 additions & 0 deletions
Loading

app/static/js/settings.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
document.addEventListener('DOMContentLoaded', () => {
2+
const tabBtn = document.getElementsByClassName("tab");
3+
for (let i = 0; i < tabBtn.length; i++) {
4+
const btn = document.getElementById("tab" + i);
5+
btn.addEventListener('click', () => {
6+
tabClick(btn.id, tabBtn);
7+
});
8+
}
9+
});
10+
11+
12+
function tabClick(tab_id, tabBtn) {
13+
let shownTab = document.querySelector(".tab-show");
14+
let selectedTabContent = document.querySelector(`#${tab_id}-content`);
15+
shownTab.classList.remove("tab-show");
16+
shownTab.classList.add("tab-hide");
17+
for (btn of tabBtn) {
18+
btn.children[0].classList.remove("active");
19+
}
20+
document.getElementById(tab_id).classList.add("active");
21+
selectedTabContent.classList.remove("tab-hide");
22+
selectedTabContent.classList.add("tab-show");
23+
}

0 commit comments

Comments
 (0)