diff --git a/backend/.gitignore b/backend/.gitignore index 52fb102..6583f64 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,10 +1,11 @@ # JSON files devices.json +selected_user.json # Venv files venv/ -Python cache files +# Python cache files __pycache__/ # Secrets diff --git a/backend/fastAPI.py b/backend/fastAPI.py index 77ef1dd..d4357b4 100644 --- a/backend/fastAPI.py +++ b/backend/fastAPI.py @@ -1,4 +1,6 @@ import devices_json as dj +import users + import asyncio import os import json @@ -16,7 +18,7 @@ # Add CORS Middleware app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:3000"], + allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -51,8 +53,13 @@ def change_device_name(id: int, new_name: str): @app.get("/updates") def get_updates(): - """Returns the updates list.""" - return {"updates": dj.getUpdates()} + """Returns combined updates from devices and users.""" + device_updates = dj.getUpdates() + user_updates = users.getUpdates() + + all_updates = device_updates + user_updates + + return {"updates": all_updates} @app.post("/device/{id}/connect") async def change_connection_status(id: int): @@ -75,4 +82,19 @@ def get_user_data(): @app.get("/device_functions") def get_device_functions(): """Returns the list of device functions.""" - return {"functions": dj.deviceFunctions()} \ No newline at end of file + return {"functions": dj.deviceFunctions()} + +@app.post("/select_user/{user}") +def set_selected_user(user: str): + """Sets the selected user""" + return users.select_user(user) + +@app.get("/selected_user") +def get_selected_user(): + """Returns the selected user""" + return users.get_selected_user() + +@app.get("/updates/users") +def get_updates(): + """Returns the updates list.""" + return {"updates": users.getUpdates()} \ No newline at end of file diff --git a/backend/users.py b/backend/users.py index 8f9cac3..c70de95 100644 --- a/backend/users.py +++ b/backend/users.py @@ -1 +1,30 @@ -selected_user = "" \ No newline at end of file +import json +import os + +SELECTED_USER_FILE = "selected_user.json" +updates = [] + +def select_user(user: str): + """Set the selected user and persist it.""" + global selected_user + selected_user = user + with open(SELECTED_USER_FILE, "w") as f: + json.dump({"selected_user": selected_user}, f) + message = f"Logged in as {selected_user}" + updates.append(message) + return {"success": message} + +def get_selected_user(): + """Retrieve the currently selected user.""" + global selected_user + if os.path.exists(SELECTED_USER_FILE): + with open(SELECTED_USER_FILE, "r") as f: + data = json.load(f) + selected_user = data.get("selected_user", "") + return {"selected_user": selected_user} + +def getUpdates(): + global updates + messages = updates[:] + updates.clear() + return messages diff --git a/database/dailyenergy.json b/database/dailyenergy.json new file mode 100644 index 0000000..2b0f0ae --- /dev/null +++ b/database/dailyenergy.json @@ -0,0 +1,7 @@ +{ + "daily energy": [ + { + "device_id": 1, + "date":"24/02/2025", + "energy": + }, \ No newline at end of file diff --git a/database/devices_db.py b/database/devices_db.py index 46f02c0..2f080ed 100644 --- a/database/devices_db.py +++ b/database/devices_db.py @@ -132,4 +132,3 @@ def start_file_watcher(): if __name__ == "__main__": print("Starting file watcher for devices.json...") start_file_watcher() - diff --git a/database/users_db.py b/database/users_db.py index 0b09e05..fc4503f 100644 --- a/database/users_db.py +++ b/database/users_db.py @@ -2,33 +2,127 @@ from firebase_admin import credentials, firestore import json import os -#rom dotenv import load_dotenv +import bcrypt +import time +from dotenv import load_dotenv +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +# Load environment variables +load_dotenv() + +# Read Firebase credentials from .env +firebase_config = { + "type": "service_account", + "project_id": os.getenv("FIREBASE_PROJECT_ID"), + "private_key": (os.getenv("FIREBASE_PRIVATE_KEY") or "").replace("\\n", "\n"), + "client_email": os.getenv("FIREBASE_CLIENT_EMAIL"), + "token_uri": "https://oauth2.googleapis.com/token" +} + +# Initialize Firebase Admin SDK if not already initialized +if not firebase_admin._apps: + cred = credentials.Certificate(firebase_config) + firebase_admin.initialize_app(cred) -cred = credentials.Certificate("/Users/annerinjeri/Downloads/powerhouse-62f4d-firebase-adminsdk-fbsvc-9a2e946c98.json") -firebase_admin.initialize_app(cred) db = firestore.client() -#load_dotenv() -def_set_doc_data(collref,) -# Load JSON file -with open("users.json", "r") as file: - data = json.load(file) - -# Upload users to Firestore with device references -for user in data["users"]: - user_id = user["user_name"].replace(" ", "_").lower() # Unique Firestore ID - user_ref = db.collection("Users").document(user_id) - - # Convert device IDs into Firestore document references - device_refs = [db.collection("Devices").document(device_id) for device_id in user["allocated_devices"]] - - user_data = { - "user_name": user["user_name"], - "user_password": user["user_password"], # Password stored as plain text (⚠ Not recommended for production) - "allocated_devices": device_refs, # Store Firestore document references - "user_role": user["user_role"] +# Hash password function +def hash_password(password): + """Hash password using bcrypt.""" + salt = bcrypt.gensalt() + return bcrypt.hashpw(password.encode(), salt).decode() + +# Get existing users from Firestore +def get_existing_users(coll_ref): + """Fetch existing users from Firestore.""" + return {doc.id: doc.to_dict() for doc in coll_ref.stream()} + +# Function to update Firestore +def set_users_data(coll_ref, file): + """Compare JSON data with Firestore and update only necessary records.""" + rel_path = os.path.join(os.path.dirname(__file__), f"../database/{file}") + + if not os.path.exists(rel_path): + print(f"Error: {file} not found at {rel_path}") + return + + with open(rel_path, "r") as JSONfile: + data = json.load(JSONfile) + + # Convert allocated device IDs to Firestore document references + def get_device_refs(device_ids): + return [db.collection("Devices").document(str(device_id)) for device_id in device_ids] + + filtered_users = { + str(user["user_id"]): { + "user_id": user["user_id"], + "user_name": user["user_name"], + "user_password": hash_password(user["user_password"]), # Hash password + "allocated_devices": get_device_refs(user.get("allocated_devices", [])), + "user_role": user["user_role"] + } + for user in data.get("users", []) } - user_ref.set(user_data) - print(f"User {user['user_name']} added with device references!") + existing_users = get_existing_users(coll_ref) + + added_users, modified_users, deleted_users = [], [], [] + + # Add or update users + for user_id, user_data in filtered_users.items(): + if user_id not in existing_users: + coll_ref.document(user_id).set(user_data) + added_users.append(user_data) + elif existing_users[user_id] != user_data: + coll_ref.document(user_id).set(user_data) + modified_users.append(user_data) + + # Delete outdated users no longer in the JSON + for user_id in existing_users.keys(): + if user_id not in filtered_users: + coll_ref.document(user_id).delete() + deleted_users.append(user_id) + + # Logging changes + for user in added_users: + print(f"Added user: {user}") + for user in modified_users: + print(f"Updated user: {user}") + for user_id in deleted_users: + print(f"Deleted outdated user: {user_id}") + +# Watchdog Event Handler +class UsersFileHandler(FileSystemEventHandler): + def on_modified(self, event): + """Triggered when users_db.json is modified""" + if event.src_path.endswith("users_db.json"): + print("Detected changes in users_db.json, updating Firestore...") + set_users_data(db.collection("Users"), "users_db.json") + +# Watchdog Observer +def watch_users_file(): + """Monitor users_db.json for changes and update Firestore in real-time.""" + users_file_path = os.path.join(os.path.dirname(__file__), "../database") + + event_handler = UsersFileHandler() + observer = Observer() + observer.schedule(event_handler, path=users_file_path, recursive=False) + + observer.start() + print("Watching for changes in users_db.json...") + + try: + while True: + time.sleep(1) # Keep script running + except KeyboardInterrupt: + observer.stop() + + observer.join() + +# Run initial Firestore sync +print("Syncing Firestore with users_db.json on startup...") +set_users_data(db.collection("Users"), "users_db.json") +# Run the watchdog file watcher +watch_users_file() \ No newline at end of file diff --git a/frontend/src/app/ui/dashboard/accountMenu.jsx b/frontend/src/app/ui/dashboard/accountMenu.jsx index 604e3e1..c1c27f1 100644 --- a/frontend/src/app/ui/dashboard/accountMenu.jsx +++ b/frontend/src/app/ui/dashboard/accountMenu.jsx @@ -20,10 +20,14 @@ import { useRouter } from "next/navigation"; export default function AccountMenu() { const router = useRouter(); const [anchorEl, setAnchorEl] = React.useState(null); + const [selectedUser, setSelectedUser] = React.useState("Loading..."); + const open = Boolean(anchorEl); + const handleClick = (event) => { setAnchorEl(event.currentTarget); }; + const handleClose = () => { setAnchorEl(null); }; @@ -37,7 +41,26 @@ export default function AccountMenu() { console.error("Sign out error:", error); } }; - + + // Fetch selected user from the backend + React.useEffect(() => { + const fetchSelectedUser = async () => { + try { + const response = await fetch("http://localhost:8000/selected_user"); + if (!response.ok) { + throw new Error("Failed to fetch user"); + } + const data = await response.json(); + setSelectedUser(data.selected_user || "Unknown User"); + } catch (error) { + console.error("Error fetching user:", error); + setSelectedUser("Error fetching user"); + } + }; + + fetchSelectedUser(); + }, []); + return ( @@ -50,7 +73,7 @@ export default function AccountMenu() { aria-haspopup="true" aria-expanded={open ? "true" : undefined} > - M + {selectedUser.charAt(0)} @@ -99,7 +122,7 @@ export default function AccountMenu() { color: "primary.main", }} > - Profile + {selectedUser} diff --git a/frontend/src/app/users/page.jsx b/frontend/src/app/users/page.jsx index 6156460..e264233 100644 --- a/frontend/src/app/users/page.jsx +++ b/frontend/src/app/users/page.jsx @@ -130,11 +130,32 @@ const Users = () => { setPasswordInput(""); setPasswordValid(null); }; - - const handlePasswordSubmit = () => { - if (passwordInput === selectedUser.user_password) { + + const handlePasswordSubmit = async () => { + if (selectedUser && passwordInput === selectedUser.user_password) { setPasswordValid(true); - router.push("/dashboard"); + + try { + // Now, only set the selected user AFTER successful login + const response = await fetch(`http://localhost:8000/select_user/${selectedUser.user_name}`, { + method: "POST", + }); + + if (!response.ok) { + console.error("Failed to set selected user"); + return; + } + + // Fetch to verify if the user was actually set + const selectedResponse = await fetch("http://localhost:8000/selected_user"); + const selectedData = await selectedResponse.json(); + console.log("Selected user from API:", selectedData); + + // Redirect to dashboard after successful login + router.push("/dashboard"); + } catch (error) { + console.error("Error setting selected user:", error); + } } else { setPasswordValid(false); } @@ -236,7 +257,9 @@ const Users = () => { )} setOpenDialog(false)}> - Enter Password for {selectedUser?.user_name} + + Enter Password for {selectedUser ? selectedUser.user_name : "User"} +