diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1af553e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules +npm-debug.log* + +# Environment variables +.env* + +# IDE files +.vscode +.idea + +# OS files +.DS_Store +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore + +# Logs +logs +*.log + +# Coverage +coverage + +# Temporary files +tmp +temp \ No newline at end of file diff --git a/.github/workflows/build-lint.yml b/.github/workflows/build-lint.yml new file mode 100644 index 0000000..cf78682 --- /dev/null +++ b/.github/workflows/build-lint.yml @@ -0,0 +1,36 @@ +name: Build & lint workflow +on: [push, pull_request] +run-name: Workflow created by ${{github.actor}} on ${{github.event_name}} event + +jobs: + check_lint_and_build: + runs-on: ubuntu-latest + steps: + - name: action checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: | + backend/package-lock.json + frontend/package-lock.json + + - name: check lint in backend + working-directory: backend + run: | + npm install + npm run lint --max-warnings=0 + echo ::notice::Backend linting passed + + - name: check lint and build in frontend + working-directory: frontend + run: | + npm install + npm run lint --max-warnings=0 + npm run build + echo ::notice::Frontend linting and build passed diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..7fd24ae --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,20 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package.json package-lock.json ./ + +RUN npm ci + +RUN mkdir -p public/uploads + +COPY backend ./ + +EXPOSE 3000 + +# Start the backend server +CMD ["npm", "run", "dev"] + + +# Build image with: +#docker build -t careerly-backend-image -f backend/Dockerfile . \ No newline at end of file diff --git a/backend/controllers/authControllers.js b/backend/controllers/authControllers.js index 1879c31..33d3e94 100644 --- a/backend/controllers/authControllers.js +++ b/backend/controllers/authControllers.js @@ -1,5 +1,4 @@ const { StatusCodes } = require("http-status-codes"); -const Job = require("../models/JobModel"); const User = require("../models/UserModel"); const generateCookie = require("../utils/generateCookie"); diff --git a/backend/data/populate.js b/backend/data/populate.js index 007363f..83dca5c 100644 --- a/backend/data/populate.js +++ b/backend/data/populate.js @@ -1,3 +1,6 @@ +/* eslint-env node */ +/* global process */ + const mockData = require("./mockData"); const Job = require("../models/JobModel"); require("dotenv").config(); diff --git a/backend/index.js b/backend/index.js index da4faf6..90c50dd 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,3 +1,5 @@ +/* eslint-env node */ +/* global process, __dirname, */ const express = require("express"); const morgan = require("morgan"); const cookieParser = require("cookie-parser"); @@ -29,8 +31,8 @@ app.use("/api/v1/jobs", jobsRouter); app.use("/api/v1/auth", authRouter); app.use("/api/v1/users", userRouter); -app.use((err, req, res, next) => { - console.log(chalk.red(err)); +app.use((err, req, res) => { + console.log(err); res .status(StatusCodes.INTERNAL_SERVER_ERROR) .json({ msg: `Error: ${err.message}` }); diff --git a/backend/middlewares/authMiddleware.js b/backend/middlewares/authMiddleware.js index d3428b5..179c215 100644 --- a/backend/middlewares/authMiddleware.js +++ b/backend/middlewares/authMiddleware.js @@ -1,3 +1,5 @@ +/* global process */ + const { StatusCodes } = require("http-status-codes"); const jwt = require("jsonwebtoken"); const User = require("../models/UserModel"); @@ -15,7 +17,7 @@ const protect = async (req, res, next) => { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; - isTestUser = req.user.userId === "684214b2e2b9a4904b708c9b"; + const isTestUser = req.user.userId === "684214b2e2b9a4904b708c9b"; const activeUser = await User.findById(req.user.userId).select("-password"); if (!activeUser) { return res diff --git a/backend/utils/generateCookie.js b/backend/utils/generateCookie.js index 243d4e4..094c74d 100644 --- a/backend/utils/generateCookie.js +++ b/backend/utils/generateCookie.js @@ -1,3 +1,5 @@ +/* global process*/ + const jwt = require("jsonwebtoken"); const generateCookie = (user, res) => { diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..c4becae --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,36 @@ +services: + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "5174:5173" + volumes: + - ./frontend:/app + - frontend-node-modules:/app/node_modules + environment: + - CHOKIDAR_USEPOLLING=true + + depends_on: + - backend + + restart: unless-stopped + + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "3001:3000" + volumes: + - ./backend:/app + - backend-node-node_modules:/app/node_modules + environment: + - NODE_ENV=development + env_file: + - .env + restart: unless-stopped + +volumes: + backend-node-modules: + frontend-node-modules: diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..1b920ae --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,8 @@ +import js from "@eslint/js"; +import globals from "globals"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { files: ["**/*.{js,mjs,cjs}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } }, + { files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } }, +]); diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..5ff8412 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package.json package-lock.json ./ + +RUN npm ci + +COPY . ./ + +EXPOSE 5173 + + +CMD ["npm", "run", "dev", "--", "--host"] + + +# Build image with: +#docker build -t careerly-frontend-image . \ No newline at end of file diff --git a/frontend/src/components/BigSidebar.jsx b/frontend/src/components/BigSidebar.jsx index 1068e92..69d3559 100644 --- a/frontend/src/components/BigSidebar.jsx +++ b/frontend/src/components/BigSidebar.jsx @@ -3,10 +3,10 @@ import styled from "styled-components"; import { Link } from "react-router"; import { FiPlusCircle, FiList, FiBarChart2, FiUser } from "react-icons/fi"; import { LuLogOut } from "react-icons/lu"; -import { useDashboardContext } from "../pages/DashboardLayout"; +import { useDashboardContext } from "../hooks/useDashboardContext"; const BigSidebar = () => { - const { user, setIsSidebarOpen } = useDashboardContext(); + const { setIsSidebarOpen } = useDashboardContext(); return ( setIsSidebarOpen(false)}>
{ const { isSidebarOpen, setIsSidebarOpen } = useDashboardContext(); diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 9862b68..2c2d4f0 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -4,7 +4,7 @@ import Switch from "./Switch"; import styled from "styled-components"; import img from "../assets/images/user-pic.jpg"; import Burger from "./Burger"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; const Navbar = () => { const { data: user } = useQuery({ diff --git a/frontend/src/components/Switch.jsx b/frontend/src/components/Switch.jsx index bc62768..c363f8c 100644 --- a/frontend/src/components/Switch.jsx +++ b/frontend/src/components/Switch.jsx @@ -1,6 +1,6 @@ import React from "react"; import styled from "styled-components"; -import { useDashboardContext } from "../pages/DashboardLayout"; +import { useDashboardContext } from "../hooks/useDashboardContext"; const Switch = () => { const { toggleDarkTheme, isDarkTheme } = useDashboardContext(); diff --git a/frontend/src/contexts/DashboardContext.jsx b/frontend/src/contexts/DashboardContext.jsx new file mode 100644 index 0000000..625d275 --- /dev/null +++ b/frontend/src/contexts/DashboardContext.jsx @@ -0,0 +1,5 @@ +import { createContext } from "react"; + +const DashboardContext = createContext(); + +export default DashboardContext; diff --git a/frontend/src/hooks/useDashboardContext.js b/frontend/src/hooks/useDashboardContext.js new file mode 100644 index 0000000..34c33ec --- /dev/null +++ b/frontend/src/hooks/useDashboardContext.js @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import DashboardContext from "../contexts/DashboardContext"; + +export const useDashboardContext = () => { + return useContext(DashboardContext); +}; diff --git a/frontend/src/pages/AllJobs.jsx b/frontend/src/pages/AllJobs.jsx index 4374e67..be7d628 100644 --- a/frontend/src/pages/AllJobs.jsx +++ b/frontend/src/pages/AllJobs.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import styled from "styled-components"; import { diff --git a/frontend/src/pages/DashboardLayout.jsx b/frontend/src/pages/DashboardLayout.jsx index e2583ed..5ee0090 100644 --- a/frontend/src/pages/DashboardLayout.jsx +++ b/frontend/src/pages/DashboardLayout.jsx @@ -2,11 +2,10 @@ import React from "react"; import Navbar from "../components/Navbar"; import { Outlet } from "react-router"; import Sidebar from "../components/Sidebar"; -import { useState, createContext, useContext } from "react"; +import { useState } from "react"; import BigSidebar from "../components/BigSidebar"; import styled from "styled-components"; - -const DashboardContext = createContext(); +import DashboardContext from "../contexts/DashboardContext"; const DashboardLayout = ({ currentTheme }) => { const [isSidebarOpen, setIsSidebarOpen] = useState(false); const user = { @@ -52,8 +51,4 @@ const Wrapper = styled.div` } `; -export const useDashboardContext = () => { - return useContext(DashboardContext); -}; - export default DashboardLayout; diff --git a/frontend/src/pages/Stats.jsx b/frontend/src/pages/Stats.jsx index e2dc12d..1123eed 100644 --- a/frontend/src/pages/Stats.jsx +++ b/frontend/src/pages/Stats.jsx @@ -1,13 +1,12 @@ import React from "react"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import axiosInstance from "../utils/axios"; import DoughnutChart from "../components/DoughnutChart"; import LineChart from "../components/LineChart"; import styled from "styled-components"; const Stats = () => { - const queryClient = useQueryClient(); - const { data, isLoading } = useQuery({ + const { data } = useQuery({ queryKey: ["chartData"], queryFn: async () => { const response = await axiosInstance.get("/users/stats"); @@ -16,7 +15,7 @@ const Stats = () => { }); const { defaultStats, monthlyApplications } = data || {}; - console.log(data); + return (
diff --git a/package.json b/package.json index d555259..80e57c2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "nodemon backend/index.js", - "setup-project": "npm i && cd frontend && npm i" + "setup-project": "npm i && cd frontend && npm i", + "lint": "eslint backend" }, "author": "", "license": "ISC", @@ -29,6 +30,9 @@ "nanoid": "^5.1.5" }, "devDependencies": { + "@eslint/js": "^9.36.0", + "eslint": "^9.36.0", + "globals": "^16.4.0", "nodemon": "^3.1.10" } }