Commit bc901804 authored by Tharaka it19975696's avatar Tharaka it19975696 🎧

Registration and login functionality inclusion

parent 7bd9d697
......@@ -5,10 +5,10 @@ DB_PASSWORD=1234!@#$Qw
//MySQL
MYSQL_HOST=localhost // Replace with your MySQL server host (e.g., localhost)
MYSQL_USER=tharaka // Replace with your MySQL database username
MYSQL_PASSWORD=1234 // Replace with your MySQL database password
MYSQL_DATABASE=MathsSystem // Replace with your MySQL database name
MYSQL_HOST=localhost
MYSQL_USER=tharaka
MYSQL_PASSWORD=1234
MYSQL_DATABASE=MathsSystem
# This was inserted by `prisma init`:
......@@ -18,4 +18,5 @@ MYSQL_DATABASE=MathsSystem // Replace with your MySQL database name
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="mysql://tharaka:1234@localhost:3306/mathssystem"
\ No newline at end of file
DATABASE_URL="mysql://tharaka:1234@localhost:3306/mathssystem"
JWT_SECRET="1234"
This diff is collapsed.
......@@ -9,9 +9,11 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.8",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.3.1",
"mysql": "^2.18.1",
"react": "^18.2.0",
......@@ -49,5 +51,10 @@
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"prisma": "^5.12.1"
},
"proxy": "http://localhost:5000"
"proxy": "http://localhost:5000",
"description": "Adaptive Learning System for Enhancing Mathematical Aptitude in English Medium O-Level Education",
"main": "tailwind.config.js",
"keywords": [],
"author": "",
"license": "ISC"
}
-- CreateTable
CREATE TABLE `performancereport` (
`reportID` INTEGER NOT NULL AUTO_INCREMENT,
`studentID` INTEGER NOT NULL,
`details` TEXT NOT NULL,
INDEX `studentID`(`studentID`),
PRIMARY KEY (`reportID`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `practiceset` (
`setID` INTEGER NOT NULL AUTO_INCREMENT,
`studentID` INTEGER NOT NULL,
INDEX `studentID`(`studentID`),
PRIMARY KEY (`setID`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `question` (
`questionID` INTEGER NOT NULL AUTO_INCREMENT,
`content` TEXT NOT NULL,
`correctAnswer` VARCHAR(255) NOT NULL,
`difficultyLevel` ENUM('High', 'Medium', 'Low') NOT NULL,
`bloomsLevel` ENUM('remembering', 'understanding', 'applying') NOT NULL,
`learningType` VARCHAR(255) NOT NULL,
`graphicOrAnimation` VARCHAR(255) NULL,
`subtopicID` INTEGER NOT NULL,
INDEX `subtopicID`(`subtopicID`),
PRIMARY KEY (`questionID`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `response` (
`responseID` INTEGER NOT NULL AUTO_INCREMENT,
`questionID` INTEGER NOT NULL,
`studentID` INTEGER NOT NULL,
`answer` VARCHAR(255) NOT NULL,
`timestamp` DATETIME(0) NOT NULL,
INDEX `questionID`(`questionID`),
INDEX `studentID`(`studentID`),
PRIMARY KEY (`responseID`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `student` (
`studentID` INTEGER NOT NULL AUTO_INCREMENT,
`username` VARCHAR(191) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`learningStyle` ENUM('Visual', 'Auditory', 'Kinesthetic', 'Read/Write') NOT NULL,
`mathematicalProficiency` ENUM('High', 'Medium', 'Low') NOT NULL,
UNIQUE INDEX `student_username_key`(`username`),
PRIMARY KEY (`studentID`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `subtopic` (
`subtopicID` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`learningOutcomes` TEXT NOT NULL,
`periods` INTEGER NOT NULL,
PRIMARY KEY (`subtopicID`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `teacher` (
`teacherID` INTEGER NOT NULL AUTO_INCREMENT,
`username` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
PRIMARY KEY (`teacherID`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `performancereport` ADD CONSTRAINT `performancereport_ibfk_1` FOREIGN KEY (`studentID`) REFERENCES `student`(`studentID`) ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE `practiceset` ADD CONSTRAINT `practiceset_ibfk_1` FOREIGN KEY (`studentID`) REFERENCES `student`(`studentID`) ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE `question` ADD CONSTRAINT `question_ibfk_1` FOREIGN KEY (`subtopicID`) REFERENCES `subtopic`(`subtopicID`) ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE `response` ADD CONSTRAINT `response_ibfk_1` FOREIGN KEY (`questionID`) REFERENCES `question`(`questionID`) ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE `response` ADD CONSTRAINT `response_ibfk_2` FOREIGN KEY (`studentID`) REFERENCES `student`(`studentID`) ON DELETE CASCADE ON UPDATE NO ACTION;
-- AlterTable
ALTER TABLE `student` MODIFY `learningStyle` ENUM('Visual', 'Auditory', 'Kinesthetic', 'Read/Write') NULL,
MODIFY `mathematicalProficiency` ENUM('High', 'Medium', 'Low') NULL;
-- RenameIndex
ALTER TABLE `student` RENAME INDEX `student_username_key` TO `Student_username_key`;
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "mysql"
\ No newline at end of file
......@@ -11,7 +11,7 @@ model performancereport {
reportID Int @id @default(autoincrement())
studentID Int
details String @db.Text
student student @relation(fields: [studentID], references: [studentID], onDelete: Cascade, onUpdate: NoAction, map: "performancereport_ibfk_1")
student Student @relation(fields: [studentID], references: [studentID], onDelete: Cascade, onUpdate: NoAction, map: "performancereport_ibfk_1")
@@index([studentID], map: "studentID")
}
......@@ -19,7 +19,7 @@ model performancereport {
model practiceset {
setID Int @id @default(autoincrement())
studentID Int
student student @relation(fields: [studentID], references: [studentID], onDelete: Cascade, onUpdate: NoAction, map: "practiceset_ibfk_1")
student Student @relation(fields: [studentID], references: [studentID], onDelete: Cascade, onUpdate: NoAction, map: "practiceset_ibfk_1")
@@index([studentID], map: "studentID")
}
......@@ -46,23 +46,24 @@ model response {
answer String @db.VarChar(255)
timestamp DateTime @db.DateTime(0)
question question @relation(fields: [questionID], references: [questionID], onDelete: Cascade, onUpdate: NoAction, map: "response_ibfk_1")
student student @relation(fields: [studentID], references: [studentID], onDelete: Cascade, onUpdate: NoAction, map: "response_ibfk_2")
student Student @relation(fields: [studentID], references: [studentID], onDelete: Cascade, onUpdate: NoAction, map: "response_ibfk_2")
@@index([questionID], map: "questionID")
@@index([studentID], map: "studentID")
}
model student {
studentID Int @id @default(autoincrement())
username String @db.VarChar(255)
password String @db.VarChar(255)
learningStyle student_learningStyle
mathematicalProficiency student_mathematicalProficiency
model Student {
studentID Int @id @default(autoincrement())
username String @unique
password String @db.VarChar(255)
learningStyle student_learningStyle? // Optional
mathematicalProficiency student_mathematicalProficiency? // Optional
performancereport performancereport[]
practiceset practiceset[]
response response[]
}
model subtopic {
subtopicID Int @id @default(autoincrement())
name String @db.VarChar(255)
......
......@@ -2,6 +2,10 @@
const express = require('express');
const cors = require('cors');
const { PrismaClient } = require('@prisma/client');
const { hashPassword, verifyPassword } = require('./utils/authHelpers');
const jwt = require('jsonwebtoken');
// Uncomment the line below if you're using the authenticateToken middleware
// const { authenticateToken } = require('./middleware/authenticateToken');
const app = express();
const prisma = new PrismaClient();
......@@ -9,30 +13,105 @@ const prisma = new PrismaClient();
app.use(cors());
app.use(express.json());
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the server API!' });
// Use an environment variable for the JWT_SECRET
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
console.error('JWT_SECRET is not set');
process.exit(1);
}
// Registration endpoint for a student
app.post('/register/student', async (req, res) => {
try {
const { username, password } = req.body;
// Check if username is already taken
const existingStudent = await prisma.student.findUnique({
where: { username: username }
});
if (existingStudent) {
return res.status(409).json({ message: 'Username already exists' });
}
const hashedPassword = await hashPassword(password);
const student = await prisma.student.create({
data: { username, password: hashedPassword }
});
// Exclude password from the response for security reasons
const { password: _, ...studentWithoutPassword } = student;
res.status(201).json(studentWithoutPassword);
} catch (error) {
res.status(500).json({ message: 'Error registering student', error: error.message });
}
});
// Test endpoint to ensure Prisma is connected to the database
app.get('/test-db', async (req, res) => {
// Registration endpoint for a teacher
app.post('/register/teacher', async (req, res) => {
try {
const response = await prisma.$queryRaw`SELECT 1 + 1 AS solution`;
res.status(200).json({ solution: response[0].solution });
} catch (err) {
res.status(500).send(err.message);
const { username, password } = req.body;
// Check if username is already taken
const existingTeacher = await prisma.teacher.findUnique({
where: { username }
});
if (existingTeacher) {
return res.status(409).json({ message: 'Username already exists' });
}
const hashedPassword = await hashPassword(password);
const teacher = await prisma.teacher.create({
data: { username, password: hashedPassword }
});
// Exclude password from the response for security reasons
const { password: _, ...teacherWithoutPassword } = teacher;
res.status(201).json(teacherWithoutPassword);
} catch (error) {
res.status(500).json({ message: 'Error registering teacher', error: error.message });
}
});
// Using Prisma Client to get subtopics data
app.get('/subtopics', async (req, res) => {
// Login endpoint for a student
app.post('/login/student', async (req, res) => {
try {
const subtopics = await prisma.subtopic.findMany();
res.json(subtopics);
} catch (err) {
res.status(500).send(err.message);
const { username, password } = req.body;
const student = await prisma.student.findUnique({
where: { username }
});
if (!student || !(await verifyPassword(password, student.password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ studentId: student.studentID }, 'YOUR_SECRET_KEY', { expiresIn: '1h' });
res.json({ token });
} catch (error) {
res.status(500).json({ message: 'Error logging in', error: error.message });
}
});
// Similar login endpoint for teachers
app.post('/login/teacher', async (req, res) => {
const { username, password } = req.body;
try {
const teacher = await prisma.teacher.findUnique({ where: { username } });
if (!teacher) {
return res.status(401).json({ message: 'Invalid username or password' });
}
const isPasswordValid = await verifyPassword(password, teacher.password);
if (!isPasswordValid) {
return res.status(401).json({ message: 'Invalid username or password' });
}
const token = jwt.sign({ teacherId: teacher.teacherID }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
} catch (err) {
res.status(500).json({ message: 'Something went wrong', error: err.message });
}
});
/* Commented out due to repeated Logic Conflicts
// Example of a protected route
app.get('/protected-route', authenticateToken, (req, res) => {
res.json({ message: 'This is a protected route' });
});
*/
// Start the server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
......
// server/middlewares/authenticateToken.js
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
module.exports = authenticateToken;
// server/utils/authHelpers.js
const bcrypt = require('bcryptjs');
const hashPassword = async (password) => {
return await bcrypt.hash(password, 10);
};
const verifyPassword = async (password, hash) => {
return await bcrypt.compare(password, hash);
};
module.exports = {
hashPassword,
verifyPassword,
};
// api.js
// src/api.js
import axios from 'axios';
const API_BASE_URL = 'http://localhost:5000'; // Use your actual backend URL here
const API_URL = 'http://localhost:5000'; // Adjust this if your server runs on a different port
export const testDbConnection = async () => {
async function makeRequest(url, method, data) {
try {
const response = await axios.get(`${API_BASE_URL}/test-db`);
return response.data;
const response = await axios({ url, method, data });
return response.data;
} catch (error) {
throw error;
if (error.response) {
console.error('Response error:', error.response.data);
throw new Error(error.response.data.message || 'Error with the request.');
} else if (error.request) {
console.error('Request error:', error.request);
throw new Error('No response received.');
} else {
console.error('Setup error:', error.message);
throw new Error(error.message);
}
}
};
}
export const registerStudent = (userData) => makeRequest(`${API_URL}/register/student`, 'post', userData);
export const loginStudent = (credentials) => makeRequest(`${API_URL}/login/student`, 'post', credentials);
export const registerTeacher = (userData) => makeRequest(`${API_URL}/register/teacher`, 'post', userData);
export const loginTeacher = (credentials) => makeRequest(`${API_URL}/login/teacher`, 'post', credentials);
export const testDbConnection = () => makeRequest(`${API_URL}/test-db`, 'get');
// src/App.js
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import Home from './Home';
import About from './About';
import About from './About';
import Insights from './Insights';
import Navbar from './Navbar';
import LoginPage from './LoginPage';
import { testDbConnection } from '../api/api';
import RegisterPage from './RegisterPage';
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [dbTestResult, setDbTestResult] = useState(null);
useEffect(() => {
// Assume token is stored in localStorage after successful login/registration
const token = localStorage.getItem('token');
if (token) {
setIsLoggedIn(true);
}
}, []);
const handleLoginSuccess = () => {
setIsLoggedIn(true);
};
const handleTestDbConnection = async () => {
try {
const response = await testDbConnection();
setDbTestResult(response);
console.log(response);
} catch (error) {
console.error('Database test failed:', error);
setDbTestResult(null);
}
const handleRegistrationSuccess = () => {
setIsLoggedIn(true);
};
// Test the database connection once when the app is loaded
useEffect(() => {
handleTestDbConnection();
}, []);
return (
<Router>
<Navbar />
<Routes>
{isLoggedIn ? (
// Render these routes only if the user is logged in
<>
<Route path="/about" element={<About />} />
<Route path="/insights" element={<Insights />} />
<Route path="/" element={<Home onTestDbConnection={handleTestDbConnection} dbTestResult={dbTestResult} />} />
</>
) : (
// If not logged in, redirect to login page
<>
<Route path="/login" element={<LoginPage onLoginSuccess={handleLoginSuccess} />} />
<Route path="*" element={<Navigate replace to="/login" />} />
</>
)}
{/* Redirect to home if user is already logged in */}
<Route path="/login" element={!isLoggedIn ? <LoginPage onLoginSuccess={handleLoginSuccess} /> : <Navigate replace to="/home" />} />
<Route path="/register" element={!isLoggedIn ? <RegisterPage onRegistrationSuccess={handleRegistrationSuccess} /> : <Navigate replace to="/home" />} />
{/* Protected routes that require user to be logged in */}
<Route path="/about" element={isLoggedIn ? <About /> : <Navigate replace to="/login" />} />
<Route path="/insights" element={isLoggedIn ? <Insights /> : <Navigate replace to="/login" />} />
<Route path="/home" element={isLoggedIn ? <Home /> : <Navigate replace to="/login" />} />
{/* Default route */}
<Route path="/" element={<Navigate replace to={isLoggedIn ? "/home" : "/register"} />} />
<Route path="*" element={<Navigate replace to={isLoggedIn ? "/home" : "/register"} />} />
</Routes>
</Router>
);
......
// LoginPage.js
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, Link } from 'react-router-dom';
import { loginStudent, loginTeacher } from '../api/api'; // Make sure the path is correct
function LoginPage({ onLoginSuccess }) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [credentials, setCredentials] = useState({
username: '',
password: '',
role: 'student', // Default role is set to student
});
const navigate = useNavigate();
const handleLogin = (event) => {
const handleLogin = async (event) => {
event.preventDefault();
// Check for teacher or student login
if ((username === 'teacher' && password === '1234') ||
(username === 'student' && password === '1234')) {
// Call the handler with the username
onLoginSuccess(username);
// Navigate to the homepage or dashboard
navigate('/');
} else {
alert('Invalid credentials!');
try {
const response = credentials.role === 'student'
? await loginStudent(credentials)
: await loginTeacher(credentials);
// Assuming the response includes some user data and a token
if (typeof onLoginSuccess === 'function') {
onLoginSuccess(response.user, response.token);
} else {
console.warn('onLoginSuccess is not provided as a function');
navigate('/home'); // Fallback navigation if onLoginSuccess is not provided
}
} catch (error) {
alert('Login failed: ' + (error.response?.data?.message || error.message));
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setCredentials(prevCredentials => ({ ...prevCredentials, [name]: value }));
};
return (
<div className="flex items-center justify-center h-screen bg-gray-200">
<div className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 flex flex-col">
<h1 className="mb-6 text-2xl font-bold text-center">O Level Maths Tutor</h1>
<form onSubmit={handleLogin} className="flex flex-col">
<label htmlFor="username" className="mb-2 text-gray-700">Username:</label>
<form onSubmit={handleLogin} className="flex flex-col space-y-4">
<label htmlFor="role" className="text-gray-700">I am a:</label>
<select
id="role"
name="role"
value={credentials.role}
onChange={handleChange}
required
className="mb-4 p-2 border border-gray-300 rounded shadow"
>
<option value="student">Student</option>
<option value="teacher">Teacher</option>
</select>
<label htmlFor="username" className="text-gray-700">Username:</label>
<input
id="username"
name="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
value={credentials.username}
onChange={handleChange}
required
className="mb-4 p-2 border border-gray-300 rounded shadow"
className="p-2 border border-gray-300 rounded shadow"
/>
<label htmlFor="password" className="mb-2 text-gray-700">Password:</label>
<label htmlFor="password" className="text-gray-700">Password:</label>
<input
id="password"
name="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
value={credentials.password}
onChange={handleChange}
required
className="mb-4 p-2 border border-gray-300 rounded shadow"
className="p-2 border border-gray-300 rounded shadow"
/>
<button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition duration-200 ease-in-out">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition duration-200 ease-in-out"
>
Login
</button>
<p className="text-center">
Don't have an account yet?{' '}
<Link to="/" className="text-blue-600 hover:text-blue-800 transition duration-200 ease-in-out">
Register here
</Link>
</p>
</form>
</div>
</div>
......
// src/components/RegisterPage.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { registerStudent, registerTeacher } from '../api/api'; // Adjusted import path
const RegisterPage = () => {
const [userData, setUserData] = useState({
username: '',
password: '',
role: 'student', // default role
});
const handleChange = (e) => {
setUserData({ ...userData, [e.target.name]: e.target.value });
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
if (userData.role === 'student') {
await registerStudent({ username: userData.username, password: userData.password });
} else {
await registerTeacher({ username: userData.username, password: userData.password });
}
alert('Registration successful');
} catch (error) {
alert(error.message);
}
};
return (
<div className="flex items-center justify-center h-screen bg-gray-200">
<div className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 flex flex-col">
<h1 className="mb-6 text-2xl font-bold text-center">O Level Maths Tutor</h1>
<form onSubmit={handleSubmit} className="flex flex-col">
<label htmlFor="username" className="mb-2 text-gray-700">Username:</label>
<input
id="username"
name="username"
type="text"
value={userData.username}
onChange={handleChange}
placeholder="Username"
required
className="mb-4 p-2 border border-gray-300 rounded shadow"
/>
<label htmlFor="password" className="mb-2 text-gray-700">Password:</label>
<input
id="password"
name="password"
type="password"
value={userData.password}
onChange={handleChange}
placeholder="Password"
required
className="mb-4 p-2 border border-gray-300 rounded shadow"
/>
<label htmlFor="role" className="mb-2 text-gray-700">Role:</label>
<select
id="role"
name="role"
value={userData.role}
onChange={handleChange}
className="mb-6 p-2 border border-gray-300 rounded shadow"
>
<option value="student">Student</option>
<option value="teacher">Teacher</option>
</select>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition duration-200 ease-in-out"
>
Register
</button>
<p className="text-center">
Already have an account?{' '}
<Link to="/login" className="text-blue-600 hover:text-blue-700">
Login here
</Link>
</p>
</form>
</div>
</div>
);
};
export default RegisterPage;
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment