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
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
# AWS
CLF-C02: AWS Certified Cloud Practitioner
Technology Stack
Frontend
Framework: React (Next.js recommended for SSR)
Styling: Tailwind CSS
Backend
Framework/Language: Node.js with Express.js
Database: PostgreSQL
Other Key Decisions
Payment Gateway: [To be decided - e.g., Stripe, PayPal]
Deployment Platform: [To be decided - e.g., Vercel, Heroku, AWS]
Version Control: Git (already in use)
23 changes: 23 additions & 0 deletions backend/src/config/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { Pool } = require('pg');

const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_DATABASE,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
});

pool.on('connect', () => {
console.log('Connected to the PostgreSQL database!');
});

pool.on('error', (err) => {
console.error('Unexpected error on idle client', err);
process.exit(-1);
});

module.exports = {
query: (text, params) => pool.query(text, params),
pool, // Export pool if direct access is needed
};
5 changes: 5 additions & 0 deletions backend/src/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const dbConfig = require('./db');

module.exports = {
dbConfig
};
88 changes: 88 additions & 0 deletions backend/src/controllers/authController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const User = require('../models/User');
const jwt = require('jsonwebtoken');

// Basic input validation (can be expanded with a library like Joi)
const validateInput = (email, password) => {
if (!email || !password || password.length < 6) {
return 'Invalid input: Email is required and password must be at least 6 characters.';
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return 'Invalid input: Please provide a valid email address.';
}
return null; // No error
};

exports.register = async (req, res) => {
try {
const { email, password, firstName, lastName, phoneNumber } = req.body;

const validationError = validateInput(email, password);
if (validationError) {
return res.status(400).json({ message: validationError });
}

const existingUser = await User.findByEmail(email);
if (existingUser) {
return res.status(400).json({ message: 'User already exists with this email.' });
}

const newUser = await User.create(email, password, firstName, lastName, phoneNumber);
// Don't send password_hash back
const userResponse = { ...newUser };
delete userResponse.password_hash;

// Optionally, generate a JWT token upon registration
const token = jwt.sign({ id: newUser.id, email: newUser.email }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
});

res.status(201).json({
message: 'User registered successfully',
user: userResponse,
token // Send token if auto-login after registration
});

} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ message: 'Server error during registration.' });
}
};

exports.login = async (req, res) => {
try {
const { email, password } = req.body;

const validationError = validateInput(email, password);
if (validationError) {
// More generic message for login to avoid confirming if email exists or not
return res.status(400).json({ message: 'Invalid email or password.' });
}

const user = await User.findByEmail(email);
if (!user) {
return res.status(401).json({ message: 'Invalid email or password.' }); // Generic message
}

const isMatch = await User.comparePassword(password, user.password_hash);
if (!isMatch) {
return res.status(401).json({ message: 'Invalid email or password.' }); // Generic message
}

const token = jwt.sign({ id: user.id, email: user.email }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
});

// Don't send password_hash back
const userResponse = { id: user.id, email: user.email, first_name: user.first_name, last_name: user.last_name };

res.status(200).json({
message: 'Login successful',
token,
user: userResponse
});

} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Server error during login.' });
}
};
60 changes: 60 additions & 0 deletions backend/src/controllers/categoryController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const Category = require('../models/Category');
const Product = require('../models/Product'); // To fetch products by category

exports.getAllCategories = async (req, res) => {
try {
const categories = await Category.findAll();
res.json(categories);
} catch (error) {
console.error('Get all categories error:', error);
res.status(500).json({ message: 'Server error while fetching categories.' });
}
};

exports.getCategoryBySlug = async (req, res) => {
try {
const category = await Category.findBySlug(req.params.slug);
if (!category) {
return res.status(404).json({ message: 'Category not found.' });
}
res.json(category);
} catch (error) {
console.error(`Get category by slug ${req.params.slug} error:`, error);
res.status(500).json({ message: 'Server error while fetching category.' });
}
};

exports.getProductsByCategorySlug = async (req, res) => {
try {
const { slug } = req.params;
const { sortBy, order, limit = 10, page = 1 } = req.query;
const offset = (parseInt(page, 10) - 1) * parseInt(limit, 10);

const category = await Category.findBySlug(slug);
if (!category) {
return res.status(404).json({ message: 'Category not found.' });
}

const { products, total } = await Product.findAll({
category: slug, // Pass slug to findAll
sortBy,
order,
limit: parseInt(limit, 10),
offset
});

res.json({
category, // Send category details along with products
data: products,
pagination: {
totalItems: total,
totalPages: Math.ceil(total / limit),
currentPage: parseInt(page, 10),
pageSize: parseInt(limit, 10)
}
});
} catch (error) {
console.error(`Get products by category slug ${req.params.slug} error:`, error);
res.status(500).json({ message: 'Server error while fetching products for category.' });
}
};
90 changes: 90 additions & 0 deletions backend/src/controllers/prescriptionController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const Prescription = require('../models/Prescription');

// Basic validation for prescription data (can be significantly expanded)
const validatePrescriptionData = (data) => {
if (!data.patientName || !data.prescriptionDate || !data.sphereRight || !data.sphereLeft || !data.prescriptionType) {
return 'Missing required fields: patientName, prescriptionDate, sphereRight, sphereLeft, prescriptionType are mandatory.';
}
// Add more specific validations for sphere, cyl, axis formats, PD ranges, etc.
return null;
};


exports.createPrescription = async (req, res) => {
try {
const userId = req.user.id; // From protect middleware
const validationError = validatePrescriptionData(req.body);
if (validationError) {
return res.status(400).json({ message: validationError });
}

const prescription = await Prescription.create(userId, req.body);
res.status(201).json(prescription);
} catch (error) {
console.error('Create prescription error:', error);
res.status(500).json({ message: 'Server error while creating prescription.' });
}
};

exports.getPrescriptions = async (req, res) => {
try {
const userId = req.user.id;
const prescriptions = await Prescription.findByUserId(userId);
res.json(prescriptions);
} catch (error) {
console.error('Get prescriptions error:', error);
res.status(500).json({ message: 'Server error while fetching prescriptions.' });
}
};

exports.getPrescriptionById = async (req, res) => {
try {
const userId = req.user.id;
const { id } = req.params;
const prescription = await Prescription.findByIdAndUserId(id, userId);
if (!prescription) {
return res.status(404).json({ message: 'Prescription not found or not owned by user.' });
}
res.json(prescription);
} catch (error) {
console.error('Get prescription by ID error:', error);
res.status(500).json({ message: 'Server error while fetching prescription.' });
}
};

exports.updatePrescription = async (req, res) => {
try {
const userId = req.user.id;
const { id } = req.params;

// Optional: Validate data before sending to model
// const validationError = validatePrescriptionData(req.body);
// if (validationError) {
// return res.status(400).json({ message: validationError });
// }

const updatedPrescription = await Prescription.update(id, userId, req.body);
if (!updatedPrescription) {
return res.status(404).json({ message: 'Prescription not found or not owned by user for update.' });
}
res.json(updatedPrescription);
} catch (error) {
console.error('Update prescription error:', error);
res.status(500).json({ message: 'Server error while updating prescription.' });
}
};

exports.deletePrescription = async (req, res) => {
try {
const userId = req.user.id;
const { id } = req.params;
const success = await Prescription.delete(id, userId);
if (!success) {
return res.status(404).json({ message: 'Prescription not found or not owned by user for deletion.' });
}
res.status(204).send(); // No content
} catch (error) {
console.error('Delete prescription error:', error);
res.status(500).json({ message: 'Server error while deleting prescription.' });
}
};
42 changes: 42 additions & 0 deletions backend/src/controllers/productController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const Product = require('../models/Product');

exports.getAllProducts = async (req, res) => {
try {
const { category, sortBy, order, limit = 10, page = 1 } = req.query;
const offset = (parseInt(page, 10) - 1) * parseInt(limit, 10);

const { products, total } = await Product.findAll({
category,
sortBy,
order,
limit: parseInt(limit, 10),
offset
});

res.json({
data: products,
pagination: {
totalItems: total,
totalPages: Math.ceil(total / limit),
currentPage: parseInt(page, 10),
pageSize: parseInt(limit, 10)
}
});
} catch (error) {
console.error('Get all products error:', error);
res.status(500).json({ message: 'Server error while fetching products.' });
}
};

exports.getProductById = async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return res.status(404).json({ message: 'Product not found.' });
}
res.json(product);
} catch (error) {
console.error(`Get product by ID ${req.params.id} error:`, error);
res.status(500).json({ message: 'Server error while fetching product.' });
}
};
34 changes: 34 additions & 0 deletions backend/src/middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const jwt = require('jsonwebtoken');
const User = require('../models/User'); // To ensure user exists, optional

// Middleware to verify JWT and attach user to request
const protect = async (req, res, next) => {
let token;

if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
try {
// Get token from header
token = req.headers.authorization.split(' ')[1];

// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);

// Get user from the token's ID
// You might choose to just trust the decoded.id and not hit the DB here for performance,
// or hit the DB to ensure the user still exists and hasn't been disabled/deleted.
// For this example, we'll just use the decoded ID.
req.user = { id: decoded.id, email: decoded.email }; // Attach minimal user info

next();
} catch (error) {
console.error('Token verification failed:', error);
res.status(401).json({ message: 'Not authorized, token failed' });
}
}

if (!token) {
res.status(401).json({ message: 'Not authorized, no token' });
}
};

module.exports = { protect };
27 changes: 27 additions & 0 deletions backend/src/models/Category.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const db = require('../config/db');

const Category = {
async findAll() {
const queryText = 'SELECT id, name, slug, description, parent_id FROM ProductCategories ORDER BY name ASC';
try {
const { rows } = await db.query(queryText);
return rows;
} catch (err) {
console.error('Error fetching all categories:', err);
throw err;
}
},

async findBySlug(slug) {
const queryText = 'SELECT id, name, slug, description, parent_id FROM ProductCategories WHERE slug = $1';
try {
const { rows } = await db.query(queryText, [slug]);
return rows[0];
} catch (err) {
console.error(`Error fetching category by slug ${slug}:`, err);
throw err;
}
}
};

module.exports = Category;
Loading