diff --git a/README.md b/README.md index 88b96f356a..b525382753 100644 --- a/README.md +++ b/README.md @@ -23,22 +23,14 @@ git clone https://github.com/CS3219-AY2425S1/cs3219-ay2425s1-project-g39.git cd cs3219-ay2425s1-project-g39 ``` -2. Install the required dependencies. +2. Add environment variables. -```bash -npm run install-all -``` - -> [!NOTE] -> The above command installs the dependencies for all of the services. No need to do it individually! +Add the provided secret .env folder in the root directory ```cs3219-ay2425s1-project-g39``` - - -3. Run the frontend. +3. Run the docker containers. ```bash -cd frontend -npm run dev +docker compose up -d ``` Congratulations! You have successfully set up PeerPrep. :tada: @@ -53,3 +45,10 @@ Sign up for an account with your email address and password, and use that to log From here, just click on any of the questions to see their descriptions. You can filter for any topic or difficulty of your choosing using the selectors. +4. Stop and remove the docker containers and images. + +```bash +docker compose up --rmi "all" +``` + + diff --git a/compose.yaml b/compose.yaml index 100dfe3672..f8b4978581 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,6 +2,7 @@ services: nginx: build: context: ./nginx + container_name: nginx restart: always volumes: - ./nginx/default.conf:/tmp/default.conf @@ -9,12 +10,14 @@ services: - react-build:/app/dist environment: USER_SERVICE_ADDR: user-service:3001 + QUESTION_SERVICE_ADDR: question-service:8000 ports: - "80:80" - "443:443" depends_on: - - user-service - frontend + - user-service + - question-service healthcheck: test: [ @@ -29,6 +32,7 @@ services: frontend: build: context: ./frontend + container_name: frontend-build volumes: - react-build:/app/dist entrypoint: ["sh", "-c", "npm run build && exit 0"] @@ -38,7 +42,7 @@ services: context: ./user-service container_name: user-service-backend env_file: - - .env + - .env/.user_env depends_on: - user-service-mongo volumes: @@ -46,10 +50,18 @@ services: - /app/node_modules user-service-mongo: + container_name: user-service-mongo-test image: mongo:4.2 volumes: - mongo-data:/data/db + question-service: + build: + context: ./question-service + container_name: question-service-backend + env_file: + - .env/.question_env + volumes: mongo-data: react-build: \ No newline at end of file diff --git a/frontend/src/pages/Admin.tsx b/frontend/src/pages/Admin.tsx index 5db66852f1..ab2aea8979 100644 --- a/frontend/src/pages/Admin.tsx +++ b/frontend/src/pages/Admin.tsx @@ -1,29 +1,10 @@ -import { - Accordion, - AppShell, - Button, - Card, - Container, - FileInput, - Group, - Image as MantineImage, - MantineProvider, - Modal, - MultiSelect, - Portal, - Select, - Stack, - Text, - TextInput, - Textarea, - Title, - createTheme, -} from '@mantine/core'; +import { Accordion, AppShell, Button, Card, Container, Group, Modal, MultiSelect, Select, Stack, Text, TextInput, Textarea, Title } from '@mantine/core'; import { useListState } from '@mantine/hooks'; import { Notifications, notifications } from '@mantine/notifications'; import '@mantine/notifications/styles.css'; import { useEffect, useMemo, useState } from 'react'; + interface Question { id: string; _id: string; @@ -34,7 +15,7 @@ interface Question { images: string[]; } -const API_BASE_URL = 'http://localhost:8000'; +const API_BASE_URL = 'http://localhost/api/questions'; const difficulties = ['Easy', 'Medium', 'Hard']; const topics = [ @@ -56,12 +37,12 @@ function QuestionEditor() { const [newDescription, setNewDescription] = useState(''); const [newDifficulty, setNewDifficulty] = useState(null); const [newTopic, setNewTopic] = useState([]); - const [editingId, setEditingId] = useState(null); + const [editingId, setEditingId] = useState(undefined); const [filterDifficulty, setFilterDifficulty] = useState(null); const [filterTopic, setFilterTopic] = useState([]); - const [newImageFiles, setImageFiles] = useState([]); + // const [newImageFiles, setImageFiles] = useState([]); const [newImageNames, setImageNames] = useState([]); - const [imageSrc, setImageSrc] = useState(''); + // const [imageSrc, setImageSrc] = useState(''); useEffect(() => { fetchQuestions(); @@ -86,37 +67,37 @@ function QuestionEditor() { ); }; - const uploadImages = async () => { - const formData = new FormData(); - newImageFiles.forEach((file) => { - formData.append('img', file); - }); + // const uploadImages = async () => { + // const formData = new FormData(); + // newImageFiles.forEach((file) => { + // formData.append('img', file); + // }); - const response = await fetch(`${API_BASE_URL}/img`, { - method: 'POST', - body: formData, - }); + // const response = await fetch(`${API_BASE_URL}/img`, { + // method: 'POST', + // body: formData, + // }); - const data = await response.json(); + // const data = await response.json(); - if (!response.ok) { - console.error('Failed to upload image'); - } else { - setImageNames([...newImageNames, data.filename]); - } - }; + // if (!response.ok) { + // console.error('Failed to upload image'); + // } else { + // setImageNames([...newImageNames, data.filename]); + // } + // }; - const getImage = async (filename: string) => { - const response = await fetch(`${API_BASE_URL}/img/${filename}`); - if (!response.ok) { - console.error('Failed to fetch image'); - } else { - const blob = await response.blob(); - // Create an object URL from the blob - const url = URL.createObjectURL(blob); - setImageSrc(url); - } - }; + // const getImage = async (filename: string) => { + // const response = await fetch(`${API_BASE_URL}/img/${filename}`); + // if (!response.ok) { + // console.error('Failed to fetch image'); + // } else { + // const blob = await response.blob(); + // // Create an object URL from the blob + // const url = URL.createObjectURL(blob); + // setImageSrc(url); + // } + // }; const addQuestion = async () => { if (newTitle.trim() === '') { @@ -280,13 +261,13 @@ function QuestionEditor() { }, [questions, filterDifficulty, filterTopic]); const resetForm = () => { - setEditingId(null); + setEditingId(undefined); setNewTitle(''); setNewDescription(''); setNewDifficulty(null); setNewTopic([]); setImageNames([]); - setImageFiles([]); + // setImageFiles([]); }; return ( @@ -464,4 +445,4 @@ function QuestionEditor() { ); } -export default QuestionEditor; +export default QuestionEditor; \ No newline at end of file diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 550afa5a76..c9b8613b48 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -1,15 +1,20 @@ import { createBrowserRouter } from 'react-router-dom'; import Admin from './pages/Admin'; -import Landing from './pages/Landing'; + +// import Landing from './pages/Landing'; const router = createBrowserRouter([ + // { + // path: '/', + // element: , + // }, + // { + // path: '/pages/', + // element: , + // }, { path: '/', - element: , - }, - { - path: '/pages/', element: , }, ]); diff --git a/frontend/tsconfig.app.tsbuildinfo b/frontend/tsconfig.app.tsbuildinfo new file mode 100644 index 0000000000..9d39389e31 --- /dev/null +++ b/frontend/tsconfig.app.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.tsx","./src/main.tsx","./src/router.tsx","./src/vite-env.d.ts","./src/components/modal/loginmodal.tsx","./src/components/modal/signupmodal.tsx","./src/pages/admin.tsx","./src/pages/landing.tsx"],"version":"5.6.2"} \ No newline at end of file diff --git a/frontend/tsconfig.node.tsbuildinfo b/frontend/tsconfig.node.tsbuildinfo new file mode 100644 index 0000000000..98ef2f9966 --- /dev/null +++ b/frontend/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./vite.config.ts"],"version":"5.6.2"} \ No newline at end of file diff --git a/nginx/default.conf b/nginx/default.conf index 341e43f532..ddca610387 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -22,12 +22,18 @@ server { } location ~ ^/api/(user|auth) { - proxy_pass http://$USER_SERVICE_ADDR; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + + location ~ ^/api/(questions) { + proxy_pass http://$QUESTION_SERVICE_ADDR; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } } # # HTTPS server block diff --git a/nginx/start.sh b/nginx/start.sh index a5d71490cb..7aafb11903 100644 --- a/nginx/start.sh +++ b/nginx/start.sh @@ -1,2 +1,3 @@ #!/bin/bash -envsubst '\$USER_SERVICE_ADDR' < /tmp/default.conf > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;' +envsubst '\$USER_SERVICE_ADDR \$QUESTION_SERVICE_ADDR' < /tmp/default.conf > /etc/nginx/conf.d/default.conf +nginx -g 'daemon off;' diff --git a/package.json b/package.json index f4a2e71b56..34ee738e4c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "install-all": "npm install && run-p install-all:*", "install-all:frontend": "cd frontend && npm install", - "install-all:question-service": "cd question-service && npm install" + "install-all:question-service": "cd question-service && npm install", + "install-all:user-service": "cd user-service && npm install" }, "devDependencies": { "@eslint/js": "^9.10.0", diff --git a/question-service/API.md b/question-service/API.md index 89fcb8382f..148852b89b 100644 --- a/question-service/API.md +++ b/question-service/API.md @@ -1,6 +1,6 @@ # Question Service API Documentation -endpoint: `http://localhost:8000` +endpoint: `http://localhost:8000/api` ## CREATE Route diff --git a/question-service/index.js b/question-service/index.js index 88b2e45276..d22b0ae327 100644 --- a/question-service/index.js +++ b/question-service/index.js @@ -30,7 +30,7 @@ app.use((req, res, next) => { next(); }); -app.use('/', router); +app.use('/api/questions', router); /** * IMAGE HANDLING diff --git a/question-service/model/repository.js b/question-service/model/repository.js index 70af6dafc6..4db527fcc9 100644 --- a/question-service/model/repository.js +++ b/question-service/model/repository.js @@ -1,9 +1,10 @@ -import Question from "./Question.js"; -import "dotenv/config"; -import { connect } from "mongoose"; +import 'dotenv/config'; +import { connect } from 'mongoose'; +import Question from './Question.js'; +import 'dotenv/config'; export async function connectToDB() { - let mongoDBUri = process.env.MONGO_URI; + let mongoDBUri = process.env.QUESTION_MONGO_CLOUD_URI; await connect(mongoDBUri); -} \ No newline at end of file +} diff --git a/question-service/server.js b/question-service/server.js index 9c20c1a4e7..354fbc1b57 100644 --- a/question-service/server.js +++ b/question-service/server.js @@ -1,9 +1,9 @@ -import http from "http"; -import index from "./index.js"; import "dotenv/config"; +import http from "http"; +import index from './index.js'; import { connectToDB } from "./model/repository.js"; -const port = process.env.PORT || 8000; +const port = process.env.QUESTION_PORT || 8000; const server = http.createServer(index); diff --git a/user-service/controller/authController.js b/user-service/controller/authController.js index 62e96ee54e..b9b7c21a99 100644 --- a/user-service/controller/authController.js +++ b/user-service/controller/authController.js @@ -27,7 +27,7 @@ const loginUser = async (req, res) => { user.lastLogin = new Date(); await updateUserById(user._id, { lastLogin: user.lastLogin }); - const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '1y' }); + const token = jwt.sign({ userId: user._id }, process.env.USER_JWT_SECRET, { expiresIn: '1y' }); res.json({ token: token, user_id: user._id.toString() }); } catch (err) { res.status(500).json({ error: err.message }); @@ -59,13 +59,13 @@ const forgotPassword = async (req, res) => { port: 465, secure: true, auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PASS, + user: process.env.USER_EMAIL_USER, + pass: process.env.USER_EMAIL_PASS, }, }); const mailOptions = { - from: process.env.EMAIL_USER, + from: process.env.USER_EMAIL_USER, to: user.email, subject: 'Password Reset', text: `You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n diff --git a/user-service/controller/userManipulation.js b/user-service/controller/userManipulation.js index 94ce7592bd..85d111e032 100644 --- a/user-service/controller/userManipulation.js +++ b/user-service/controller/userManipulation.js @@ -41,7 +41,7 @@ const getUsers = async () => { // Delete all users const deleteAllUsers = async () => { - await User.deleteMany({ email: { $ne: process.env.EMAIL_USER } }); + await User.deleteMany({ email: { $ne: process.env.USER_EMAIL_USER } }); }; // Insert default admin data @@ -53,9 +53,9 @@ const insertDefaultData = async () => { console.log('No users found, inserting default data...'); // Create a default admin user - const hashedPassword = await bcrypt.hash(process.env.EMAIL_PASS, 10); + const hashedPassword = await bcrypt.hash(process.env.USER_EMAIL_PASS, 10); const adminUser = new User({ - email: process.env.EMAIL_USER, + email: process.env.USER_EMAIL_USER, username: 'master_admin', password: hashedPassword, isAdmin: true, diff --git a/user-service/middleware/authMiddleware.js b/user-service/middleware/authMiddleware.js index 4572bafd03..02fb245d50 100644 --- a/user-service/middleware/authMiddleware.js +++ b/user-service/middleware/authMiddleware.js @@ -10,7 +10,7 @@ const authMiddleware = async (req, res, next) => { } // Verify the token and extract the user payload - const user = jwt.verify(token, process.env.JWT_SECRET); + const user = jwt.verify(token, process.env.USER_JWT_SECRET); // Load the latest user info from the database const dbUser = await findUserById(user.userId); diff --git a/user-service/server.js b/user-service/server.js index 2837ba5c2e..7db02cad1d 100644 --- a/user-service/server.js +++ b/user-service/server.js @@ -1,7 +1,7 @@ const app = require('./app'); // Start server -const PORT = process.env.PORT || 3001; +const PORT = process.env.USER_PORT || 3001; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); diff --git a/user-service/tests/userService.test.js b/user-service/tests/userService.test.js index 4db5347bcb..aad9be7735 100644 --- a/user-service/tests/userService.test.js +++ b/user-service/tests/userService.test.js @@ -28,8 +28,8 @@ describe('User Service API', () => { const adminResponse = await request(app) .post('/api/auth/login') .send({ - email: process.env.EMAIL_USER, - password: process.env.EMAIL_PASS, + email: process.env.USER_EMAIL_USER, + password: process.env.USER_EMAIL_PASS, }); adminToken = adminResponse.body.token; });