diff --git a/backend/app.py b/backend/app.py index 8d22eb0..02bb6f3 100644 --- a/backend/app.py +++ b/backend/app.py @@ -11,7 +11,7 @@ from routes.transportation import transportation_service from routes.food import food_service from routes.energy import energy_service - +from workers.jobs import start_jobs app = Flask(__name__) app.json = CustomJSONProvider(app) @@ -26,6 +26,9 @@ CORS(app) +start_jobs() + + @app.route("/") def home() -> Response: return jsonify('Carbon Track APP BACKEND API :: If You Can See This Message You Can Reach This API') diff --git a/backend/models/user.py b/backend/models/user.py index e8d6292..04f880e 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -20,6 +20,9 @@ class User(DB_MODEL): province: str household: int fuel_efficiency: float + monthly_emissions: int + yearly_emissions: int + overall_emissions: int def __init__( self, @@ -30,11 +33,14 @@ def __init__( badges: list[str], friends: list[str], monthly_score:int, - yearly_score:int, + yearly_score:int, overall_score:int, province: str, household: int, fuel_efficiency: float, + monthly_emissions: int, + yearly_emissions: int, + overall_emissions: int, ) -> None: super().__init__(oid) self.full_name = str(full_name) @@ -48,6 +54,9 @@ def __init__( self.province = province self.household = household self.fuel_efficiency = fuel_efficiency + self.monthly_emissions = monthly_emissions + self.yearly_emissions = yearly_emissions + self.overall_emissions = overall_emissions def to_json(self) -> json: return { @@ -62,7 +71,10 @@ def to_json(self) -> json: 'overall_score': self.overall_score, 'province': self.province, 'household': self.household, - 'fuel_efficiency': self.fuel_efficiency + 'fuel_efficiency': self.fuel_efficiency, + 'monthly_emissions': self.monthly_emissions, + 'yearly_emissions': self.yearly_emissions, + 'overall_emissions': self.overall_emissions, } @staticmethod @@ -80,6 +92,9 @@ def from_json(doc: json) -> User: province=doc["province"], household=doc["household"], fuel_efficiency=doc["fuel_efficiency"], + monthly_emissions=doc["monthly_emissions"], + yearly_emissions=doc["yearly_emissions"], + overall_emissions=doc["overall_emissions"] ) def __repr__(self) -> str: diff --git a/backend/requirements.txt b/backend/requirements.txt index 384a9c8..c88adcb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,8 +1,9 @@ -firebase_admin==6.2.0 +firebase_admin==6.3.0 Flask==3.0.0 Flask_Cors==4.0.0 Flask_HTTPAuth==4.8.0 -pymongo==4.6.0 +pymongo==4.6.1 ruff==0.1.4 typing~=3.7.4.3 werkzeug~=3.0.1 +APScheduler~=3.10.4 diff --git a/backend/routes/users.py b/backend/routes/users.py index 81e1fdb..dabf0d3 100644 --- a/backend/routes/users.py +++ b/backend/routes/users.py @@ -40,7 +40,7 @@ def get_top_users() -> Response: user = { "rank": rank, "name": obj.full_name, - "footprint": 0, + "footprint": obj.monthly_emissions, "score": obj.overall_score, } rank += 1 @@ -57,7 +57,7 @@ def get_top_users() -> Response: user = { "rank": rank, "name": obj.full_name, - "footprint": 0, + "footprint": obj.yearly_emissions, "score": obj.overall_score, } rank += 1 @@ -74,7 +74,7 @@ def get_top_users() -> Response: user = { "rank": rank, "name": obj.full_name, - "footprint": 0, + "footprint": obj.overall_emissions, "score": obj.overall_score, } rank += 1 @@ -231,4 +231,4 @@ def update_user_name(user_id: str) -> Response: return jsonify({"user": item}), 200 except CarbonTrackError as e: - abort(code=400, description=f"{e}") \ No newline at end of file + abort(code=400, description=f"{e}") diff --git a/backend/utils/update_user_level_and_footprint.py b/backend/utils/update_user_level_and_footprint.py index 449bbd9..ae213f9 100644 --- a/backend/utils/update_user_level_and_footprint.py +++ b/backend/utils/update_user_level_and_footprint.py @@ -1,18 +1,181 @@ """ File Description """ +from datetime import datetime, timedelta + import ct_confiq from bson import ObjectId + +from models.energy import EnergyEntry +from models.food import FoodEntry from models.user import User +from models.transportation import TransportationEntry from mongodb_api.carbon_track_db import CarbonTrackDB +def _is_within_one_month(date_object): + # Get the current date + current_date = datetime.now() + + # Calculate the difference between the current date and the given date + difference = current_date - date_object + + # Check if the difference is less than or equal to 30 days + return difference <= timedelta(days=30) + + +def _is_within_one_year(date_object): + # Get the current date + current_date = datetime.now() + + # Calculate the difference between the current date and the given date + difference = current_date - date_object + + # Check if the difference is less than or equal to 30 days + return difference <= timedelta(days=365) + + def update_user_level_and_footprint(user_id: ObjectId) -> None: """ Docstring """ - user = User.from_json(CarbonTrackDB.users_coll.find_one({'_id': user_id})) - print(user.full_name) + weekly_entries_completed: int = 0 + decreased_emissions = 0 + user: User = User.from_json(CarbonTrackDB.users_coll.find_one({'_id': user_id})) + + overall_score = 0 + yearly_score = 0 + monthly_score = 0 + + # Get Filtered and Sorted Transportation Entries + transportation_entries_dicts: list[dict] = list(CarbonTrackDB.transportation_coll.find({'user_id': user.oid})) + transportation_entries: list[TransportationEntry] = [TransportationEntry.from_json(entry) for entry in transportation_entries_dicts] + transportation_entries = sorted(transportation_entries, key=lambda entry: entry.date) + transportation_entries_overall = [entry for entry in transportation_entries + if entry.calculate_carbon_emissions() != 0] + transportation_entries_yearly = [entry for entry in transportation_entries + if entry.calculate_carbon_emissions() != 0 and _is_within_one_year(entry.date)] + transportation_entries_monthly = [entry for entry in transportation_entries + if entry.calculate_carbon_emissions() != 0 and _is_within_one_month(entry.date)] + weekly_entries_completed += transportation_entries_overall.__len__() + if transportation_entries_overall.__len__() != 0: + last_entry = transportation_entries_overall[0] + for entry in transportation_entries_overall[1:]: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + decreased_emissions += 1 + overall_score += 300 + overall_score += 100 + if entry in transportation_entries_yearly: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + yearly_score += 300 + yearly_score += 100 + if entry in transportation_entries_monthly: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + monthly_score += 300 + monthly_score += 100 + last_entry = entry + + overall_transportation_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in transportation_entries_overall]) + yearly_transportation_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in transportation_entries_yearly]) + monthly_transportation_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in transportation_entries_monthly]) + + # Get Filtered and Sorted food Entries + food_entries_dicts: list[dict] = list(CarbonTrackDB.food_coll.find({'user_id': user.oid})) + food_entries: list[FoodEntry] = [FoodEntry.from_json(entry) for entry in food_entries_dicts] + food_entries = sorted(food_entries, key=lambda entry: entry.date) + food_entries_overall = [entry for entry in food_entries + if entry.calculate_carbon_emissions() != 0] + food_entries_yearly = [entry for entry in food_entries + if entry.calculate_carbon_emissions() != 0 and _is_within_one_year(entry.date)] + food_entries_monthly = [entry for entry in food_entries + if entry.calculate_carbon_emissions() != 0 and _is_within_one_month(entry.date)] + weekly_entries_completed += food_entries_overall.__len__() + if food_entries_overall.__len__() != 0: + last_entry = food_entries_overall[0] + for entry in food_entries_overall[1:]: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + decreased_emissions += 1 + overall_score += 300 + overall_score += 100 + if entry in food_entries_yearly: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + yearly_score += 300 + yearly_score += 100 + if entry in food_entries_monthly: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + monthly_score += 300 + monthly_score += 100 + last_entry = entry + + overall_food_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in food_entries_overall]) + yearly_food_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in food_entries_yearly]) + monthly_food_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in food_entries_monthly]) + + # Get Filtered and Sorted energy Entries + energy_entries_dicts: list[dict] = list(CarbonTrackDB.energy_coll.find({'user_id': user.oid})) + energy_entries: list[EnergyEntry] = [EnergyEntry.from_json(entry) for entry in energy_entries_dicts] + energy_entries = sorted(energy_entries, key=lambda entry: entry.date) + energy_entries_overall = [entry for entry in energy_entries + if entry.calculate_carbon_emissions() != 0] + energy_entries_yearly = [entry for entry in energy_entries + if entry.calculate_carbon_emissions() != 0 and _is_within_one_year(entry.date)] + energy_entries_monthly = [entry for entry in energy_entries + if entry.calculate_carbon_emissions() != 0 and _is_within_one_month(entry.date)] + weekly_entries_completed += energy_entries_overall.__len__() + if energy_entries_overall.__len__() != 0: + last_entry = energy_entries_overall[0] + for entry in energy_entries_overall[1:]: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + decreased_emissions += 1 + overall_score += 300 + overall_score += 100 + if entry in energy_entries_yearly: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + yearly_score += 300 + yearly_score += 100 + if entry in energy_entries_monthly: + if last_entry.calculate_carbon_emissions() > entry.calculate_carbon_emissions(): + monthly_score += 300 + monthly_score += 100 + last_entry = entry + + overall_energy_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in energy_entries_overall]) + yearly_energy_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in energy_entries_yearly]) + monthly_energy_entries_emissions = sum([entry.calculate_carbon_emissions() for entry in energy_entries_monthly]) + + # Calculate Badges + badges = [] + weekly_entries_completed = weekly_entries_completed // 3 + if weekly_entries_completed >= 1: + badges.append('completed_your_first_weekly_entry!') + if weekly_entries_completed >= 10: + badges.append('completed_10_weekly_entries!') + if weekly_entries_completed >= 50: + badges.append('completed_50_weekly_entries!') + if weekly_entries_completed >= 100: + badges.append('completed_100_weekly_entries!') + if decreased_emissions > 0: + badges.append('decreased_emissions_for_first_time!') + + # Combine Emissions + overall_carbon_emissions = overall_transportation_entries_emissions + overall_food_entries_emissions + overall_energy_entries_emissions + yearly_carbon_emissions = yearly_transportation_entries_emissions + yearly_food_entries_emissions + yearly_energy_entries_emissions + monthly_carbon_emissions = monthly_transportation_entries_emissions + monthly_food_entries_emissions + monthly_energy_entries_emissions + + # Update User + user.overall_emissions = int(overall_carbon_emissions) + user.yearly_emissions = int(yearly_carbon_emissions) + user.monthly_emissions = int(monthly_carbon_emissions) + user.overall_score = overall_score + user.yearly_score = yearly_score + user.monthly_score = monthly_score + user.badges = badges + CarbonTrackDB.users_coll.update_one( + {'_id': user.oid}, + {'$set': user.to_json()} + ) + # user_d = user.to_json() + # [print(f'{key}: {user_d[key]}') for key in user_d.keys()] if __name__ == '__main__': diff --git a/backend/workers/jobs.py b/backend/workers/jobs.py new file mode 100644 index 0000000..21055c9 --- /dev/null +++ b/backend/workers/jobs.py @@ -0,0 +1,33 @@ +""" +All Jobs will be container here +""" +from apscheduler.schedulers.background import BackgroundScheduler +from datetime import datetime +import ct_confiq + +# Jobs +from workers import update_users +ct_confiq.run_carbon_track_configurations() + + +def print_curr_time(): + # Put your function code here + print("Running my job at", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + + +def start_jobs() -> None: + + # Create an instance of the scheduler + scheduler = BackgroundScheduler() + + # Add the job to the scheduler + scheduler.add_job(func=print_curr_time, trigger="interval", seconds=60) + scheduler.add_job(func=update_users.run_job, trigger="interval", seconds=60) + + # Start the scheduler + scheduler.start() + + +if __name__ == '__main__': + + start_jobs() diff --git a/backend/workers/update_users.py b/backend/workers/update_users.py new file mode 100644 index 0000000..379e028 --- /dev/null +++ b/backend/workers/update_users.py @@ -0,0 +1,41 @@ +""" +Validate all Logs Job +""" +from pprint import pprint + +from bson import ObjectId +import ct_confiq +from mongodb_api.carbon_track_db import CarbonTrackDB +from utils.update_user_level_and_footprint import update_user_level_and_footprint +ct_confiq.run_carbon_track_configurations() + + +def _update_users() -> None: + """ + Docstring {"processed": False} + """ + completed: int = 0 + users: list[dict[str, ObjectId]] = list(CarbonTrackDB.users_coll.find({}, {"_id": 1})) + if users.__len__() == 0: + return None + print("===================================================") + print(f"| Running 'update_users'") + print("===================================================") + print(f"| Completed {completed:<8}/{users.__len__():<8}| {100 * completed / users.__len__()}%") + + for user in users: + + update_user_level_and_footprint(user['_id']) + completed += 1 + print(f"| Completed {completed:<8}/{users.__len__():<8}| {100 * completed / users.__len__()}%") + + print("===================================================") + + +def run_job(): + _update_users() + + +if __name__ == '__main__': + _update_users() + diff --git a/frontend/assets/default_avatar.png b/frontend/assets/default_avatar.png new file mode 100644 index 0000000..e397a40 Binary files /dev/null and b/frontend/assets/default_avatar.png differ diff --git a/frontend/assets/toSVG.tsx b/frontend/assets/toSVG.tsx index 36d77e2..dc51ae7 100644 --- a/frontend/assets/toSVG.tsx +++ b/frontend/assets/toSVG.tsx @@ -1,6 +1,6 @@ import React from 'react'; import Svg, { Path } from 'react-native-svg'; -import { GoogleSVGType } from '../src/components/types'; +import { type GoogleSVGType } from '../src/components/types'; diff --git a/frontend/src/APIs/FLASK_API.tsx b/frontend/src/APIs/FLASK_API.tsx index 9694b82..d07d462 100644 --- a/frontend/src/APIs/FLASK_API.tsx +++ b/frontend/src/APIs/FLASK_API.tsx @@ -27,7 +27,6 @@ const instance = axios.create({ instance.interceptors.request.use( async (config) => { const token = await getFirebaseAuthToken(); - console.log(token); if (token != null) { config.headers.Authorization = `Bearer ${token}`; diff --git a/frontend/src/widgets/profileWidget.tsx b/frontend/src/widgets/profileWidget.tsx index 9a87b59..9eb16dc 100644 --- a/frontend/src/widgets/profileWidget.tsx +++ b/frontend/src/widgets/profileWidget.tsx @@ -13,12 +13,14 @@ const ProfileWidgetBox: React.FC = ({ user }) => { Josefin: require('../../assets/fonts/JosefinSansThinRegular.ttf'), }); - const [profilePicture, setProfilePicture] = useState(null); + const [profilePicture, setProfilePicture] = useState('https://cianmaggs.github.io/google-homepage/images/loginbutton.png'); const fetchProfilePicture = useCallback(async () => { try { const picture = await firebaseService.getProfilePicture(); - setProfilePicture(picture); + if (picture != null) { + setProfilePicture(picture); + } } catch (error) { console.error('Error fetching profile picture:', error); } @@ -30,18 +32,16 @@ const ProfileWidgetBox: React.FC = ({ user }) => { }, [fetchProfilePicture]) ); - if (!loaded) { - return <>; - } - - if (!loaded) { return <>; } return ( - + {user.full_name}