diff --git a/.env.example b/.env.example index 684ba4cd..e1270daf 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # env variables -# to create a secret or env variable, +# to create a secret or env variable, use the following format. ``` kubectl create secret generic NAME_OF_THE_SECRET_KEY --from-literal=KEY=VALUE_FOR_THE_KEY @@ -9,29 +9,25 @@ kubectl create secret generic NAME_OF_THE_SECRET_KEY --from-literal=KEY=VALUE_FO eg- To set jwt secret key, kubectl create secret generic jwt-secret --from-literal=JWT_SECRET_KEY=this-is-jwt-secret-for-devhive-32iqfhishf78 -# api prefix +# api prefix API_PREFIX = 'api prifix' eg:- /api/v1 # jwt - JWT_SECRET_KEY = your_jwt_secret_key JWT_REFRESH_SECRET_KEY = your_jwt_refresh_secret_key # twilio otp sending and verification - TWILIO_ACCOUNT_SID = your_twilio_sid TWILIO_AUTH_TOKEN = your_twilio_auth_token TWILIO_SERVICE_SID = your_twilio_service_sid # cloudinary image upload - CLOUDINARY_API_KEY = your_cloudinary_api_key CLOUDINARY_API_SECRET = your_cloudinary_api_secret CLOUDINARY_CLOUD_NAME = your_cloudinary_cloud_name -# mongo atlas utls(Not necessary) - +# mongo atlas utls(Not necessary if using mongodb stateful services in the './k8s/stateful' diractory) MONGO_URL_AUTH = authentication_db_url MONGO_URL_ADMIN = admin_db_url MONGO_URL_PROFILE = profile_db_url diff --git a/README.md b/README.md index 437dccad..457b834b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ### About - This is a Job Finding Application. -- This application is built using MERN stack and microservices architecture. Containerized each services to be deployed in kubernetes the cluster. +- This application is built using MERN stack and microservices architecture. Containerized each services to be deployed in kubernetes cluster. - This application have mainly 3 user roles, 1. admin 2. candidate diff --git a/admin/src/index.ts b/admin/src/index.ts index d9e71579..eef51223 100644 --- a/admin/src/index.ts +++ b/admin/src/index.ts @@ -31,14 +31,14 @@ const start = async () => { const jobDeletedEvent = new JobDeletedEventConsumer(kafkaClient); const paymentcreatedEvent = new PaymentcreatedEventConsumer(kafkaClient); - // await candidateProfileUpdatedEvent.subscribe(); - // await recruiterProfileUpdatedEvent.subscribe(); - // await jobCreatedEvent.subscribe(); - // await jobUpdatedEvent.subscribe(); - // await jobDeletedEvent.subscribe(); - // await userUpdatedEvent.subscribe(); - // await userCreatedEvent.subscribe(); - // await paymentcreatedEvent.subscribe(); + await candidateProfileUpdatedEvent.subscribe(); + await recruiterProfileUpdatedEvent.subscribe(); + await jobCreatedEvent.subscribe(); + await jobUpdatedEvent.subscribe(); + await jobDeletedEvent.subscribe(); + await userUpdatedEvent.subscribe(); + await userCreatedEvent.subscribe(); + await paymentcreatedEvent.subscribe(); app.listen(appConfig.PORT, () => { console.log(`admin Listening on port ${appConfig.PORT}....`); diff --git a/client/package.json b/client/package.json index d78aa1c4..ebec61fe 100644 --- a/client/package.json +++ b/client/package.json @@ -58,5 +58,3 @@ "vite": "^4.4.5" } } - - diff --git a/client/src/axios/apiMethods/jobs-service/jobs.ts b/client/src/axios/apiMethods/jobs-service/jobs.ts index b1faf2c6..204dbacf 100644 --- a/client/src/axios/apiMethods/jobs-service/jobs.ts +++ b/client/src/axios/apiMethods/jobs-service/jobs.ts @@ -20,8 +20,12 @@ export const filterJobsApi = async (filterData: IFilter, page: number, limit: nu }; // Candidate -export const getAJobCandidateApi = async (id: string): Promise => { - return await makeApiCall("get", jobApiUrlConfig.getAJobCandidateUrl(id)); +export const getAJobCandidateApi = async (jobId: string): Promise => { + return await makeApiCall("get", jobApiUrlConfig.getAJobCandidateUrl(jobId)); +}; + +export const searchJobsCandidateApi = async (searchKey: string, resourceType: string, page: number, limit: number): Promise => { + return await makeApiCall("get", jobApiUrlConfig.getSearchResultsUrl(searchKey, resourceType, page, limit)); }; export const candidateApplyJobApi = async (jobId: string): Promise => { @@ -46,10 +50,11 @@ export const getAnAppliedJobApi = async (jobApplicationId: string): Promise => { - return await makeApiCall("get", jobApiUrlConfig.getAllCandidateAppliedJobsUrl(candidateId, currentPage)); +export const getAllCandidateAppliedJobsApi = async ( page: number, limit: number): Promise => { + return await makeApiCall("get", jobApiUrlConfig.getAllCandidateAppliedJobsUrl(page, limit)); }; + export const changeJobApplicationStatusApi = async (jobApplicationId: string, jobApplicationStatus: any): Promise => { return await makeApiCall("post", jobApiUrlConfig.changeJobApplicationStatusUrl(jobApplicationId), jobApplicationStatus); }; diff --git a/client/src/axios/apiMethods/profile-service/recruiter.ts b/client/src/axios/apiMethods/profile-service/recruiter.ts index 30363ba3..73ee785b 100644 --- a/client/src/axios/apiMethods/profile-service/recruiter.ts +++ b/client/src/axios/apiMethods/profile-service/recruiter.ts @@ -17,6 +17,6 @@ export const getACandidateProfileApi = async (userId: string): Promise => { - return await makeApiCall("get", profileApiUrlConfig.getAllCandidatesProfilesUrl(page)); +export const getAllCandidatesProfilesApi = async (page: number, limit: number): Promise => { + return await makeApiCall("get", profileApiUrlConfig.getAllCandidatesProfilesUrl(page, limit)); }; diff --git a/client/src/components/shimmer/JobCardShimmerLandingPage.tsx b/client/src/components/shimmer/JobCardShimmerLandingPage.tsx deleted file mode 100644 index 12181195..00000000 --- a/client/src/components/shimmer/JobCardShimmerLandingPage.tsx +++ /dev/null @@ -1,22 +0,0 @@ -const JobCardShimmerLandingPage = () => { - return ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ); -} - -export default JobCardShimmerLandingPage; diff --git a/client/src/components/shimmer/dashboard/DashboardCardAdminShimmer.tsx b/client/src/components/shimmer/dashboard/DashboardCardAdminShimmer.tsx new file mode 100644 index 00000000..2e2adb26 --- /dev/null +++ b/client/src/components/shimmer/dashboard/DashboardCardAdminShimmer.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +const DashboardCardAdminShimmer: React.FC = () => { + return ( +
+
+ {/* Placeholder for icon or children */} +
+ +
+
+

+ +

+
+
+ ); +}; + +export default DashboardCardAdminShimmer; diff --git a/client/src/components/shimmer/job/JobCardShimmer.tsx b/client/src/components/shimmer/job/JobCardShimmer.tsx new file mode 100644 index 00000000..8c5c02e7 --- /dev/null +++ b/client/src/components/shimmer/job/JobCardShimmer.tsx @@ -0,0 +1,22 @@ +const JobCardShimmer = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} + +export default JobCardShimmer; diff --git a/client/src/components/shimmer/job/JobDetailsShimmer.tsx b/client/src/components/shimmer/job/JobDetailsShimmer.tsx new file mode 100644 index 00000000..319f46f1 --- /dev/null +++ b/client/src/components/shimmer/job/JobDetailsShimmer.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +// ShimmerLine Component +interface ShimmerLineProps { + width?: string; + height?: string; + className?: string; +} + +const ShimmerLine: React.FC = ({ width = "100%", height = "1.5rem", className = "" }) => { + return ( +
+ ); +}; + +// JobDetailsCardShimmer Component +interface ShimmerJobDetailsProps { + lines: number; +} + +const JobDetailsCardShimmer: React.FC = ({ lines }) => { + return ( +
+
+
+ + +
+
+ +
+ {/* Repeat ShimmerLine for job details */} + {Array.from({ length: lines }).map((_, index) => ( +
+ + +
+ ))} +
+
+ ); +}; + +export default JobDetailsCardShimmer; diff --git a/client/src/components/shimmer/PaymentPlanCardShimmer.tsx b/client/src/components/shimmer/payment/PaymentPlanCardShimmer.tsx similarity index 100% rename from client/src/components/shimmer/PaymentPlanCardShimmer.tsx rename to client/src/components/shimmer/payment/PaymentPlanCardShimmer.tsx diff --git a/client/src/components/shimmer/recruiter/CandidateCardShimmer.tsx b/client/src/components/shimmer/recruiter/CandidateCardShimmer.tsx new file mode 100644 index 00000000..3cd26e48 --- /dev/null +++ b/client/src/components/shimmer/recruiter/CandidateCardShimmer.tsx @@ -0,0 +1,23 @@ +import React from "react"; + +const CandidateCardShimmer: React.FC = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default CandidateCardShimmer; \ No newline at end of file diff --git a/client/src/components/shimmer/table/TableShimmer.tsx b/client/src/components/shimmer/table/TableShimmer.tsx new file mode 100644 index 00000000..7c55d683 --- /dev/null +++ b/client/src/components/shimmer/table/TableShimmer.tsx @@ -0,0 +1,20 @@ +const TableShimmer = ({ columnCount, rowCount }: { columnCount: number; rowCount: number }) => { + console.log(Array.from({ length: rowCount })); + console.log(Array.from({ length: columnCount })); + + return ( + + {Array.from({ length: columnCount }).map((_, rowIndex) => ( + + {Array.from({length:rowCount}).map((_: unknown, colIndex: number) => ( + +
+ + ))} + + ))} + + ); +}; + +export default TableShimmer; diff --git a/client/src/config/apiUrlsConfig/jobApiUrlConfig.ts b/client/src/config/apiUrlsConfig/jobApiUrlConfig.ts index ff19bd4b..23d63756 100644 --- a/client/src/config/apiUrlsConfig/jobApiUrlConfig.ts +++ b/client/src/config/apiUrlsConfig/jobApiUrlConfig.ts @@ -12,13 +12,15 @@ const jobApiUrlConfig = { // Candidate getAJobCandidateUrl: (id: string) => `${CANDIDATE_JOB_URL}/${id}`, + getSearchResultsUrl: (searchKey: string, resourceType: string, page: number, limit: number) => + `${CANDIDATE_JOB_URL}/search/${resourceType}/${page}/${limit}?searchKey=${searchKey}`, candidateApplyJobUrl: (jobId: string) => `${CANDIDATE_JOB_URL}/apply/${jobId}`, getAnAppliedJobUrl: (jobApplicationId: string) => `${CANDIDATE_JOB_URL}/application/${jobApplicationId}`, - getAllCandidateAppliedJobsUrl: (candidateId: string, currentPage: number) => - `${CANDIDATE_JOB_URL}/applied/${candidateId}/${currentPage}`, + getAllCandidateAppliedJobsUrl: (page: number, limit: number) => + `${CANDIDATE_JOB_URL}/applied/${page}/${limit}`, checkAppliedUrl: (jobId: string) => `${CANDIDATE_JOB_URL}/hasApplied/${jobId}`, diff --git a/client/src/config/apiUrlsConfig/profileApiUrlConfig.ts b/client/src/config/apiUrlsConfig/profileApiUrlConfig.ts index ecb8dedb..38bc2533 100644 --- a/client/src/config/apiUrlsConfig/profileApiUrlConfig.ts +++ b/client/src/config/apiUrlsConfig/profileApiUrlConfig.ts @@ -15,8 +15,8 @@ const profileApiUrlConfig = { // Recruiter getRecruiterProfileUrl: `${RECRUITER_PROFILE_URL}`, updategetRecruiterProfileUrl: `${RECRUITER_PROFILE_URL}`, - getAllCandidatesProfilesUrl: (page: number) => - `${RECRUITER_PROFILE_URL}/candidates/${page}`, + getAllCandidatesProfilesUrl: (page: number, limit: number) => + `${RECRUITER_PROFILE_URL}/candidates/${page}/${limit}`, getACandidateProfileUrl: (candidateId: string) => `${RECRUITER_PROFILE_URL}/candidate/${candidateId}`, }; diff --git a/client/src/pages/chat/ChatPage.tsx b/client/src/pages/chat/ChatPage.tsx index d4456a82..a59e5569 100644 --- a/client/src/pages/chat/ChatPage.tsx +++ b/client/src/pages/chat/ChatPage.tsx @@ -94,7 +94,7 @@ const ChatPage = () => { return () => { dispatch(clearChatRooms()); }; - }, [selectedChatRoom, selectedChatRoomMessages]); + }, [selectedChatRoom]); useEffect(() => { socket.on("chatNotification", () => { @@ -145,9 +145,9 @@ const ChatPage = () => { } }); - return () => { - socket.off("receiveMessage"); - }; + // return () => { + // socket.off("receiveMessage"); + // }; }, [selectedChatRoom, userData.id]); const sendMessage = (message: string) => { diff --git a/client/src/pages/dashboard/AdminDashBoard.tsx b/client/src/pages/dashboard/AdminDashBoard.tsx index bf3e8c73..289fc366 100644 --- a/client/src/pages/dashboard/AdminDashBoard.tsx +++ b/client/src/pages/dashboard/AdminDashBoard.tsx @@ -6,6 +6,7 @@ import { getGraphDataApi, } from "../../axios/apiMethods/admin-service/admin-dashboard"; import { formatCurrency } from "../../utils/currency-format"; +import DashboardCardAdminShimmer from "../../components/shimmer/dashboard/DashboardCardAdminShimmer"; const AdminDashBoard: React.FC = () => { const [data, setData] = useState({ @@ -40,92 +41,114 @@ const AdminDashBoard: React.FC = () => { <> {/* */}
- - + ) : ( + - - - - - - + + + + + )} + {!data.candidateCount ? ( + + ) : ( + - - - - - - - - + + + + + + + )} + {!data.jobCount ? ( + + ) : ( + + + - - - + + + + )} - - + ) : ( + - + + - - - + + + + )}
diff --git a/client/src/pages/job/JobDetailsPage.tsx b/client/src/pages/job/JobDetailsPage.tsx index 7ab58242..1e453afe 100644 --- a/client/src/pages/job/JobDetailsPage.tsx +++ b/client/src/pages/job/JobDetailsPage.tsx @@ -13,6 +13,7 @@ import { swal } from "../../utils/swal"; import { IJob } from "../../types/Job"; import { ROLES } from "../../utils/constants"; import { IResponse } from "../../types/api"; +import JobDetailsCardShimmer from "../../components/shimmer/job/JobDetailsShimmer"; function JobDetailsPage() { const [jobDetails, setJobDetails] = useState | null>(null); @@ -75,7 +76,6 @@ function JobDetailsPage() { `Yes, ${jobDetails?.isActive ? "close job" : "open job"}` ).then(async (result) => { if (result.isConfirmed) { - const job = await changeJobCloseStatusApi(jobId); if (job) { @@ -94,14 +94,20 @@ function JobDetailsPage() { navigate(-1); }; return ( - + <> + {!jobDetails ? ( + + ) : ( + + )} + ); } diff --git a/client/src/pages/job/candidate/AllJobsPage.tsx b/client/src/pages/job/candidate/AllJobsPage.tsx index c7e56b8b..d4a61a7e 100644 --- a/client/src/pages/job/candidate/AllJobsPage.tsx +++ b/client/src/pages/job/candidate/AllJobsPage.tsx @@ -1,19 +1,25 @@ import { useEffect, useState } from "react"; -import { getAllJobsApi } from "../../../axios/apiMethods/jobs-service/jobs"; +import { + getAllJobsApi, + searchJobsCandidateApi, +} from "../../../axios/apiMethods/jobs-service/jobs"; import { useNavigate } from "react-router-dom"; import Paginate from "../../../components/pagination/Paginate"; import JobCard from "../../../components/cards/JobCard"; import SearchBar from "../../../components/filterSearch/SearchBar"; import { IResponse } from "../../../types/api"; import { IJob } from "../../../types/Job"; -import { searchApi } from "../../../axios/apiMethods/admin-service/search"; import { SEARCH_RESOURCE_TYPES } from "../../../utils/constants"; +import JobCardShimmer from "../../../components/shimmer/job/JobCardShimmer"; + +const LIMIT: number = 2; function AllJobsPage() { const navigate = useNavigate(); const [currentPage, setCurrentPage] = useState(1); const [numberOfPages, setNumberOfPages] = useState(1); const [jobs, setJobs] = useState([]); + const [loading, setLoading] = useState(false); const [searchKey, setSearchKey] = useState(""); const handlePageChange = ({ selected }: { selected: number }) => { @@ -31,43 +37,56 @@ function AllJobsPage() { useEffect(() => { (async () => { - let allJobs: IResponse | null = null; - if (!searchKey) { - allJobs = await getAllJobsApi(currentPage); - } else { - allJobs = await searchApi( - searchKey, - SEARCH_RESOURCE_TYPES.JOBS, - currentPage, - 2 - ); - } + try { + let allJobs: IResponse | null = null; + setLoading(true); + if (!searchKey) { + allJobs = await getAllJobsApi(currentPage); + } else { + allJobs = await searchJobsCandidateApi( + searchKey, + SEARCH_RESOURCE_TYPES.JOBS, + currentPage, + LIMIT + ); + } - if (allJobs) { - setJobs(allJobs.data.jobs); - setNumberOfPages(allJobs.data.numberOfPages); + if (allJobs) { + setJobs(allJobs.data.jobs); + setNumberOfPages(allJobs.data.numberOfPages); + } + } finally { + setLoading(false); } })(); }, [currentPage, searchKey]); return ( -
-
+
+ {" "} + {/* Added padding for consistency */} +
+ {" "} + {/* Adjusted margin */}
{jobs.length > 0 ? ( <>
- {jobs.map((job: Partial) => ( - - ))} + {loading + ? Array.from({ length: LIMIT }).map((_, index) => ( + + )) + : jobs.map((job: Partial) => ( + + ))}
{numberOfPages > 1 && ( ([]); + const [loading, setLoading] = useState(false); + const [searchKey, setSearchKey] = useState(""); const navigate = useNavigate(); - - const candidateData = useSelector( - (store: RootState) => store.userReducer.authData - ); - + const handlePageChange = async ({ selected }: { selected: number }) => { setCurrentPage(selected + 1); }; + // Reset to page 1 when starting a new search + useEffect(() => { + setCurrentPage(1); + }, [searchKey]); + useEffect(() => { (async () => { - // dispatch(setLoading()); - if(candidateData){ - const response = await getAllCandidateAppliedJobsApi( - candidateData.id, - currentPage - ); - - setAppliedJobsData(response.data.appliedJobs); - setpageCount(response.data.numberOfPages); + let appliedJobs: IResponse | null = null; + setLoading(true); + + try { + if (!searchKey) { + appliedJobs = await getAllCandidateAppliedJobsApi( + currentPage, + LIMIT + ); + setAppliedJobsData(appliedJobs.data.appliedJobs); + } else { + appliedJobs = await searchJobsCandidateApi( + searchKey, + SEARCH_RESOURCE_TYPES.APPLIED_JOBS, + currentPage, + LIMIT + ); + setAppliedJobsData(appliedJobs.data.jobs); + } + setpageCount(appliedJobs.data.numberOfPages); + } finally { + setLoading(false); } })(); - }, [currentPage, candidateData]); + }, [currentPage, searchKey]); const viewApplicationDetails = async (jobId: string) => { navigate(`/candidate/application-details/${jobId}`); }; return ( - <> +
+
+

My Applied Jobs

+
{" "} + {/* Added padding for consistency */} +
+ {" "} + {/* Adjusted margin */} + {/* */} +
{appliedJobsData.length > 0 ? (
-
-

My Applied Jobs

-
- {appliedJobsData.map((job: any) => ( - - ))} + {loading + ? Array.from({ length: LIMIT }).map((_, index) => ( + + )) + : appliedJobsData.map((job: any) => ( + + ))}
-
- {pageCount > 1 && ( - - )} -
+ {pageCount > 1 && ( + + )}
) : (
@@ -76,7 +106,7 @@ function AppliedJobsPage() {
)} - +
); } diff --git a/client/src/pages/job/recruiter/AllAddedJobs.tsx b/client/src/pages/job/recruiter/AllAddedJobs.tsx index ce848e11..3904c9c7 100644 --- a/client/src/pages/job/recruiter/AllAddedJobs.tsx +++ b/client/src/pages/job/recruiter/AllAddedJobs.tsx @@ -9,21 +9,28 @@ import { IJob } from "../../../types/Job"; import { swal } from "../../../utils/swal"; import Table from "../../../components/table/Table"; import { IResponse } from "../../../types/api"; +import TableShimmer from "../../../components/shimmer/table/TableShimmer"; function AllAddedJobs() { const navigate = useNavigate(); const [jobsData, setJobsData] = useState([]); const [numberOfPages, setNumberOfPages] = useState(0); + const [loading, setLoading] = useState(false); const JOBS_PER_PAGE: number = 2; const fetchJobs = async (currentPage: number) => { - const jobsData: IResponse = await getAllRecruiterAddedJobsApi( - currentPage, - JOBS_PER_PAGE - ); - setJobsData(jobsData.data.jobs); - setNumberOfPages(jobsData.data.numberOfPages); + try { + setLoading(true) + const jobsData: IResponse = await getAllRecruiterAddedJobsApi( + currentPage, + JOBS_PER_PAGE + ); + setJobsData(jobsData.data.jobs); + setNumberOfPages(jobsData.data.numberOfPages); + } finally { + setLoading(false) + } }; useEffect(() => { @@ -127,7 +134,7 @@ function AllAddedJobs() {
- {jobsData.length > 0 ? ( + {loading?:jobsData.length > 0 ? (
store.jobReducer.currentPage ); + const loading: boolean = useSelector( + (store: RootState) => store.loading.isLoading + ); + const jobs: IJob[] = useSelector((store: RootState) => { return store.jobReducer.jobs; }); @@ -84,10 +89,6 @@ function LandingPage() { ]); setJobFieldsValues(jobFieldsValues.data); })(); - // return () => { - // dispatch(clearTotalNumberOfPages()); - // dispatch(clearCurrentPage()); - // }; }, []); const handleJobFilter = async () => { @@ -266,7 +267,11 @@ function LandingPage() { />
- {jobs && jobs.length > 0 ? ( + {loading ? ( + <> + {" "} + + ) : jobs && jobs.length > 0 ? ( jobs.map( (job: Partial) => job.isActive && ( diff --git a/client/src/pages/payment/PaymentPlans.tsx b/client/src/pages/payment/PaymentPlans.tsx index 5b742780..e4933acc 100644 --- a/client/src/pages/payment/PaymentPlans.tsx +++ b/client/src/pages/payment/PaymentPlans.tsx @@ -6,7 +6,7 @@ import { useSelector } from "react-redux"; import { loadStripe } from "@stripe/stripe-js"; import PaymentPlanCard from "../../components/cards/PaymentPlanCard"; import { getAllMembershipPlansByCandidateApi } from "../../axios/apiMethods/premium-plans-service/candidate"; -import PaymentPlanCardShimmer from "../../components/shimmer/PaymentPlanCardShimmer"; +import PaymentPlanCardShimmer from "../../components/shimmer/payment/PaymentPlanCardShimmer"; const PaymentPlans: React.FC = () => { const [membershipPlansData, setMembershipPlansData] = useState<[]>([]); diff --git a/client/src/pages/profile/candidate/CandidateProfilePage.tsx b/client/src/pages/profile/candidate/CandidateProfilePage.tsx index 8b2f0f3a..d5bfb021 100644 --- a/client/src/pages/profile/candidate/CandidateProfilePage.tsx +++ b/client/src/pages/profile/candidate/CandidateProfilePage.tsx @@ -114,7 +114,7 @@ const CandidateProfilePage: React.FC = () => { filename: selectedFile.name, url: downloadURL, }); - + if (response) { setCandidateProfileData({ ...candidateProfileData, @@ -153,9 +153,7 @@ const CandidateProfilePage: React.FC = () => { }; const handleSavePreferredJobs = async () => { - const response = await updateCandidatePreferredJobsApi( - preferredJobs - ); + const response = await updateCandidatePreferredJobsApi(preferredJobs); if (response.data) { hotToastMessage(response.message, "success"); @@ -167,9 +165,7 @@ const CandidateProfilePage: React.FC = () => { }; const handleSaveSkills = async () => { - const response = await updateCandidateSkillsApi( - skills - ); + const response = await updateCandidateSkillsApi(skills); if (response.data) { hotToastMessage(response.message, "success"); @@ -224,7 +220,10 @@ const CandidateProfilePage: React.FC = () => { {imgLoading && } {!imgLoading && ( )} @@ -341,85 +340,95 @@ const CandidateProfilePage: React.FC = () => {
- {isRecruiterUrl && !candidateProfileData?.resume?.filename ?"":
- {candidateProfileData?.resume && ( - <> - -
+ {candidateProfileData?.resume && ( + <> + +
- {candidateProfileData?.resume?.filename} - {!isRecruiterUrl && ( + > + { + candidateProfileData?.resume + ?.filename + } + {!isRecruiterUrl && ( +
+ +
+ )} +
- + + +
- )} +
+ + )} -
- + {!isRecruiterUrl && + (candidateProfileData?.resume + ? "Change Your Resume" + : "Upload Your Resume")} + +
+ {!isRecruiterUrl && ( +
+ + +
-
- - )} - - -
- {!isRecruiterUrl && ( -
- - - -
+ )} +
+ {selectedFile && ( +

+ Selected File: {selectedFile?.name} +

)}
- {selectedFile && ( -

- Selected File: {selectedFile?.name} -

- )} -
} + )}
{/* =============================================Skill start============================================= */} @@ -517,174 +526,182 @@ const CandidateProfilePage: React.FC = () => { {/* ====modal end ==== */} {/* Skills */} - {isRecruiterUrl && skills.length === 0 ?"":
-
-

- - Skills{" "} - - {/* The button to open modal */} - - {!isRecruiterUrl && ( - - )} - -

+ {isRecruiterUrl && skills.length === 0 ? ( + "" + ) : ( +
+
+

+ + Skills{" "} + + {/* The button to open modal */} + + {!isRecruiterUrl && ( + + )} + +

-
    - {/* Add more skills based on your data */} - {skills.length > 0 && - candidateProfileData?.skills.map( - (skill: string) => ( -
    -
  • {skill}
  • -
    - ) - )} -
+
    + {/* Add more skills based on your data */} + {skills.length > 0 && + candidateProfileData?.skills.map( + (skill: string) => ( +
    +
  • {skill}
  • +
    + ) + )} +
+
-
} + )} {/* ===============================================Skill end================================================== */} {/* =================================================Preferred jobs start================================================ */} {/* ====modal start ==== */} {/* Put this part before tag */} -{isRecruiterUrl && preferredJobs.length === 0 ?"":<> - - -
-
-

- Add your Preffered Jobs -

- -
- - -
-
    - {/* Add more skills based on your data */} - {preferredJobs.length > 0 && - candidateProfileData?.preferredJobs.map( - (preferredJob: string) => ( -
    -
  • {preferredJob}
  • - - { - const preferredJobsAfterRemove = - preferredJobs.filter( - ( - currentPreferredJob: string - ) => { - return ( - currentPreferredJob !== - preferredJob + {isRecruiterUrl && preferredJobs.length === 0 ? ( + "" + ) : ( + <> + + +
    +
    +

    + Add your Preffered Jobs +

    + +
    + + +
    +
      + {/* Add more skills based on your data */} + {preferredJobs.length > 0 && + candidateProfileData?.preferredJobs.map( + (preferredJob: string) => ( +
      +
    • {preferredJob}
    • + + { + const preferredJobsAfterRemove = + preferredJobs.filter( + ( + currentPreferredJob: string + ) => { + return ( + currentPreferredJob !== + preferredJob + ); + } ); - } - ); - setPreferredJobs( - preferredJobsAfterRemove - ); - }} + setPreferredJobs( + preferredJobsAfterRemove + ); + }} + > + + + +
      + ) + )} +
    +
    + + +
    +
    +
    + {/* ====modal end ==== */} + {/* Preferred jobs */} +
    +
    +

    + + Preferred jobs{" "} + + {/* The button to open modal */} + + {!isRecruiterUrl && ( + + )} + +

    + +
      + {/* Add more skills based on your data */} + {preferredJobs.length > 0 && + candidateProfileData?.preferredJobs.map( + (job: string) => ( +
      - - - -
      - ) - )} -
    -
    - - +
  • {job}
  • +
    + ) + )} +
+
-
- - {/* ====modal end ==== */} - {/* Preferred jobs */} -
-
-

- - Preferred jobs{" "} - - {/* The button to open modal */} - - {!isRecruiterUrl && ( - - )} - -

- -
    - {/* Add more skills based on your data */} - {preferredJobs.length > 0 && - candidateProfileData?.preferredJobs.map( - (job: string) => ( -
    -
  • {job}
  • -
    - ) - )} -
-
-
- } + + )} {/* =================================================Preferred jobs end================================================ */} diff --git a/client/src/pages/recruiter/ViewAllCandidatesPage.tsx b/client/src/pages/recruiter/ViewAllCandidatesPage.tsx index f4ca0785..2719606b 100644 --- a/client/src/pages/recruiter/ViewAllCandidatesPage.tsx +++ b/client/src/pages/recruiter/ViewAllCandidatesPage.tsx @@ -3,23 +3,19 @@ import { useNavigate } from "react-router-dom"; import CandidateCard from "../../components/cards/CandidateCard"; import { getAllCandidatesProfilesApi } from "../../axios/apiMethods/profile-service/recruiter"; import Paginate from "../../components/pagination/Paginate"; +import CandidateCardShimmer from "../../components/shimmer/recruiter/CandidateCardShimmer"; +import { IUserData } from "../../types/user"; -interface CandidateInterface { - id: string; - name: string; - email: string; - phone: string; - isActive: boolean; - userId: string; -} +const LIMIT: number = 2 function ViewAllCandidatesPage() { const [currentPage, setCurrentPage] = useState(1); const [pageCount, setpageCount] = useState(1); const [searchKey, setSearchKey] = useState(""); + const [loading, setLoading] = useState(false); const navigate = useNavigate(); - const [candidatesData, setCandidatesData] = useState( + const [candidatesData, setCandidatesData] = useState( [] ); @@ -29,17 +25,24 @@ function ViewAllCandidatesPage() { useEffect(() => { (async () => { - // dispatch(setLoading()); - const candidates = await getAllCandidatesProfilesApi(currentPage); + setLoading(true); + try { + const candidates = await getAllCandidatesProfilesApi( + currentPage, + LIMIT + ); - setCandidatesData(candidates.data.candidates); - setpageCount(candidates.data.totalNumberOfPages); + setCandidatesData(candidates.data.candidates); + setpageCount(candidates.data.totalNumberOfPages); + } finally { + setLoading(false); + } // dispatch(setLoaded()); })(); }, [currentPage]); - const filteredCandidates = candidatesData.filter((candidate: any) => + const filteredCandidates = candidatesData.filter((candidate: IUserData) => candidate.name.toLowerCase().includes(searchKey.toLowerCase()) ); @@ -63,7 +66,9 @@ function ViewAllCandidatesPage() { - {filteredCandidates.length <= 0 ? ( + {loading ? ( + Array.from({length: LIMIT}).map((_, index)=> ) + ) : filteredCandidates.length <= 0 ? (
No Candidates are registered yet
) : ( filteredCandidates.map((candidate) => ( diff --git a/client/src/utils/constants.ts b/client/src/utils/constants.ts index 13f1eb88..e97f7e73 100644 --- a/client/src/utils/constants.ts +++ b/client/src/utils/constants.ts @@ -24,6 +24,7 @@ export const SEARCH_RESOURCE_TYPES = Object.freeze({ CANDIDATE: 'candidate', RECRUITER: 'recruiter', JOBS: 'jobs', + APPLIED_JOBS: 'applied-jobs', PAYMENTS: 'payments', PLANS: 'palns', }); \ No newline at end of file diff --git a/job/src/controllers/index.ts b/job/src/controllers/index.ts index c42c1cf1..f381f9d2 100644 --- a/job/src/controllers/index.ts +++ b/job/src/controllers/index.ts @@ -1,5 +1,6 @@ import jobsControllers from './jobs'; import candidateJobControllers from './candidate'; import recruiterJobControllers from './recruiter'; +import searchControllers from './search'; -export { jobsControllers, candidateJobControllers, recruiterJobControllers }; +export { jobsControllers, candidateJobControllers, recruiterJobControllers, searchControllers }; diff --git a/job/src/controllers/jobs/index.ts b/job/src/controllers/jobs/index.ts index c437de0f..96282487 100644 --- a/job/src/controllers/jobs/index.ts +++ b/job/src/controllers/jobs/index.ts @@ -2,7 +2,6 @@ import filterJobsController from './filterJobs.controller'; import viewAllJobsController from './getJobs.controller'; import viewJobByJobIdController from './getJob.controller'; import viewAllJobFieldsDistinctValuesController from './viewDistinctFieldValues.controller'; -import searchJobsController from './search.controller'; import { IDependency } from '../../frameworks/types/dependency'; @@ -12,6 +11,5 @@ export = (dependencies: IDependency) => { viewAllJobsController: viewAllJobsController(dependencies), viewAllJobFieldsDistinctValuesController: viewAllJobFieldsDistinctValuesController(dependencies), viewJobByJobIdController: viewJobByJobIdController(dependencies), - searchJobsController: searchJobsController(dependencies), }; }; diff --git a/job/src/controllers/search/index.ts b/job/src/controllers/search/index.ts new file mode 100644 index 00000000..f9d3f557 --- /dev/null +++ b/job/src/controllers/search/index.ts @@ -0,0 +1,8 @@ +import searchController from "./search.controller" +import { IDependency } from '../../frameworks/types/dependency'; + +export = (dependencies: IDependency) => { + return { + searchController: searchController(dependencies), + }; +}; diff --git a/job/src/controllers/jobs/search.controller.ts b/job/src/controllers/search/search.controller.ts similarity index 59% rename from job/src/controllers/jobs/search.controller.ts rename to job/src/controllers/search/search.controller.ts index 530b2268..125311ad 100644 --- a/job/src/controllers/jobs/search.controller.ts +++ b/job/src/controllers/search/search.controller.ts @@ -3,17 +3,18 @@ import { IDependency } from '../../frameworks/types/dependency'; export = (dependencies: IDependency) => { const { - useCases: { getSearchResultUseCase }, + useCases: { searchUseCase }, } = dependencies; return async (req: Request, res: Response) => { + const { type: resourceType } = req.params; + const { searchKey } = req.query; - const { searchKey } = req.body; - - const { jobs, numberOfPages } = await getSearchResultUseCase(dependencies).execute( - searchKey, + const { jobs, numberOfPages } = await searchUseCase(dependencies).execute( + resourceType, + searchKey as string, Number(req.params.page) || 1, - Number(req.params.limit) || 2, + Number(req.params.limit) || 4, ); res.status(200).json({ diff --git a/job/src/frameworks/express/routes/candidate.ts b/job/src/frameworks/express/routes/candidate.ts index d13ee3c4..76c4d05b 100644 --- a/job/src/frameworks/express/routes/candidate.ts +++ b/job/src/frameworks/express/routes/candidate.ts @@ -1,7 +1,7 @@ import express from 'express'; import { auth, ROLES } from '@abijobportal/common'; -import { jobsControllers, candidateJobControllers } from '../../../controllers'; +import { jobsControllers, candidateJobControllers, searchControllers } from '../../../controllers'; import { IDependency } from '../../types/dependency'; export const candidateRouter = (dependencies: IDependency) => { @@ -9,26 +9,28 @@ export const candidateRouter = (dependencies: IDependency) => { const jobsController = jobsControllers(dependencies); const candidateJobController = candidateJobControllers(dependencies); - + const searchController = searchControllers(dependencies); + + router.get('/jobs/:page', jobsController.viewAllJobsController); router.post('/filter-bar-values', jobsController.viewAllJobFieldsDistinctValuesController); router.post('/filter/:page/:limit', jobsController.filterJobsController); - - router.post('/search/:page/:limit', jobsController.searchJobsController); router.use(auth(ROLES.CANDIDATE)); - + router.get('/:id', jobsController.viewJobByJobIdController); router.post('/apply/:jobId', candidateJobController.applyJobController); router.get('/application/:jobApplicationId', candidateJobController.getAppliedJobApplicationController); - router.get('/applied/:candidateId/:page', candidateJobController.appliedJobsController); + router.get('/applied/:page/:limit', candidateJobController.appliedJobsController); router.get('/hasApplied/:jobId', candidateJobController.checkAppliedController); + router.get('/search/:type/:page/:limit/', auth(ROLES.CANDIDATE), searchController.searchController); + return router; }; diff --git a/job/src/frameworks/express/routes/recruiter.ts b/job/src/frameworks/express/routes/recruiter.ts index a60ab366..e8c14d09 100644 --- a/job/src/frameworks/express/routes/recruiter.ts +++ b/job/src/frameworks/express/routes/recruiter.ts @@ -1,7 +1,7 @@ import express from 'express'; import { auth, ROLES } from '@abijobportal/common'; -import { jobsControllers, recruiterJobControllers } from '../../../controllers'; +import { jobsControllers, recruiterJobControllers, searchControllers } from '../../../controllers'; import { IDependency } from '../../types/dependency'; export const recruiterRouter = (dependencies: IDependency) => { @@ -9,6 +9,8 @@ export const recruiterRouter = (dependencies: IDependency) => { const jobsController = jobsControllers(dependencies); const recruiterJobController = recruiterJobControllers(dependencies); + const searchController = searchControllers(dependencies); + router.get('/jobs/:page', jobsController.viewAllJobsController); @@ -38,5 +40,7 @@ export const recruiterRouter = (dependencies: IDependency) => { router.get('/dashboard/graph-data', recruiterJobController.recruiterDashboardGraphController); + router.get('/search/:type/:page/:limit', searchController.searchController); + return router; }; diff --git a/job/src/frameworks/repositories/mongo/job.repository.ts b/job/src/frameworks/repositories/mongo/job.repository.ts index d5ec88ba..bd09926c 100644 --- a/job/src/frameworks/repositories/mongo/job.repository.ts +++ b/job/src/frameworks/repositories/mongo/job.repository.ts @@ -113,7 +113,7 @@ export = { return await JobModel.countDocuments({ recruiterId, isDeleted: false }); }, - getSearchResults: async ( + searchJob: async ( searchKey: string, skip: number, limit: number, @@ -130,7 +130,7 @@ export = { return searchedJobs; }, - getCountOfSearchResults: async (searchKey: string): Promise => { + searchJobCount: async (searchKey: string): Promise => { return await JobModel.countDocuments({ title: { $regex: new RegExp(searchKey, 'i') }, isDeleted: false, diff --git a/job/src/frameworks/repositories/mongo/jobApplication.repository.ts b/job/src/frameworks/repositories/mongo/jobApplication.repository.ts index 55e36e9f..afe0d3a4 100644 --- a/job/src/frameworks/repositories/mongo/jobApplication.repository.ts +++ b/job/src/frameworks/repositories/mongo/jobApplication.repository.ts @@ -4,6 +4,15 @@ import { IJobApplication } from '../../types/jobApplication'; const { jobApplicationModel } = Models; +const APPLIED_JOBS_SELECT_FIELDS: string[] = [ + '_id', + 'title', + 'companyLocation', + 'salaryMax', + 'employmentType', + 'createdAt', +]; + export = { applyJob: async (data: IJobApplication): Promise => { const newApplication = await jobApplicationModel.create(data); @@ -23,17 +32,9 @@ export = { skip: number, limit: number, ): Promise => { - // use populate const appliedJobs = await jobApplicationModel .find({ candidateId }) - .populate('jobId', [ - '_id', - 'title', - 'companyLocation', - 'salaryMax', - 'employmentType', - 'createdAt', - ]) + .populate('jobId', APPLIED_JOBS_SELECT_FIELDS) .select('jobId') .sort({ createdAt: -1 }) .skip(skip) @@ -42,6 +43,14 @@ export = { return appliedJobs; }, + getCountOfAppliedJobs: async (candidateId: string): Promise => { + const totalJobs: number = await jobApplicationModel.countDocuments({ + candidateId, + }); + + return totalJobs; + }, + getAllJobApplicationsByUserId: async ( recruiterId: string, candidateId: string, @@ -68,6 +77,14 @@ export = { return jobApplications; }, + getCountOfApplications: async (recruiterId: string): Promise => { + const totalJobs: number = await jobApplicationModel.countDocuments({ + recruiterId, + }); + + return totalJobs; + }, + updateJobApplicationStatus: async (id: string, status: object) => { const updatedJob = await jobApplicationModel.findOneAndUpdate( { _id: id }, @@ -108,30 +125,67 @@ export = { return jobApplications; }, - // Used by candidates - getCountOfAppliedJobs: async (candidateId: string): Promise => { + getCountOfApplicationsStatus: async (recruiterId: string, applicationStatus: string): Promise => { const totalJobs: number = await jobApplicationModel.countDocuments({ - candidateId, + recruiterId, + applicationStatus, }); return totalJobs; }, - // Used by recruiters - getCountOfApplications: async (recruiterId: string): Promise => { - const totalJobs: number = await jobApplicationModel.countDocuments({ - recruiterId, - }); - - return totalJobs; + searchAppliedJobs: async ( + searchKey: string, + skip: number, + limit: number, + ): Promise => { + console.log(searchKey); + + const searchedJobs: IJobApplicationDocument[] | [] = await jobApplicationModel + .aggregate([ + { + $lookup: { + from: 'jobs', + foreignField: '_id', + localField: 'jobId', + as: 'jobDetails', + }, + }, + { + $unwind: '$jobDetails', // Unwind to access individual job fields + }, + + { + $match: { + 'jobDetails.title': { $regex: searchKey, $options: 'i' }, // Case-insensitive regex match + }, + }, + { + $project: { + jobId: 1, + candidateId: 1, + recruiterId: 1, + applicationStatus: 1, + 'jobDetails.title': 1, // Include the title from Job details + }, + }, + // { + // $match: { + // title: { + // $regex: new RegExp(searchKey, 'i'), + // }, + // }, + // }, + ]) + .skip(skip) + .limit(limit); + return searchedJobs; }, - getCountOfApplicationsStatus: async (recruiterId: string, applicationStatus: string): Promise => { - const totalJobs: number = await jobApplicationModel.countDocuments({ - recruiterId, - applicationStatus, + searchAppliedJobsCount: async (searchKey: string): Promise => { + return await jobApplicationModel.countDocuments({ + title: { $regex: new RegExp(searchKey, 'i') }, + isDeleted: false, }); - - return totalJobs; }, }; diff --git a/job/src/index.ts b/job/src/index.ts index cb21f5e7..befa7eb6 100644 --- a/job/src/index.ts +++ b/job/src/index.ts @@ -22,9 +22,9 @@ const start = async () => { const userUpdatedEvent = new UserUpdatedEventConsumer(kafkaClient); const jobUpdatedEvent = new jobUpdatedEventConsumer(kafkaClient); - await userUpdatedEvent.subscribe(); - await userCreatedEvent.subscribe(); - await jobUpdatedEvent.subscribe(); + // await userUpdatedEvent.subscribe(); + // await userCreatedEvent.subscribe(); + // await jobUpdatedEvent.subscribe(); app.listen(appConfig.PORT, () => { console.log(`job service Listening on port ${appConfig.PORT}....`); diff --git a/job/src/useCases/job/index.ts b/job/src/useCases/job/index.ts index 63fccfe1..567b4cf6 100644 --- a/job/src/useCases/job/index.ts +++ b/job/src/useCases/job/index.ts @@ -2,12 +2,12 @@ import getAllJobsUseCase from './getJobs'; import filterJobUseCase from './filterJob'; import getJobByIdUseCase from './getJobById'; import getAllJobFieldsDistinctValuesUseCase from './getJobFieldsDistinctValues'; -import getSearchResultUseCase from './search'; +import searchUseCase from './search'; export { getAllJobsUseCase, filterJobUseCase, getJobByIdUseCase, getAllJobFieldsDistinctValuesUseCase, - getSearchResultUseCase, + searchUseCase, }; diff --git a/job/src/useCases/job/search.ts b/job/src/useCases/job/search.ts index 3f97896a..5567ff11 100644 --- a/job/src/useCases/job/search.ts +++ b/job/src/useCases/job/search.ts @@ -1,23 +1,46 @@ +import { BadRequestError } from '@abijobportal/common'; import { IDependency } from '../../frameworks/types/dependency'; +const SEARCH_RESOURCE_TYPES = Object.freeze({ + APPLIED_JOBS: 'applied-jobs', + JOBS: 'jobs', +}); + export = (dependencies: IDependency) => { const { - repositories: { jobRepository }, + repositories: { jobRepository, jobApplicationRepository }, } = dependencies; + + const execute = async (resourceType: string, searchKey: string, page: number, limit: number) => { + // pagination + const skip = (page - 1) * limit; + + let jobs; + let count: number = 0; - if (!jobRepository) { - throw new Error('jobRepository should exist in dependencies'); - } + switch (resourceType) { + case SEARCH_RESOURCE_TYPES.JOBS: { + jobs = await jobRepository.searchJob(searchKey.trim(), skip, limit); + count = await jobRepository.searchJobCount(searchKey); + + break; + } + + case SEARCH_RESOURCE_TYPES.APPLIED_JOBS: { + jobs = await jobApplicationRepository.searchAppliedJobs(searchKey.trim(), skip, limit); + count = await jobApplicationRepository.searchAppliedJobsCount(searchKey); + console.log("adf",jobs); + + break; + } - const execute = async (searchText: string, page: number, limit: number) => { - // pagination - const skip = (page - 1) * limit; - const searchResult = await jobRepository.getSearchResults(searchText.trim(), skip, limit); + default: + throw new BadRequestError('Invalid resoutce type'); + } // It is used to get the total number of pages - const searchResultCount = await jobRepository.getCountOfSearchResults(searchText); - const numberOfPages = Math.ceil(searchResultCount / limit); - return { jobs: searchResult, numberOfPages }; + const numberOfPages = Math.ceil(count / limit); + return { jobs, numberOfPages }; }; return { execute }; diff --git a/k8s/README.md b/k8s/README.md index 13ac23ee..842b4706 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -1,80 +1,80 @@ # devHive setup ---- -
## Getting Started +This guide will help you set up and run the devHive application locally on your system. Follow the steps below based on your operating system. + ## Prerequisites To Run Locally: -- in ubuntu, +***in ubuntu***, 1. install docker (refer any documents) 2. install minikube (refer any documents) -- start minikube if already installed -``` - minikube start -``` -3. enable ingress, +- enable ingress, ``` minikube addons enable ingress ``` -4. enable dashboard (optional), + +- enable dashboard (optional) ``` minikube addons enable dashboard ``` -5. [Install Skaffold](https://skaffold.dev/docs/install/) (optional) + +3. Install kubectl +4. [Install Skaffold](https://skaffold.dev/docs/install/) (optional) -- in windows, +***in windows***, 1. [install Docker Desktop](https://docs.docker.com/get-docker/) -2. Enable Kubernetes in the Docker Desktop -3. [Install Ingress Nginx](https://kubernetes.github.io/ingress-nginx/deploy/) +- Download and install Docker Desktop from Docker's official website. +- Enable Kubernetes in Docker Desktop settings. +2. Install kubectl +3. Install skaffold +4. [Install Ingress Nginx using helm](https://kubernetes.github.io/ingress-nginx/deploy/) ## steps to start application -1. start minikube +1. Create the required secrets(env's) [check the .env.example file](https://github.com/iam-abin/devHive/blob/master/.env.example) + +2. start minikube ``` minikube start ``` +- To check minikube is running +``` +minikube status +``` -2. Create the required secrets(env's) (example) - -[.env.example](https://github.com/iam-abin/devHive/blob/master/.env.example) - -3. To apply configuration of deployment, services and pods +3. To apply the configurations of deployment, services and pods -- go to root folder -1) -### for ingress deployments +- Go to root folder +**For ingress deployments** ``` kubectl apply -f k8s/ingress/dev/ingress-srv.yaml ``` -2) -### for stateful deployments +**For stateful deployments (data that should persist)** -#### imp:- +#### Important: -- if using mongodb atlas not necessary to apply the mongodb-srv yaml files inside the stateful folder, - But we need to apply the configuration file for kafka(). -### for kafka deployments +- If using mongodb atlas, it's not necessary to apply the mongodb-deployment yaml files inside the stateful folder. +- But we need to apply the configuration file for kafka (message broker). +#### for kafka deployments ``` kubectl apply -f k8s/stateful/kafka-deployment.yaml ``` - - if not using mongodb atlas ``` kubectl apply -f k8s/stateful/ ``` -3) -### for stateless deployments + +**For stateless deployments (servers)** ``` kubectl apply -f ./k8s/stateless ``` -4. -## If Skaffold is installed run, +4. If Skaffold is installed run ``` skaffold dev @@ -82,7 +82,7 @@ skaffold dev --- -## last +## last steps for ubuntu ### get minikube ip, @@ -91,11 +91,12 @@ minikube ip ``` ### Setup /etc/hosts +- Open '/etc/hosts' file in nano editor ``` -sudo nano /etc/hosts - to open '/etc/hosts' file in nano editor +sudo nano /etc/hosts ``` -- add at last, +- Add at last, ``` 192.168.49.2 devhive.dev @@ -111,14 +112,11 @@ ctrl x - to exit nano editor ### Open your browser and type ``` -https://ticketing.dev +https://devhive.dev ``` - to stop minikube context or cluster -``` -minikube stop -``` ### optional: To open minikube minikube dashboard to see kubernetes cluster information @@ -177,7 +175,7 @@ kubectl delete pods --all ### to bild an image for a service ``` -docker build -t your_image_name:tag . +docker build -t : . ``` here, - -t: Specifies the name and optionally a tag to the Docker image. @@ -204,10 +202,10 @@ docker login 2. After logging in, you can push the image: ``` -docker push your_image_name:tag +docker push : ``` -eg:- docker push abin12334324/auth +eg:- docker push abin12334324/auth:latest # in production diff --git a/profile/src/frameworks/express/routes/recruiter.ts b/profile/src/frameworks/express/routes/recruiter.ts index a2645f45..0c8a905e 100644 --- a/profile/src/frameworks/express/routes/recruiter.ts +++ b/profile/src/frameworks/express/routes/recruiter.ts @@ -17,7 +17,7 @@ export const recruiterRouter = (dependencies: IDependency) => { router.get('/', recruiterProfileController.viewRecruiterProfileController); router.patch('/', recruiterProfileController.updateRecruiterProfileController); router.get('/candidate/:candidateId', candidateProfileController.viewCandidateProfileController); - router.get('/candidates/:page', recruiterProfileController.viewAllCandidatesProfilesController); + router.get('/candidates/:page/:limit', recruiterProfileController.viewAllCandidatesProfilesController); return router; };