Skip to content
Open
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
11 changes: 6 additions & 5 deletions .env
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
DB_HOST=localhost
DB_USER=root
DB_PASS=Alpha
DB_NAME=LearnSync
DB_PORT=3306
PGHOST=localhost
PGUSER=postgres
PGPASSWORD=amna@2327
PGDATABASE=learnsync_database
PGPORT=5432
PORT=3000
JWT_SECRET=7c6278999cfae23d94f6aa7091b654bff3ad9e1a832c3127
JWT_EXPIRES_IN=1d

7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
.env
.env.local
.env.production
.env.development
uploads/
logs/
57 changes: 57 additions & 0 deletions controllers/adminController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// src/controllers/adminController.js
import {
getPendingInstructorsService,
approveInstructorService,
rejectInstructorService
} from "../services/adminService.js";
import { getInstructorDetails } from "../databases/userDatabase.js";

// Existing functions (getPendingInstructors, approveInstructor, rejectInstructor) remain

export async function approveInstructor(req, res) {
try {
const { id } = req.params;
const instructor = await approveInstructorService(id);
return res.json({ message: "Instructor approved", instructor });
} catch (err) {
console.error(err);
return res.status(500).json({ error: err.message });
}
}

export async function rejectInstructor(req, res) {
try {
const { id } = req.params;
const instructor = await rejectInstructorService(id);
return res.json({ message: "Instructor rejected", instructor });
} catch (err) {
console.error(err);
return res.status(500).json({ error: err.message });
}
}


// New: fetch full instructor details including uploaded files
export async function getInstructorFullDetails(req, res) {
try {
const { id } = req.params;
const details = await getInstructorDetails(id);
if (!details) return res.status(404).json({ error: "Instructor not found" });

res.json(details);
} catch (err) {
console.error("Error fetching instructor details:", err);
res.status(500).json({ error: "Failed to fetch instructor details" });
}
}

export async function getPendingInstructors(req, res) {
try {
const instructors = await getPendingInstructorsService();
return res.json(instructors);
} catch (err) {
console.error(err);
return res.status(500).json({ error: err.message });
}
}

10 changes: 6 additions & 4 deletions controllers/authController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {loginService, signupService} from "../services/authService.js";
import { loginService, signupService } from "../services/authService.js";

export async function login(req, res) {
try {
Expand All @@ -14,12 +14,14 @@ export async function login(req, res) {

export async function signup(req, res) {
try {
const { name, email, password } = req.body;
const result = await signupService(name, email, password);
const { name, email, password, role, timeZone } = req.body;

const result = await signupService(name, email, password, role, timeZone);
console.log("noice signup");
return res.status(200).json(result);
} catch (err) {
console.log("error in signup");
return res.status(400).json({ error: err.message });
}
}
}

59 changes: 59 additions & 0 deletions controllers/instructorController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { saveInstructorFiles } from "../services/instructorService.js";
import { getInstructorDetails } from "../databases/userDatabase.js";

export async function getInstructorProfile(req, res) {
try {
const instructorId = req.user.id; // From JWT

const details = await getInstructorDetails(instructorId);

res.status(200).json({
id: instructorId,
status: req.user.status,
certifications: details?.certifications || [],
demo_material: details?.demo_material || [],
subject_tags: details?.subject_tags || [],
education_level_tags: details?.education_level_tags || []
});

} catch (err) {
console.error("Error fetching instructor profile:", err);
res.status(500).json({ error: "Failed to load instructor profile" });
}
}

export async function uploadInstructorFiles(req, res) {
try {
const instructorId = req.user.id;
if (!instructorId) throw new Error("Instructor not found");

// Files
const certificationsFiles = req.files["certifications"] || [];
const demoMaterialFiles = req.files["demo_material"] || [];

const certificationsUrls = certificationsFiles.map(f => `/uploads/${f.filename}`);
const demoMaterialUrls = demoMaterialFiles.map(f => `/uploads/${f.filename}`);

// Extra fields from form
const { subject_tags, education_level_tags } = req.body;

// Convert comma-separated strings to arrays
const subjectsArray = subject_tags ? subject_tags.split(",").map(s => s.trim()) : [];
const educationLevelsArray = education_level_tags ? education_level_tags.split(",").map(s => s.trim()) : [];

// Call service to update DB
await saveInstructorFiles(
instructorId,
certificationsUrls,
demoMaterialUrls,
subjectsArray,
educationLevelsArray
);

res.status(200).json({ message: "Instructor profile updated successfully" });
} catch (err) {
console.error("Error uploading instructor files:", err);
res.status(500).json({ error: "Failed to upload instructor profile" });
}
}

58 changes: 58 additions & 0 deletions controllers/studentController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// controllers/studentController.js
import {
getStudentDetailsService,
createStudentProfileService,
updateStudentProfileService
} from "../services/studentService.js";

/**
* GET /student/details
* Returns { exists: boolean, profile: {...} }
* Pure fetch, does NOT update anything.
*/
export async function getStudentDetailsForDashboard(req, res) {
try {
const details = await getStudentDetailsService(req.user.id);
if (!details) {
return res.json({ exists: false, profile: null });
}
return res.json({ exists: true, profile: details });
} catch (err) {
console.error("Error in getStudentDetailsForDashboard:", err);
return res.status(500).json({ error: "Server error fetching profile" });
}
}

/**
* POST /student/profile
* Called only when student submits the form (create or explicit update).
* This will create profile if none exists, otherwise it will update.
*/
export async function saveStudentProfile(req, res) {
try {
let { education_level, subject_tags } = req.body;

// basic validation
if (!education_level || !subject_tags) {
return res.status(400).json({ error: "Missing required fields" });
}

// convert comma string to array if needed
if (typeof subject_tags === "string") {
subject_tags = subject_tags.split(",").map(s => s.trim()).filter(Boolean);
}

const existing = await getStudentDetailsService(req.user.id);

if (!existing) {
await createStudentProfileService(req.user.id, education_level, subject_tags);
return res.json({ message: "Profile created" });
} else {
await updateStudentProfileService(req.user.id, education_level, subject_tags);
return res.json({ message: "Profile updated" });
}
} catch (err) {
console.error("Error in saveStudentProfile:", err);
return res.status(500).json({ error: "Server error while saving profile" });
}
}
118 changes: 118 additions & 0 deletions database_learnsync.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
-- Database: learnsync_database

-- DROP DATABASE IF EXISTS learnsync_database;

CREATE DATABASE learnsync_database
WITH
OWNER = postgres
ENCODING = 'UTF8'
LC_COLLATE = 'English_United States.1252'
LC_CTYPE = 'English_United States.1252'
LOCALE_PROVIDER = 'libc'
TABLESPACE = pg_default
CONNECTION LIMIT = -1
IS_TEMPLATE = False;

-----------------------------------------------------------------

---the tables below are for zoom meeting, they have not been implememted completely(lack users)
----will be completed when working upon meeting inetgration
CREATE TABLE zoom_accounts (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
client_id TEXT,
client_secret TEXT,
account_id TEXT,
access_token TEXT,
token_expires_at BIGINT
);
CREATE TABLE sessions (
session_id SERIAL PRIMARY KEY,
description TEXT NOT NULL,
start_time TIMESTAMP NOT NULL,
duration_minutes INT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending' -- pending, accepted, rejected, completed
);

CREATE TABLE meetings (
meeting_id SERIAL PRIMARY KEY,
link TEXT NOT NULL,
zoom_account_id INT NOT NULL REFERENCES zoom_accounts(id) ON DELETE CASCADE,
session_id INT REFERENCES sessions(session_id) ON DELETE CASCADE,
start_time TIMESTAMP NOT NULL,
duration_minutes INT NOT NULL
);

------------------------------------------------------------------------------------------------

---This is complete zoom accounts, system zoom accounts which are used for scheduling meetings
INSERT INTO zoom_accounts (name, client_id, client_secret, account_id, access_token, token_expires_at)
VALUES (
'me',
'fL85BzWMSRqLbSyFJFN3fQ',
'v5yUyx6aHvP3avSxnN9P590xOXFVoiAv',
'gV_PX5AdRWmUF3GMQItB3g',
NULL,
0
),
(
'me',
'Zk2T5hrsQGuCWtWWtsKCQg',
'M0FjcxZ16VbeSvclmIsFJhcLOKoAqBKv',
'4CJlZ93eQVmPAjaiNhO5Pw',
NULL,
0
)

----------------------------------------------------------------
------User table used by all users including admins, instructors, students
-----this table is mainly used for signup and login
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'student',
time_zone VARCHAR(50) DEFAULT 'UTC',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
status VARCHAR(50) NOT NULL DEFAULT 'active'
);

-- Index for fast email lookup (login/lookup)
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

-- Insert an initial admin user
INSERT INTO users (name, email, password_hash, role, time_zone, status)
VALUES (
'Admin User',
'admin@learnsync.com',
'$2b$10$k8C4kNzW23L4gbCakjSlxesE1RET1HaAP0QTv.uoXzz8qWHyRxYJK',
'admin',
'UTC',
'active'
);

--Extra info of instructor
CREATE TABLE IF NOT EXISTS instructor_details (
id BIGSERIAL PRIMARY KEY,
instructor_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
certifications TEXT[], -- array of URLs for PDF/image files(at the moment all these
-- files are going in local memory in root directory, named upload)
demo_material TEXT[], -- array of URLs for PDF/image/video files
subject_tags TEXT[], -- array of strings
education_level_tags TEXT[], -- array of strings
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);


--Extra info of student
CREATE TABLE IF NOT EXISTS student_details (
student_id INT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
education_level VARCHAR,
subject_tags TEXT[], -- array of subjects interested in
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

Loading