Added user system with database features. This is fairly experimental and does not include dockerfile to automatically generate a mongodb. This should be in future commits along with ability to save hosts branching off this database feature.

This commit is contained in:
Karmaa
2025-03-10 23:59:06 -05:00
parent 54f03d73ce
commit 4e277bdd07
13 changed files with 1057 additions and 26 deletions

View File

@@ -1,6 +1,8 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useRef } from "react";
import { NewTerminal } from "./Terminal.jsx";
import { User } from "./User.jsx";
import AddHostModal from "./AddHostModal.jsx";
import LoginUserModal from "./LoginUserModal.jsx";
import { Button } from "@mui/joy";
import { CssVarsProvider } from "@mui/joy";
import theme from "./theme";
@@ -9,13 +11,23 @@ import Launchpad from "./Launchpad.jsx";
import { Debounce } from './Utils';
import TermixIcon from "./images/termix_icon.png";
import RocketIcon from './images/launchpad_rocket.png';
import ProfileIcon from './images/profile_icon.png';
import CreateUserModal from "./CreateUserModal.jsx";
import ProfileModal from "./ProfileModal.jsx";
import ErrorModal from "./ErrorModal.jsx";
function App() {
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
const [isLoginUserHidden, setIsLoginUserHidden] = useState(true);
const [isCreateUserHidden, setIsCreateUserHidden] = useState(true);
const [isProfileHidden, setIsProfileHidden] = useState(true);
const [isErrorHidden, setIsErrorHidden] = useState(true);
const [errorMessage, setErrorMessage] = useState('');
const [terminals, setTerminals] = useState([]);
const userRef = useRef(null);
const [activeTab, setActiveTab] = useState(null);
const [nextId, setNextId] = useState(1);
const [form, setForm] = useState({
const [addHostForm, setAddHostForm] = useState({
name: "",
ip: "",
user: "",
@@ -23,6 +35,14 @@ function App() {
port: 22,
authMethod: "Select Auth",
});
const [loginUserForm, setLoginUserForm] = useState({
username: "",
password: "",
});
const [createUserForm, setCreateUserForm] = useState({
username: "",
password: "",
});
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
const [splitTabIds, setSplitTabIds] = useState([]);
@@ -81,17 +101,38 @@ function App() {
});
}, [splitTabIds]);
useEffect(() => {
const sessionToken = localStorage.getItem('sessionToken');
if (sessionToken) {
setTimeout(() => {
handleLoginUser({
sessionToken,
onSuccess: () => {
setIsLoginUserHidden(true);
},
onFailure: (error) => {
setErrorMessage(`Auto-login failed: ${error}`);
setIsErrorHidden(false);
setIsLoginUserHidden(false);
},
});
}, 500);
} else {
setIsLoginUserHidden(false);
}
}, []);
const handleAddHost = () => {
if (form.ip && form.user && ((form.authMethod === 'password' && form.password) || (form.authMethod === 'rsaKey' && form.rsaKey)) && form.port) {
if (addHostForm.ip && addHostForm.user && ((addHostForm.authMethod === 'password' && addHostForm.password) || (addHostForm.authMethod === 'rsaKey' && addHostForm.rsaKey)) && addHostForm.port) {
const newTerminal = {
id: nextId,
title: form.name || form.ip,
title: addHostForm.name || addHostForm.ip,
hostConfig: {
ip: form.ip,
user: form.user,
password: form.authMethod === 'password' ? form.password : undefined,
rsaKey: form.authMethod === 'rsaKey' ? form.rsaKey : undefined,
port: String(form.port),
ip: addHostForm.ip,
user: addHostForm.user,
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
rsaKey: addHostForm.authMethod === 'rsaKey' ? addHostForm.rsaKey : undefined,
port: String(addHostForm.port),
},
terminalRef: null,
};
@@ -99,12 +140,58 @@ function App() {
setActiveTab(nextId);
setNextId(nextId + 1);
setIsAddHostHidden(true);
setForm({ name: "", ip: "", user: "", password: "", rsaKey: "", port: 22, authMethod: "Select Auth" });
setAddHostForm({ name: "", ip: "", user: "", password: "", rsaKey: "", port: 22, authMethod: "Select Auth" });
} else {
alert("Please fill out all fields.");
}
};
const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
if (userRef.current) {
if (sessionToken) {
userRef.current.loginUser({
sessionToken,
onSuccess,
onFailure,
});
} else {
userRef.current.loginUser({
username,
password,
onSuccess,
onFailure,
});
}
}
};
const handleCreateUser = ({ username, password, onSuccess, onFailure }) => {
if (userRef.current) {
userRef.current.createUser({
username,
password,
onSuccess,
onFailure,
});
}
}
const handleDeleteUser = ({ onSuccess, onFailure }) => {
if (userRef.current) {
userRef.current.deleteUser({
onSuccess,
onFailure,
});
}
};
const handleLogoutUser = () => {
if (userRef.current) {
userRef.current.logoutUser();
window.location.reload();
}
};
const closeTab = (id) => {
const newTerminals = terminals.filter((t) => t.id !== id);
setTerminals(newTerminals);
@@ -197,6 +284,28 @@ function App() {
>
+
</Button>
{/* Profile Button */}
<Button
onClick={() => setIsProfileHidden(false)}
sx={{
backgroundColor: theme.palette.general.tertiary,
"&:hover": { backgroundColor: theme.palette.general.secondary },
flexShrink: 0,
height: "52px",
width: "52px",
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: 0,
}}
>
<img
src={ProfileIcon}
alt="Profile"
style={{ width: "70%", height: "70%", objectFit: "contain" }}
/>
</Button>
</div>
{/* Terminal Views */}
@@ -209,10 +318,8 @@ function App() {
} flex-1`}
style={{
order: splitTabIds.includes(terminal.id)
? splitTabIds.indexOf(terminal.id) + 1
: activeTab === terminal.id
? 0
: undefined
? splitTabIds.indexOf(terminal.id)
: 0,
}}
>
<NewTerminal
@@ -220,13 +327,7 @@ function App() {
hostConfig={terminal.hostConfig}
isVisible={activeTab === terminal.id || splitTabIds.includes(terminal.id)}
ref={(ref) => {
if (ref && !terminal.terminalRef) {
setTerminals((prev) =>
prev.map((t) =>
t.id === terminal.id ? { ...t, terminalRef: ref } : t
)
);
}
terminal.terminalRef = ref;
}}
/>
</div>
@@ -237,12 +338,57 @@ function App() {
{/* Modals */}
<AddHostModal
isHidden={isAddHostHidden}
form={form}
setForm={setForm}
form={addHostForm}
setForm={setAddHostForm}
handleAddHost={handleAddHost}
setIsAddHostHidden={setIsAddHostHidden}
/>
<LoginUserModal
isHidden={isLoginUserHidden}
form={loginUserForm}
setForm={setLoginUserForm}
handleLoginUser={handleLoginUser}
setIsLoginUserHidden={setIsLoginUserHidden}
setIsCreateUserHidden={setIsCreateUserHidden}
/>
<CreateUserModal
isHidden={isCreateUserHidden}
form={createUserForm}
setForm={setCreateUserForm}
handleCreateUser={handleCreateUser}
setIsCreateUserHidden={setIsCreateUserHidden}
setIsLoginUserHidden={setIsLoginUserHidden}
/>
<ProfileModal
isHidden={isProfileHidden}
handleDeleteUser={handleDeleteUser}
handleLogoutUser={handleLogoutUser}
setIsProfileHidden={setIsProfileHidden}
/>
<ErrorModal
isHidden={isErrorHidden}
errorMessage={errorMessage}
setIsErrorHidden={setIsErrorHidden}
/>
{isLaunchpadOpen && <Launchpad onClose={() => setIsLaunchpadOpen(false)} />}
{/* User component */}
<User
ref={userRef}
onLoginSuccess={() => setIsLoginUserHidden(true)}
onCreateSuccess={() => {
setIsCreateUserHidden(true);
handleLoginUser({ username: createUserForm.username, password: createUserForm.password })}
}
onDeleteSuccess={() => {
setIsProfileHidden(true);
window.location.reload();
}}
onFailure={(error) => {
setErrorMessage(`Action failed: ${error}`);
setIsErrorHidden(false);
}}
/>
</div>
</CssVarsProvider>
);

122
src/CreateUserModal.jsx Normal file
View File

@@ -0,0 +1,122 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
import theme from './theme';
import { useEffect } from 'react';
const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreateUserHidden, setIsLoginUserHidden }) => {
const isFormValid = () => {
if (!form.username || !form.password) return false;
return true;
};
const handleCreate = () => {
handleCreateUser({
...form
});
};
useEffect(() => {
if (isHidden) {
setForm({ username: '', password: '' });
}
}, [isHidden]);
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => {}}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<DialogTitle>Create</DialogTitle>
<DialogContent>
<form
onSubmit={(event) => {
event.preventDefault();
if (isFormValid()) handleCreate();
}}
>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
<FormControl>
<FormLabel>Username</FormLabel>
<Input
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<FormControl>
<FormLabel>Password</FormLabel>
<Input
type="password"
value={form.password}
onChange={(event) => setForm({ ...form, password: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<Button
type="submit"
disabled={!isFormValid()}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Create
</Button>
<Button
onClick={() => {
setForm({ username: '', password: '' });
setIsCreateUserHidden(true);
setIsLoginUserHidden(false);
}}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Back
</Button>
</Stack>
</form>
</DialogContent>
</ModalDialog>
</Modal>
</CssVarsProvider>
);
};
CreateUserModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
handleCreateUser: PropTypes.func.isRequired,
setIsCreateUserHidden: PropTypes.func.isRequired,
setIsLoginUserHidden: PropTypes.func.isRequired,
};
export default CreateUserModal;

56
src/ErrorModal.jsx Normal file
View File

@@ -0,0 +1,56 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
import theme from './theme';
const ErrorModal = ({ isHidden, errorMessage, setIsErrorHidden }) => {
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => setIsErrorHidden(true)}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 1,
}}
>
<DialogTitle sx={{ marginBottom: 1.5 }}>Error</DialogTitle>
<DialogContent sx={{ color: theme.palette.text.primary }}>
{errorMessage}
</DialogContent>
<Button
onClick={() => setIsErrorHidden(true)}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Close
</Button>
</ModalDialog>
</Modal>
</CssVarsProvider>
);
};
ErrorModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
errorMessage: PropTypes.string.isRequired,
setIsErrorHidden: PropTypes.func.isRequired,
};
export default ErrorModal;

122
src/LoginUserModal.jsx Normal file
View File

@@ -0,0 +1,122 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
import theme from './theme';
import {useEffect} from 'react';
const LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, setIsLoginUserHidden, setIsCreateUserHidden }) => {
const isFormValid = () => {
if (!form.username || !form.password) return false;
return true;
};
const handleLogin = () => {
handleLoginUser({
...form,
});
};
useEffect(() => {
if (isHidden) {
setForm({ username: '', password: '' });
}
}, [isHidden]);
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => {}}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<DialogTitle>Login</DialogTitle>
<DialogContent>
<form
onSubmit={(event) => {
event.preventDefault();
if (isFormValid()) handleLogin();
}}
>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
<FormControl>
<FormLabel>Username</FormLabel>
<Input
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<FormControl>
<FormLabel>Password</FormLabel>
<Input
type="password"
value={form.password}
onChange={(event) => setForm({ ...form, password: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<Button
type="submit"
disabled={!isFormValid()}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Login
</Button>
<Button
onClick={() => {
setForm({ username: '', password: '' });
setIsCreateUserHidden(false);
setIsLoginUserHidden(true);
}}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Create User
</Button>
</Stack>
</form>
</DialogContent>
</ModalDialog>
</Modal>
</CssVarsProvider>
);
};
LoginUserModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
handleLoginUser: PropTypes.func.isRequired,
setIsLoginUserHidden: PropTypes.func.isRequired,
setIsCreateUserHidden: PropTypes.func.isRequired,
};
export default LoginUserModal;

85
src/ProfileModal.jsx Normal file
View File

@@ -0,0 +1,85 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import {Modal, Button, DialogTitle, DialogContent, ModalDialog, Stack } from '@mui/joy';
import theme from './theme';
const ProfileModal = ({ isHidden, handleDeleteUser, handleLogoutUser, setIsProfileHidden }) => {
const handleDelete = () => {
handleDeleteUser({
onSuccess: () => {
window.location.reload();
}
});
};
const handleLogout = () => {
handleLogoutUser({
onSuccess: () => {
window.location.reload();
}
});
}
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => setIsProfileHidden(true)}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 1,
}}
>
<DialogTitle sx={{ marginBottom: 1.5 }}>Profile</DialogTitle>
<DialogContent>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden", mt: 1.5 }}>
<Button
onClick={handleDelete}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Delete User
</Button>
<Button
onClick={handleLogout}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Logout
</Button>
</Stack>
</DialogContent>
</ModalDialog>
</Modal>
</CssVarsProvider>
);
};
ProfileModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
handleDeleteUser: PropTypes.func.isRequired,
handleLogoutUser: PropTypes.func.isRequired,
setIsProfileHidden: PropTypes.func.isRequired,
};
export default ProfileModal;

View File

@@ -91,7 +91,6 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
});
terminalInstance.current.attachCustomKeyEventHandler((event) => {
console.log("Event caled");
if (isPasting) return;
isPasting = true;

129
src/User.jsx Normal file
View File

@@ -0,0 +1,129 @@
import { useRef, forwardRef, useImperativeHandle } from "react";
import io from "socket.io-client";
import PropTypes from "prop-types";
let socket;
if (!socket) {
socket = io(
window.location.hostname === "localhost"
? "http://localhost:8082"
: "/",
{
path: "/socket.io",
transports: ["websocket", "polling"],
}
);
}
export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSuccess, onFailure }, ref) => {
const socketRef = useRef(socket);
const currentUser = useRef(null);
const createUser = (userConfig) => {
if (socketRef.current) {
socketRef.current.emit("createUser", {
username: userConfig.username,
password: userConfig.password,
});
socketRef.current.once("userCreated", (data) => {
console.log("User created", data);
currentUser.current = {
id: data.user._id,
username: data.user.username,
sessionToken: data.user.sessionToken,
};
localStorage.setItem('sessionToken', data.user.sessionToken);
onCreateSuccess(data);
});
socketRef.current.once("error", (error) => {
console.error(error);
const errorMsg = (error && typeof error === 'object' && error !== null)
? error.error || error.message || 'An error occurred'
: String(error);
onFailure(errorMsg);
});
}
};
const loginUser = (userConfig) => {
if (socketRef.current) {
setTimeout(() => {
socketRef.current.emit("loginUser", {
username: userConfig.username,
password: userConfig.password,
sessionToken: userConfig.sessionToken,
});
socketRef.current.once("userFound", (data) => {
console.log("User found", data);
currentUser.current = {
id: data._id,
username: data.username,
sessionToken: data.sessionToken,
};
localStorage.setItem('sessionToken', data.sessionToken);
onLoginSuccess(data);
});
socketRef.current.once("error", (error) => {
console.error(error);
const errorMsg = (error && typeof error === 'object' && error !== null)
? error.error || error.message || 'An error occurred'
: String(error);
onFailure(errorMsg);
});
}, 500);
}
};
const logoutUser = () => {
localStorage.removeItem('sessionToken');
currentUser.current = null;
};
const deleteUser = () => {
if (currentUser.current?.id && socketRef.current) {
socketRef.current.emit("deleteUser", {
userId: currentUser.current.id,
});
socketRef.current.once("userDeleted", (data) => {
console.log("User deleted", data);
onDeleteSuccess(data);
currentUser.current = null;
localStorage.removeItem('sessionToken');
});
socketRef.current.once("error", (error) => {
console.error(error);
const errorMsg = (error && typeof error === 'object' && error !== null)
? error.error || error.message || 'An error occurred'
: String(error);
onFailure(errorMsg);
});
} else {
onFailure("No user is currently logged in.");
}
};
useImperativeHandle(ref, () => ({
createUser,
loginUser,
logoutUser,
deleteUser,
}));
return <div></div>;
});
User.displayName = "User";
User.propTypes = {
onLoginSuccess: PropTypes.func.isRequired,
onCreateSuccess: PropTypes.func.isRequired,
onDeleteSuccess: PropTypes.func.isRequired,
onFailure: PropTypes.func.isRequired,
};

156
src/backend/database.cjs Normal file
View File

@@ -0,0 +1,156 @@
const http = require("http");
const socketIo = require("socket.io");
const mongoose = require("mongoose");
const crypto = require('crypto');
const server = http.createServer();
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
credentials: true
},
allowEIO3: true
});
async function connectToMongoDB() {
try {
const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost:27017/termix';
await mongoose.connect(mongoUrl, {});
console.log('Connected to MongoDB');
const db = mongoose.connection.db;
// Create the 'users' collection if it doesn't exist
const collections = await db.listCollections().toArray();
if (!collections.find(col => col.name === 'users')) {
await db.createCollection('users');
console.log('Successfully created collection: users');
}
} catch (error) {
console.error('Error connecting to MongoDB:', error);
}
}
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
sessionToken: { type: String, required: true },
sshConnections: { type: [Object], default: [] },
});
const User = mongoose.model('User', userSchema);
async function createUser(username, password) {
try {
const userExists = await User.findOne({ username });
if (userExists) {
return { error: "User already exists for username" };
}
const sessionToken = crypto.randomBytes(64).toString('hex');
const newUser = new User({ username, password, sessionToken });
await newUser.save();
return { success: true, user: { _id: newUser._id, username: newUser.username, sessionToken: newUser.sessionToken } };
} catch (err) {
return { error: 'Error creating user: ' + err.message };
}
}
async function loginUser(username, password) {
try {
const user = await User.findOne({ username, password });
if (user) {
if (!user.sessionToken) {
user.sessionToken = crypto.randomBytes(64).toString('hex');
await user.save();
}
return {
_id: user._id,
username: user.username,
sessionToken: user.sessionToken,
};
} else {
return { error: 'User not found or incorrect credentials for username' };
}
} catch (err) {
return { error: 'Error checking user: ' + err.message };
}
}
async function loginWithToken(sessionToken) {
try {
const user = await User.findOne({ sessionToken });
if (user) {
return {
_id: user._id,
username: user.username,
sessionToken: user.sessionToken,
};
} else {
return { error: 'Invalid session token' };
}
} catch (err) {
return { error: 'Error checking session token: ' + err.message };
}
}
async function deleteUser(userId) {
try {
const user = await User.findById(userId);
if (user) {
await User.deleteOne({ _id: userId });
return { success: true };
} else {
return { error: 'User not found'};
}
} catch (err) {
return { error: 'Error removing user: ' + err.message };
}
}
io.on("connection", (socket) => {
console.log("New socket connection established");
socket.on("createUser", async (data) => {
const { username, password } = data;
if (!username || !password) {
socket.emit("error", "Please provide both username and password");
return;
}
const result = await createUser(username, password);
socket.emit(result.error ? "error" : "userCreated", result);
console.log(result.error || `User created`);
});
socket.on("loginUser", async (data) => {
const { username, password, sessionToken } = data;
let result;
if (sessionToken) {
result = await loginWithToken(sessionToken);
} else if (username && password) {
result = await loginUser(username, password);
} else {
socket.emit("error", "Please provide both username and password or a session token");
return;
}
socket.emit(result.error ? "error" : "userFound", result);
console.log(result.error || `User logged in`);
});
socket.on("deleteUser", async (data) => {
const { userId } = data;
if (!userId) {
socket.emit("error", "User ID is required");
return;
}
const result = await deleteUser(userId);
socket.emit(result.error ? "error" : "userDeleted", result);
console.log(result.error || `User deleted`);
});
});
server.listen(8082, '0.0.0.0', async () => {
console.log("Server is running on port 8082");
await connectToMongoDB();
});

BIN
src/images/profile_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB