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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#server
PORT = 3000

#db
DB_HOST=localhost
DB_USER=db_user
DB_PASSWORD=db_pass
DB_NAME=db_name
DB_DIALECT=postgres
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#dependencies
node_modules/

#env variables
.env
21 changes: 21 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import express from 'express';
import studentRoutes from './routes/student.js';
import authRoutes from './routes/auth.js';
import dotenv from 'dotenv';

dotenv.config();

const app = express();
const port = Number(process.env.PORT) || 3000;

app.use(express.json());



app.use('/students', studentRoutes);
app.use('/api/auth', authRoutes);


app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
26 changes: 26 additions & 0 deletions config/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { config } from 'dotenv';
config();

export default {
development: {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD || null,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT
},
test: {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD || null,
database: process.env.DB_NAME + "_test",
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT
},
production: {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD || null,
database: process.env.DB_NAME + "_prod",
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT
}
};
17 changes: 17 additions & 0 deletions config/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// db.js
import { config } from 'dotenv';
config();

const { DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_DIALECT } = process.env;

const dbConfig = {
development: {
host: DB_HOST,
username: DB_USER,
password: DB_PASSWORD,
database: DB_NAME,
dialect: DB_DIALECT,
},
};

export default dbConfig;
30 changes: 30 additions & 0 deletions config/sequelize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Sequelize } from 'sequelize';
import config from '../config/config.js';
import pino from 'pino'; // Import pino

const env = process.env.NODE_ENV || 'development';
const dbConfig = config[env];

// Create a pino logger instance with prettyPrint using pino-pretty
const logger = pino({
level: 'info',
transport: {
target: 'pino-pretty', // Use pino-pretty for pretty printing
options: {
colorize: true, // Adds colors to the output
translateTime: 'SYS:standard', // Formats timestamp in a human-readable way
},
},
});

const sequelize = new Sequelize(dbConfig.database, dbConfig.username, dbConfig.password, {
host: dbConfig.host,
dialect: dbConfig.dialect,
});

// Test the database connection
sequelize.authenticate()
.then(() => logger.info('Database connected successfully'))
.catch(error => logger.error('Unable to connect to the database:', error));

export default sequelize;
34 changes: 34 additions & 0 deletions controllers/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { registerUser, loginUser, refreshAccessToken } from '../services/auth.js';
import { sendResponse, sendErrorResponse } from '../utils/response.js';

// Register a user
export const signup = async (req, res) => {
try {
const user = await registerUser(req.body);
sendResponse(res, 201, 'User created successfully', user);
} catch (error) {
sendErrorResponse(res, error.message);
}
};

// Login a user and return both access and refresh tokens
export const login = async (req, res) => {
try {
const { email, password } = req.body;
const { accessToken, refreshToken } = await loginUser(email, password);
sendResponse(res, 200, 'Login successful', { accessToken, refreshToken });
} catch (error) {
sendErrorResponse(res, error.message, 401);
}
};

// Refresh the access token using a valid refresh token
export const refresh = async (req, res) => {
try {
const { refreshToken } = req.body;
const { accessToken } = await refreshAccessToken(refreshToken);
sendResponse(res, 200, 'Access token refreshed', { accessToken });
} catch (error) {
sendErrorResponse(res, error.message, 401);
}
};
67 changes: 67 additions & 0 deletions controllers/student.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { sendResponse, sendErrorResponse } from '../utils/response.js';
import { createStudent, getAllStudents, getStudentById, updateStudent, deleteStudent } from '../services/student.js';
import studentValidationSchema from '../utils/validators/student.js';
import { HTTP_STATUS_CODES } from '../utils/constants/status-code.js'; // Import status codes

export const createStudentController = async (req, res) => {
try {
const { error } = studentValidationSchema.validate(req.body);
if (error) {
return sendErrorResponse(res, error.details[0].message, HTTP_STATUS_CODES.BAD_REQUEST);
}
const student = await createStudent(req.body);
sendResponse(res, HTTP_STATUS_CODES.CREATED, 'Student created successfully', student);
} catch (error) {
sendErrorResponse(res, error.message || 'An error occurred while creating student', HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
}
};

export const getAllStudentsController = async (req, res) => {
try {
const students = await getAllStudents();
sendResponse(res, HTTP_STATUS_CODES.OK, 'Students fetched successfully', students);
} catch (error) {
sendErrorResponse(res, error.message || 'An error occurred while fetching students', HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
}
};

export const getStudentByIdController = async (req, res) => {
try {
const student = await getStudentById(req.params.id);
if (!student) {
return sendErrorResponse(res, 'Student not found', HTTP_STATUS_CODES.NOT_FOUND);
}
sendResponse(res, HTTP_STATUS_CODES.OK, 'Student retrieved successfully', student);
} catch (error) {
sendErrorResponse(res, error.message || 'An error occurred while retrieving student', HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
}
};

export const updateStudentController = async (req, res) => {
try {
const { error } = studentValidationSchema.validate(req.body);
if (error) {
return sendErrorResponse(res, error.details[0].message, HTTP_STATUS_CODES.BAD_REQUEST);
}

const student = await updateStudent(req.params.id, req.body);
if (!student) {
return sendErrorResponse(res, 'Student not found', HTTP_STATUS_CODES.NOT_FOUND);
}
sendResponse(res, HTTP_STATUS_CODES.OK, 'Student updated successfully', student);
} catch (error) {
sendErrorResponse(res, error.message || 'An error occurred while updating student', HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
}
};

export const deleteStudentController = async (req, res) => {
try {
const response = await deleteStudent(req.params.id);
if (!response) {
return sendErrorResponse(res, 'Student not found', HTTP_STATUS_CODES.NOT_FOUND);
}
sendResponse(res, HTTP_STATUS_CODES.OK, 'Student deleted successfully', response);
} catch (error) {
sendErrorResponse(res, error.message || 'An error occurred while deleting student', HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
}
};
18 changes: 18 additions & 0 deletions middleware/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// middleware/authMiddleware.js
import jwt from 'jsonwebtoken';

export const verifyToken = (req, res, next) => {
const token = req.headers.authorization && req.headers.authorization.split(' ')[1]; // Assuming token is sent as "Bearer <token>"

if (!token) {
return res.status(403).json({ message: 'No token provided' });
}

jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Invalid or expired token' });
}
req.user = decoded; // Store user info in request
next();
});
};
11 changes: 11 additions & 0 deletions middleware/role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// middleware/roleMiddleware.js
export const authorizeRole = (role) => {
return (req, res, next) => {
// Check if the user's role matches the required role
if (req.user.role !== role) {
return res.status(403).json({ message: `Access denied. ${role}s only` });
}
next(); // Allow access if role matches
};
};

22 changes: 22 additions & 0 deletions migrations/20250203101448-create-user.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
/**
* Add altering commands here.
*
* Example:
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
},

async down (queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
};
22 changes: 22 additions & 0 deletions migrations/20250203101535-create-student.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
/**
* Add altering commands here.
*
* Example:
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
},

async down (queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
};
16 changes: 16 additions & 0 deletions migrations/20250203113433-add-column-to-students.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

module.exports = {
async up(queryInterface, Sequelize) {
// Add the new column
await queryInterface.addColumn('Students', 'adress', {
type: Sequelize.STRING, // Specify the data type
allowNull: true, // You can change this based on your needs
});
},

async down(queryInterface, Sequelize) {
// Revert the column addition in case you want to rollback
await queryInterface.removeColumn('Students', 'adress');
}
};
25 changes: 25 additions & 0 deletions migrations/20250203113942-rename-column-in-students.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
/**
* Add altering commands here.
*
* Example:
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
queryInterface.renameColumn('Students','adress','address');
},

async down (queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
queryInterface.renameColumn('Students','address','adress');

}
};
34 changes: 34 additions & 0 deletions migrations/20250204090949-update-student-model.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
// await queryInterface.removeColumn('Students', 'name');
// await queryInterface.removeColumn('Students', 'email');
// await queryInterface.removeColumn('Students', 'password');
// await queryInterface.removeColumn('Students', 'age');

await queryInterface.addColumn('Students', 'user_id', {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id',
},
allowNull: true,
});
},

down: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('Students', 'name', {
type: Sequelize.STRING,
});
await queryInterface.addColumn('Students', 'email', {
type: Sequelize.STRING,
});
await queryInterface.addColumn('Students', 'password', {
type: Sequelize.STRING,
});
await queryInterface.addColumn('Students', 'age', {
type: Sequelize.INTEGER,
});

await queryInterface.removeColumn('Students', 'userId');
},
};
10 changes: 10 additions & 0 deletions models/associations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import User from './user.js';
import Student from './student.js';

// Define relationships
User.hasOne(Student, { foreignKey: 'userId', onDelete: 'CASCADE' });
Student.belongsTo(User, { foreignKey: 'userId' });

export default function setupAssociations() {
console.log('Associations have been established.');
}
Loading