Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion backend/fastAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,9 @@ def get_selected_user():
@app.post("/add_user/{user_name}/{user_password}")
def add_new_user(user_name: str, user_password: str):
"""Adds a new user with the given name and password."""
return users.add_user(user_name, user_password)
return users.add_user(user_name, user_password)

@app.delete("/delete_user/{user_name}/{user_password}")
def delete_user(user_name: str, user_password: str):
"""Deletes a user with the given name and password."""
return users.delete_user(user_name, user_password)
66 changes: 58 additions & 8 deletions backend/users.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import json
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Get current script directory
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
SELECTED_USER_FILE = "selected_user.json"
# print(os.path.abspath(os.path.join(BASE_DIR, "../database/users_db.json")))
USER_DB_FILE = os.path.abspath(os.path.join(BASE_DIR, "../database/users_db.json"))
DEVICE_DB_FILE = os.path.abspath(os.path.join(BASE_DIR, "../backend/devices.json"))
# print(os.path.abspath(os.path.join(BASE_DIR, "../backend/devices.json")))
updates = []

def load_devices():
"""Load smart home devices data from devices.json."""
if os.path.exists(DEVICE_DB_FILE):
with open(DEVICE_DB_FILE, "r") as f:
try:
data = json.load(f)
return [str(device["id"]) for device in data.get("smart_home_devices", [])]
except json.JSONDecodeError:
return []

def load_users():
"""Load user data from users_db.json."""
if os.path.exists(USER_DB_FILE):
Expand All @@ -27,10 +38,11 @@ def save_users(users):
def add_user(user_name: str, user_password: str):
"""Adds a new user with correct role and allocated devices."""
users = load_users()
available_devices = load_devices()

if not users:
user_role = "super_user"
allocated_devices = ["1", "2", "3", "4", "5", "6"]
allocated_devices = available_devices
else:
user_role = "sub_user"
allocated_devices = []
Expand All @@ -45,17 +57,22 @@ def add_user(user_name: str, user_password: str):
"user_role": user_role
}

print(new_user)

users.append(new_user)
save_users(users)

message = f"New user added: {user_name} ({user_role})"
message = f"New user added: {user_name} with role {'Super User' if user_role == 'super_user' else 'Sub User'}"
updates.append(message)

return {"success": message, "user": new_user}

def delete_user(user_name: str, user_password: str):
"""Deletes a user from the system if the given password matches. If deleting the super user, assign the position to the next user"""
"""Deletes a user from the system if the given password matches. If deleting the super user, assign the position to the next user.
Re-indexes user IDs to maintain sequential order.
"""
users = load_users()
available_devices = load_devices()
user_to_delete = next((u for u in users if u["user_name"] == user_name), None)

if not user_to_delete:
Expand All @@ -68,13 +85,16 @@ def delete_user(user_name: str, user_password: str):

users.remove(user_to_delete)
message = f"User {user_name} deleted."

if user_to_delete["user_role"] == "super_user" and users:
next_super_user = min(users, key=lambda u: u["user_id"])
next_super_user["user_role"] = "super_user"
next_super_user["allocated_devices"] = ["1", "2", "3", "4", "5", "6"]
next_super_user["allocated_devices"] = available_devices
message += f" {next_super_user['user_name']} is now the super user."

for index, user in enumerate(users, start=1):
user["user_id"] = index

save_users(users)
updates.append(message)
return {"success": message}
Expand All @@ -98,11 +118,41 @@ def get_selected_user():
selected_user = data.get("selected_user", "")
return {"selected_user": selected_user}

## USERS DEVICE MANAGEMENT ##
def create_selected_user_devices_json():
"""Create a JSON file with the selected users allocated devices"""
selected_user_data = get_selected_user()
selected_user_name = selected_user_data.get("selected_user")

if not selected_user_name:
return {"error": "No user selected."}

users = load_users()
devices = load_devices(full_details=True)

selected_user = next((u for u in users if u["user_name"] == selected_user_name), None)

if not selected_user:
return {"error": f"User {selected_user_name} not found."}

allocated_device_ids = set(selected_user.get("allocated_devices", []))

allocated_devices = [device for device in devices if str(device["id"]) in allocated_device_ids]

if not allocated_devices:
message = f"No devices allocated to {selected_user_name}."
updates.append(message)
return {"error": f"No devices allocated to {selected_user_name}."}

with open(SELECTED_USER_FILE, "w") as f:
json.dump({"user"})

def getUpdates():
global updates
messages = updates[:]
updates.clear()
return messages

# DEBUGGING SHIT DONT MIND
# load_users()
# load_users()
# add_user("Aditya S", "0000")
11 changes: 10 additions & 1 deletion database/users_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"3",
"4",
"5",
"6"
"6",
"7",
"8"
],
"user_role": "super_user"
},
Expand All @@ -20,6 +22,13 @@
"user_password": "1415",
"allocated_devices": [],
"user_role": "sub_user"
},
{
"user_id": 3,
"user_name": "Ann E",
"user_password": "9999",
"allocated_devices": [],
"user_role": "sub_user"
}
]
}
121 changes: 96 additions & 25 deletions frontend/src/app/users/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,60 +112,100 @@ const Users = () => {
const [newUsername, setNewUsername] = useState("");
const [newPassword, setNewPassword] = useState("");
const [error, setError] = useState("");
const [userToDelete, setUserToDelete] = useState(null);
const [deletePassword, setDeletePassword] = useState("");
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deleteError, setDeleteError] = useState("");

const handleDeleteClick = (user) => {
setUserToDelete(user);
setDeletePassword("");
setDeleteError("");
setDeleteDialogOpen(true);
};

const fetchUsers = async () => {
try {
const response = await fetch("http://localhost:8000/user_data");
const data = await response.json();
setUsers(data.users || []);
} catch (error) {
console.error("Error fetching users:", error);
} finally {
setLoading(false);
}
};

useEffect(() => {
fetchUsers();
}, []);

const handleNewUserSubmit = async () => {
if (newUsername.trim() === "" || newPassword.length !== 4 || isNaN(newPassword)) {
setError("Username must not be empty and password must be a 4-digit number.");
return;
}

try {
const response = await fetch(
`http://localhost:8000/add_user/${encodeURIComponent(newUsername)}/${encodeURIComponent(newPassword)}`,
{
method: "POST",
}
);

if (!response.ok) {
const errorMessage = await response.text();
setError(`Failed to add user: ${errorMessage}`);
return;
}

const newUser = await response.json();

setUsers((prevUsers) => [...prevUsers, newUser]);


setOpenNewUserDialog(false);
setNewUsername("");
setNewPassword("");
setError("");

fetchUsers();
} catch (error) {
console.error("Error adding user:", error);
setError("An error occurred while adding the user.");
}
};

const router = useRouter();
const handleConfirmDelete = async () => {
if (!userToDelete || deletePassword.length !== 4 || isNaN(deletePassword)) {
setDeleteError("Please enter a valid 4-digit password.");
return;
}

const theme = getTheme(darkMode ? "dark" : "light");
try {
const response = await fetch(
`http://localhost:8000/delete_user/${encodeURIComponent(userToDelete.user_name)}/${encodeURIComponent(deletePassword)}`,
{
method: "DELETE",
}
);

useEffect(() => {
const fetchUsers = async () => {
try {
const response = await fetch("http://localhost:8000/user_data");
const data = await response.json();
setUsers(data.users || []);
} catch (error) {
console.error("Error fetching users:", error);
} finally {
setLoading(false);
if (!response.ok) {
const errorMessage = await response.text();
setDeleteError(`Failed to delete user: ${errorMessage}`);
return;
}
};

fetchUsers();
}, []);
// Fetch users again to update the UI correctly
await fetchUsers();

setDeleteDialogOpen(false);
setUserToDelete(null);
} catch (error) {
console.error("Error deleting user:", error);
setDeleteError("An error occurred while deleting the user.");
}
};

const router = useRouter();

const theme = getTheme(darkMode ? "dark" : "light");

const handleUserClick = (user) => {
setSelectedUser(user);
Expand Down Expand Up @@ -266,8 +306,8 @@ const Users = () => {
sx={{
bgcolor: "background.paper",
color: "text.primary",
p: 2,
textAlign: "center",
p: 2,
boxShadow: 6,
borderRadius: 3,
cursor: "pointer",
Expand All @@ -293,9 +333,9 @@ const Users = () => {
position: "absolute",
bottom: 8,
right: 8,
color: "text.secondary",
color: "primary.main",
}}
onClick={() => handleDeleteUser(user.user_name)}
onClick={() => handleDeleteClick(user)}
>
<DeleteIcon sx={{ fontSize: 20 }} />
</IconButton>
Expand Down Expand Up @@ -390,6 +430,37 @@ const Users = () => {
</DialogActions>
</Dialog>

<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
<DialogTitle sx={{ fontFamily: "JetBrains Mono", color: "primary.main" }}>
Confirm Delete
</DialogTitle>
<DialogContent>
<Typography variant="body1" sx={{ mb: 2, fontFamily: "JetBrains Mono" }}>
Selected user to delete: <strong>{userToDelete?.user_name}</strong>
</Typography>
<TextField
label="Enter 4-digit Password"
variant="outlined"
fullWidth
required
type="password"
value={deletePassword}
onChange={(e) => setDeletePassword(e.target.value)}
inputProps={{ maxLength: 4, pattern: "[0-9]*", inputMode: "numeric" }}
sx={{ mb: 2 }}
/>
{deleteError && <Typography color="error">{deleteError}</Typography>}
</DialogContent>
<DialogActions>
<Button onClick={() => setDeleteDialogOpen(false)} color="primary" sx={{ fontFamily: "JetBrains Mono" }}>
Cancel
</Button>
<Button onClick={handleConfirmDelete} color="error" sx={{ fontFamily: "JetBrains Mono" }}>
Delete
</Button>
</DialogActions>
</Dialog>

<Button
variant="contained"
color="primary"
Expand Down