diff --git a/.k8s/emptydir-volume/database/configmap.yaml b/.k8s/emptydir-volume/database/configmap.yaml index 05e0a37..774403f 100644 --- a/.k8s/emptydir-volume/database/configmap.yaml +++ b/.k8s/emptydir-volume/database/configmap.yaml @@ -4,4 +4,4 @@ metadata: name: devcamper-db-configmap namespace: devcamper-namespace data: - mongodb_db_name: 'devcamper-db' + mongodb_db_name: devcamper-db diff --git a/.k8s/emptydir-volume/database/deployment.yaml b/.k8s/emptydir-volume/database/deployment.yaml index 4f13e93..155462c 100644 --- a/.k8s/emptydir-volume/database/deployment.yaml +++ b/.k8s/emptydir-volume/database/deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: devcamper-db-container - image: mongo:4.4 + image: mongo ports: - containerPort: 27017 env: @@ -44,8 +44,8 @@ spec: memory: '1Gi' cpu: '1' volumeMounts: - - name: mongo-volume + - name: mongodb-volume mountPath: /data/db volumes: - - name: mongo-volume + - name: mongodb-volume emptyDir: {} diff --git a/.k8s/emptydir-volume/database/install.sh b/.k8s/emptydir-volume/database/install.sh index b1aa41b..f2cd6a6 100644 --- a/.k8s/emptydir-volume/database/install.sh +++ b/.k8s/emptydir-volume/database/install.sh @@ -2,6 +2,6 @@ kubectl apply -f configmap.yaml kubectl apply -f secret.yaml kubectl apply -f deployment.yaml -kubectl apply -f service-nodeport.yaml -kubectl apply -f service-clusterip.yaml +kubectl apply -f service.yaml + diff --git a/.k8s/emptydir-volume/database/secret.yaml b/.k8s/emptydir-volume/database/secret.yaml index 9dc04fb..8b8de8a 100644 --- a/.k8s/emptydir-volume/database/secret.yaml +++ b/.k8s/emptydir-volume/database/secret.yaml @@ -4,5 +4,5 @@ metadata: name: devcamper-db-secret namespace: devcamper-namespace data: - mongodb_username: 'YWRtaW4=' # base64 encoded string for 'admin' - mongodb_password: 'cGFzc3dvcmQ=' # base64 encoded string for 'password' + mongodb_username: 'YWRtaW4=' + mongodb_password: 'cGFzc3dvcmQ=' diff --git a/.k8s/emptydir-volume/database/service-nodeport.yaml b/.k8s/emptydir-volume/database/service-nodeport.yaml deleted file mode 100644 index 938ee4e..0000000 --- a/.k8s/emptydir-volume/database/service-nodeport.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: devcamper-db-nodeport-service - namespace: devcamper-namespace -spec: - type: NodePort - selector: - app: devcamper-db - ports: - - protocol: TCP - port: 27017 - targetPort: 27017 - nodePort: 32017 diff --git a/.k8s/emptydir-volume/database/service-clusterip.yaml b/.k8s/emptydir-volume/database/service.yaml similarity index 83% rename from .k8s/emptydir-volume/database/service-clusterip.yaml rename to .k8s/emptydir-volume/database/service.yaml index a49704d..6d7883c 100644 --- a/.k8s/emptydir-volume/database/service-clusterip.yaml +++ b/.k8s/emptydir-volume/database/service.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: devcamper-db-clusterip-service + name: devcamper-db-service namespace: devcamper-namespace spec: type: ClusterIP diff --git a/.k8s/emptydir-volume/database/uninstall.sh b/.k8s/emptydir-volume/database/uninstall.sh index c408ac9..398ae0c 100644 --- a/.k8s/emptydir-volume/database/uninstall.sh +++ b/.k8s/emptydir-volume/database/uninstall.sh @@ -2,5 +2,5 @@ kubectl delete -f configmap.yaml kubectl delete -f secret.yaml kubectl delete -f deployment.yaml -kubectl delete -f service-nodeport.yaml -kubectl delete -f service-clusterip.yaml +kubectl delete -f service.yaml + diff --git a/.k8s/emptydir-volume/install.sh b/.k8s/emptydir-volume/install.sh index a264e08..153a8d9 100644 --- a/.k8s/emptydir-volume/install.sh +++ b/.k8s/emptydir-volume/install.sh @@ -1,11 +1,13 @@ #!/bin/bash -# Function to run install script in a directory +# Function to run install scripts in specified directories run_install() { local dir=$1 - cd "$dir" || { echo "Failed to change directory to $dir"; exit 1; } + echo -e "\033[1;34m==================== Installing $dir ====================\033[0m" + cd "$dir" || { echo -e "\033[1;31mFailed to change directory to $dir\033[0m"; exit 1; } bash ./install.sh - cd - > /dev/null || { echo "Failed to return to previous directory"; exit 1; } + cd - > /dev/null || { echo -e "\033[1;31mFailed to return to previous directory\033[0m"; exit 1; } + echo -e "\033[1;34m==================== Finished $dir ====================\033[0m" } # Set trap to ensure we return to the original directory on exit @@ -16,3 +18,4 @@ run_install namespace run_install database run_install webapi run_install webapp +run_install network \ No newline at end of file diff --git a/.k8s/emptydir-volume/network/ingress.yaml b/.k8s/emptydir-volume/network/ingress.yaml new file mode 100644 index 0000000..661c3c4 --- /dev/null +++ b/.k8s/emptydir-volume/network/ingress.yaml @@ -0,0 +1,30 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: devcamper-ingress + namespace: devcamper-namespace + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: devcamper.webapp + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: devcamper-webapp-service + port: + number: 80 + - host: devcamper.webapi + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: devcamper-webapi-service + port: + number: 80 diff --git a/.k8s/emptydir-volume/network/install.sh b/.k8s/emptydir-volume/network/install.sh new file mode 100644 index 0000000..6ab0a3e --- /dev/null +++ b/.k8s/emptydir-volume/network/install.sh @@ -0,0 +1,4 @@ +#!bin/bash +# Install an NGINX Ingress Controller +kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml +kubectl apply -f ingress.yaml \ No newline at end of file diff --git a/.k8s/emptydir-volume/network/uninstall.sh b/.k8s/emptydir-volume/network/uninstall.sh new file mode 100644 index 0000000..84d9ef5 --- /dev/null +++ b/.k8s/emptydir-volume/network/uninstall.sh @@ -0,0 +1,2 @@ +#!bin/bash +kubectl delete -f ingress.yaml \ No newline at end of file diff --git a/.k8s/emptydir-volume/uninstall.sh b/.k8s/emptydir-volume/uninstall.sh index 3dd7216..69e7345 100644 --- a/.k8s/emptydir-volume/uninstall.sh +++ b/.k8s/emptydir-volume/uninstall.sh @@ -3,9 +3,11 @@ # Function to run uninstall script in a directory run_uninstall() { local dir=$1 - cd "$dir" || { echo "Failed to change directory to $dir"; exit 1; } + echo -e "\033[1;34m==================== Uninstalling $dir ====================\033[0m" + cd "$dir" || { echo -e "\033[1;31mFailed to change directory to $dir\033[0m"; exit 1; } bash ./uninstall.sh - cd - > /dev/null || { echo "Failed to return to previous directory"; exit 1; } + cd - > /dev/null || { echo -e "\033[1;31mFailed to return to previous directory\033[0m"; exit 1; } + echo -e "\033[1;34m==================== Finished $dir ====================\033[0m" } # Set trap to ensure we return to the original directory on exit @@ -15,4 +17,5 @@ trap 'cd - > /dev/null' EXIT run_uninstall database run_uninstall webapi run_uninstall webapp -run_uninstall namespace +run_uninstall network +run_uninstall namespace \ No newline at end of file diff --git a/.k8s/emptydir-volume/webapi/configmap.yaml b/.k8s/emptydir-volume/webapi/configmap.yaml index fba66ae..b0f3f98 100644 --- a/.k8s/emptydir-volume/webapi/configmap.yaml +++ b/.k8s/emptydir-volume/webapi/configmap.yaml @@ -4,7 +4,7 @@ metadata: name: devcamper-webapi-configmap namespace: devcamper-namespace data: - mongodb_host: 'devcamper-db-clusterip-service' + mongodb_host: 'devcamper-db-service' mongodb_port: '27017' mongodb_db_name: 'devcamper-db' mongodb_db_params: 'authSource=admin' diff --git a/.k8s/emptydir-volume/webapi/install.sh b/.k8s/emptydir-volume/webapi/install.sh index da2786b..6e708a3 100644 --- a/.k8s/emptydir-volume/webapi/install.sh +++ b/.k8s/emptydir-volume/webapi/install.sh @@ -2,4 +2,5 @@ kubectl apply -f configmap.yaml kubectl apply -f secret.yaml kubectl apply -f deployment.yaml -kubectl apply -f service-nodeport.yaml +kubectl apply -f service.yaml + diff --git a/.k8s/emptydir-volume/webapi/service-nodeport.yaml b/.k8s/emptydir-volume/webapi/service.yaml similarity index 69% rename from .k8s/emptydir-volume/webapi/service-nodeport.yaml rename to .k8s/emptydir-volume/webapi/service.yaml index 7736107..6bb8d9e 100644 --- a/.k8s/emptydir-volume/webapi/service-nodeport.yaml +++ b/.k8s/emptydir-volume/webapi/service.yaml @@ -1,14 +1,12 @@ apiVersion: v1 kind: Service metadata: - name: devcamper-webapi-nodeport-service + name: devcamper-webapi-service namespace: devcamper-namespace spec: - type: NodePort ports: - protocol: TCP port: 80 targetPort: 5000 - nodePort: 32018 selector: app: devcamper-webapi diff --git a/.k8s/emptydir-volume/webapi/uninstall.sh b/.k8s/emptydir-volume/webapi/uninstall.sh index 0a7fb30..40249bd 100644 --- a/.k8s/emptydir-volume/webapi/uninstall.sh +++ b/.k8s/emptydir-volume/webapi/uninstall.sh @@ -2,4 +2,4 @@ kubectl delete -f configmap.yaml kubectl delete -f secret.yaml kubectl delete -f deployment.yaml -kubectl delete -f service-nodeport.yaml \ No newline at end of file +kubectl delete -f service.yaml diff --git a/.k8s/emptydir-volume/webapp/configmap.yaml b/.k8s/emptydir-volume/webapp/configmap.yaml new file mode 100644 index 0000000..1d23696 --- /dev/null +++ b/.k8s/emptydir-volume/webapp/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: devcamper-webapp-configmap + namespace: devcamper-namespace +data: + react_app_devcamper_base_api_uri: 'http://devcamper.webapi' diff --git a/.k8s/emptydir-volume/webapp/deployment.yaml b/.k8s/emptydir-volume/webapp/deployment.yaml new file mode 100644 index 0000000..df86638 --- /dev/null +++ b/.k8s/emptydir-volume/webapp/deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: devcamper-webapp-deployment + namespace: devcamper-namespace +spec: + replicas: 1 + selector: + matchLabels: + app: devcamper-webapp + template: + metadata: + labels: + app: devcamper-webapp + spec: + containers: + - name: devcamper-webapp-container + image: prasadhonrao/devcamper-webapp:latest + ports: + - containerPort: 3000 + env: + - name: REACT_APP_DEVCAMPER_BASE_API_URI + valueFrom: + configMapKeyRef: + name: devcamper-webapp-configmap + key: react_app_devcamper_base_api_uri + resources: + limits: + memory: '2Gi' + cpu: '2' + requests: + memory: '1Gi' + cpu: '1' diff --git a/.k8s/emptydir-volume/webapp/install.sh b/.k8s/emptydir-volume/webapp/install.sh index bfeaf9d..13d4082 100644 --- a/.k8s/emptydir-volume/webapp/install.sh +++ b/.k8s/emptydir-volume/webapp/install.sh @@ -1 +1,4 @@ #!bin/bash +kubectl apply -f configmap.yaml +kubectl apply -f deployment.yaml +kubectl apply -f service.yaml diff --git a/.k8s/emptydir-volume/webapp/service.yaml b/.k8s/emptydir-volume/webapp/service.yaml new file mode 100644 index 0000000..0e8e168 --- /dev/null +++ b/.k8s/emptydir-volume/webapp/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: devcamper-webapp-service + namespace: devcamper-namespace +spec: + ports: + - protocol: TCP + port: 80 + targetPort: 3000 + selector: + app: devcamper-webapp diff --git a/.k8s/emptydir-volume/webapp/uninstall.sh b/.k8s/emptydir-volume/webapp/uninstall.sh index bfeaf9d..1a6e510 100644 --- a/.k8s/emptydir-volume/webapp/uninstall.sh +++ b/.k8s/emptydir-volume/webapp/uninstall.sh @@ -1 +1,4 @@ #!bin/bash +kubectl delete -f configmap.yaml +kubectl delete -f deployment.yaml +kubectl delete -f service.yaml diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..2577bd5 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,40 @@ +# Web App Configuration +WEBAPP_PORT=3000 +REACT_APP_DEVCAMPER_BASE_API_URI=http://localhost + +# Server configuration +port=5000 +node_env=development + +# Database configuration +mongodb_host=localhost +mongodb_port=27017 +mongodb_username=admin +mongodb_password=password +mongodb_db_name=devcamper-db +mongodb_db_params=authSource=admin + +# Geocoder configuration +geocoder_provider=mapquest +geocoder_api_key=m5SpLGTkplsHrlCYbX9Yj5LOiALoK5o1 + +# File upload configuration +file_upload_path=../ui/public/images +max_file_upload=1000000 + +# JWT configuration +jwt_secret=jwt123 +jwt_expire=30d +jwt_cookie_expire=30 + +# Email configuration +smtp_host=sandbox.smtp.mailtrap.io +smtp_port=2525 +smtp_email=b672b25167e875 +smtp_password=208334e8a6a9d8 +from_email=honrao.prasad@gmail.com +from_name=DevCamper + +# Rate limiting configuration +rate_limit_window=100 +rate_limit_max=1000 diff --git a/docker/docker-compose-mongo.yml b/docker/docker-compose-mongo.yml new file mode 100644 index 0000000..998e91a --- /dev/null +++ b/docker/docker-compose-mongo.yml @@ -0,0 +1,16 @@ +version: '3.8' + +services: + mongo: + image: mongo + container_name: devcamper-db + ports: + - 27017:27017 + environment: + - MONGO_INITDB_ROOT_USERNAME=admin + - MONGO_INITDB_ROOT_PASSWORD=password + volumes: + - mongo-data:/data/db + +volumes: + mongo-data: diff --git a/api/docker-compose.yml b/docker/docker-compose.yml similarity index 72% rename from api/docker-compose.yml rename to docker/docker-compose.yml index a92bc75..a179da6 100644 --- a/api/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,7 +1,15 @@ version: '3.8' services: - api: + webapp: + image: prasadhonrao/devcamper-webapp:latest + ports: + - '${WEBAPP_PORT}:3000' + environment: + - REACT_APP_DEVCAMPER_BASE_API_URI={REACT_APP_DEVCAMPER_BASE_API_URI} + ':' + ${WEBAPP_PORT} + depends_on: + - webapi + webapi: image: prasadhonrao/devcamper-webapi:latest ports: - '${port}:${port}' @@ -31,11 +39,13 @@ services: - rate_limit_window_ms=${rate_limit_window_ms} depends_on: - mongo - mongo: - image: mongo:latest + image: mongo ports: - - '27017:27017' + - 27017:27017 + environment: + - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} volumes: - mongo-data:/data/db diff --git a/ui/Dockerfile b/ui/Dockerfile new file mode 100644 index 0000000..fbdedf5 --- /dev/null +++ b/ui/Dockerfile @@ -0,0 +1,30 @@ +# Use the official Node.js image as the base image +FROM node:14-alpine + +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Set build-time environment variables +ARG REACT_APP_DEVCAMPER_BASE_API_URI +ENV REACT_APP_DEVCAMPER_BASE_API_URI=${REACT_APP_DEVCAMPER_BASE_API_URI} + +# Build the React application +RUN npm run build + +# Install a simple HTTP server to serve the static files +RUN npm install -g serve + +# Expose the port the app runs on +EXPOSE 3000 + +# Start the application +CMD ["serve", "-s", "build", "-l", "3000"] \ No newline at end of file diff --git a/ui/src/index.css b/ui/src/index.css index a6fde85..0921c2b 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -1,3 +1,7 @@ +body { + overflow-y: scroll; /* Show scrollbar on all pages */ +} + .nav-separator { width: 1px; height: 24px; @@ -46,7 +50,7 @@ img { display: flex; justify-content: center; align-items: center; - height: 100vh; /* Full viewport height */ + height: 75vh; /* Full viewport height */ } .full-size-image { diff --git a/ui/src/pages/bootcamps/AddCoursePage.jsx b/ui/src/pages/bootcamps/AddCoursePage.jsx index 081a105..d8016f9 100644 --- a/ui/src/pages/bootcamps/AddCoursePage.jsx +++ b/ui/src/pages/bootcamps/AddCoursePage.jsx @@ -1,23 +1,24 @@ import React, { useState } from 'react'; import { Link, useParams, useNavigate } from 'react-router-dom'; +import { Form, Button, Container, Row, Col, Card } from 'react-bootstrap'; +import { toast } from 'react-toastify'; import { AiOutlineLeft } from 'react-icons/ai'; import courseService from '../../services/courseService'; import { getToken } from '../../helpers/auth'; -import { toast } from 'react-toastify'; const AddCoursePage = () => { const { bootcampId } = useParams(); const navigate = useNavigate(); const [formData, setFormData] = useState({ title: '', - description: '', - weeks: '', + duration: '', tuition: '', - minimumSkill: 'beginner', + minimumSkill: 'Beginner (Any)', // Initialize to default + description: '', scholarshipAvailable: false, }); const [error, setError] = useState(null); - const { title, description, weeks, tuition, minimumSkill, scholarshipAvailable } = formData; + const { minimumSkill, scholarshipAvailable } = formData; const onChange = (e) => { const { name, value, type, checked } = e.target; @@ -51,104 +52,74 @@ const AddCoursePage = () => { return ( - - - - - - + + + + + + Manage Courses DevWorks Bootcamp Add Course - - - Course Title - - - - Course Description - + + Course Title + + + + + Duration + + Enter number of weeks course lasts + + + + Course Tuition + + USD Currency + + + + Minimum Skill Required + + Beginner (Any) + Intermediate + Advanced + + + + + - - - Duration (weeks) - - - - Tuition Cost ($) - - - - Minimum Skill Required - - Beginner - Intermediate - Advanced - - - - Scholarship Available - No more than 500 characters + + + + - - - - - - - - + + + + Add Course + + + + + + + ); }; diff --git a/ui/src/pages/user/ManageAccountPage.jsx b/ui/src/pages/user/ManageAccountPage.jsx index 9f12fa7..27c5bd7 100644 --- a/ui/src/pages/user/ManageAccountPage.jsx +++ b/ui/src/pages/user/ManageAccountPage.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Form, Button, Row, Col } from 'react-bootstrap'; +import { Form, Button, Row, Col, Card, Container } from 'react-bootstrap'; import userService from '../../services/userService'; const ManageAccountPage = () => { @@ -35,49 +35,52 @@ const ManageAccountPage = () => { }; const redirectToUpdatePassword = () => { - navigate('/update-password'); + navigate('/user/password/update'); }; return ( - - - - - Manage Account - - - Name - - - - Email - - - - - - - Save - - - - - Update Password - - - - - - - - - + + + + + + Manage Account + + + Name + + + + Email + + + + + + + Save + + + + + Update Password + + + + + + + + + + ); };