Skip to content

Backend Services ​

All business logic lives in the /src/services/ directory. Services are static classes that interact with the database via Prisma.

AuthService ​

File: auth.service.ts

Handles user and employer authentication, registration, and token management.

Methods ​

MethodParametersReturnsDescription
registerUseruserData: IUserRegister{ user, accessToken, refreshToken }Register new job seeker
registerEmployeremployerData: IEmployerRegister{ employer, accessToken, refreshToken }Register new employer
loginUserphone: string, password: string{ user, accessToken, refreshToken }User login
loginEmployeremail: string, password: string{ employer, accessToken, refreshToken }Employer login
refreshTokenrefreshToken: string, userType: 'user' | 'employer'{ accessToken, refreshToken }Refresh JWT tokens
logoutid: string, type: 'user' | 'employer'voidClear refresh token

Password Hashing ​

Uses bcrypt with 10 salt rounds:

typescript
const hashedPassword = await bcrypt.hash(password, 10);
const isValid = await bcrypt.compare(password, user.password);

JobService ​

File: job.service.ts

Manages job listings CRUD and search functionality.

Methods ​

MethodParametersReturnsDescription
createJobjobData: IJobCreateJobCreate new job posting
getJobByIdjobId: stringJob | nullGet job with employer info
getJobsskip, limit, filters?{ jobs, total }Paginated job list
updateJobjobId, updateDataJobUpdate job details
deleteJobjobId: stringbooleanDelete job
incrementApplicationCountjobId: stringvoidBump application counter
incrementSaveCountjobId: stringvoidBump save counter
incrementShareCountjobId: stringvoidBump share counter
searchJobssearchTerm, skip, limit{ jobs, total }Full-text search

Uses PostgreSQL tsvector for search:

typescript
static async searchJobs(searchTerm: string, skip: number, limit: number) {
  const jobs = await prisma.$queryRaw`
    SELECT *, ts_rank(search_vector, plainto_tsquery('english', ${searchTerm})) AS rank
    FROM jobs
    WHERE search_vector @@ plainto_tsquery('english', ${searchTerm})
      AND "isActive" = true
    ORDER BY rank DESC
    OFFSET ${skip} LIMIT ${limit}
  `;
  return { jobs, total };
}

ApplicationService ​

File: application.service.ts

Handles job applications lifecycle.

Methods ​

MethodParametersReturnsDescription
createApplicationapplicationDataApplicationSubmit application
getApplicationByIdapplicationIdApplicationGet with relations
getApplicationsByUseruserId, skip, limit{ applications, total }User's applications
getApplicationsByEmployeremployerId, skip, limit, status?{ applications, total }Employer's received apps
updateApplicationStatusapplicationId, updateDataApplicationChange status
deleteApplicationapplicationIdbooleanRemove application

Auto-Interview Invitation ​

When creating an application, checks if job has AI interviews enabled:

typescript
const application = await prisma.application.create({ data: {...} });
await JobService.incrementApplicationCount(jobId);
// Auto-invite to AI interview if enabled
await InterviewService.autoInviteIfEnabled(application.id);

UserService ​

File: user.service.ts

Manages user profiles, swipe history, and saved jobs.

Methods ​

MethodParametersReturnsDescription
getUserByIduserIdUserGet user with education/experience
updateUseruserId, updateDataUserUpdate profile
addSwipeHistoryuserId, { jobId, action }UserRecord swipe
saveJobuserId, jobIdUserSave job to favorites
unsaveJobuserId, jobIdUserRemove from favorites
getSavedJobsuserId, skip, limit{ jobs, total }Get saved jobs
addEducationuserId, educationEducationAdd education entry
updateEducationeducationId, dataEducationUpdate education
deleteEducationeducationIdbooleanRemove education
addExperienceuserId, experienceExperienceAdd work experience
updateExperienceexperienceId, dataExperienceUpdate experience
deleteExperienceexperienceIdbooleanRemove experience
checkProfileCompletenessuserIdProfileCompletenessResultProfile completion %

Profile Completeness ​

Checks required fields for a complete profile:

typescript
static async checkProfileCompleteness(userId: string) {
  // Required: name, location, bio, profilePicture, education (1+), experience (1+)
  const missingFields: string[] = [];
  // ... check each field
  return {
    isComplete: missingFields.length === 0,
    completionPercentage: Math.round((completedCount / 6) * 100),
    missingFields
  };
}

EmployerService ​

File: employer.service.ts

Manages employer profiles and statistics.

Methods ​

MethodParametersReturnsDescription
getEmployerByIdemployerIdEmployerGet employer profile
updateEmployeremployerId, updateDataEmployerUpdate profile
incrementJobsPostedemployerIdvoidBump job counters
decrementActiveJobsemployerIdvoidDecrement active jobs
verifyEmployeremployerIdEmployerMark as verified
getEmployersskip, limit, filters?{ employers, total }List employers
deleteEmployeremployerIdbooleanRemove employer

ServicesService ​

File: services.service.ts

Manages service worker profiles, TikTok-style feed, and swiping.

Methods ​

MethodParametersReturnsDescription
createWorkerProfileinputServiceWorkerProfileCreate worker profile
getWorkerProfileByUserIduserIdServiceWorkerProfileGet with categories & reviews
updateWorkerProfileuserId, dataServiceWorkerProfileUpdate profile
toggleAvailabilityuserIdServiceWorkerProfileToggle availability status
getNearbyWorkersquery: NearbyWorkersQuery{ workers, total }GPS-based search
getWorkerFeedquery: WorkerFeedQuery{ workers, total }TikTok-style feed
swipeWorkeruserId, workerId, actionServiceSwipeHistoryRecord client swipe on worker
swipeRequestworkerId, requestId, actionRequestSwipeHistoryRecord worker swipe on request
getWorkerLikesworkerIdswipes[]Get who liked this worker
getCategoriesparentId?ServiceCategory[]Get service categories
createCategoryname, parentId?, description?, icon?ServiceCategoryCreate category
searchWorkersquery, lat?, lng?, page, limit{ workers, total }Search workers

Haversine Distance Calculation ​

GPS-based nearby workers use the Haversine formula:

typescript
const distanceFormula = Prisma.sql`
  (6371 * acos(
    cos(radians(${latitude})) * cos(radians(swp.latitude)) *
    cos(radians(swp.longitude) - radians(${longitude})) +
    sin(radians(${latitude})) * sin(radians(swp.latitude))
  ))
`;

BookingsService ​

File: bookings.service.ts

Handles service requests, quotes, and bookings lifecycle.

Methods ​

MethodParametersReturnsDescription
createRequestinput: CreateServiceRequestInputServiceRequestClient posts service need
getRequestByIdrequestIdServiceRequestGet with quotes
getClientRequestsclientId, status?, page, limit{ requests, total }Client's requests
getNearbyRequestsworkerId, categoryIds, lat, lng, radius, page, limit{ requests, total }Requests near worker
updateRequestStatusrequestId, statusServiceRequestUpdate request status
createQuoteinput: CreateQuoteInputQuoteWorker submits quote
getQuotesForRequestrequestIdQuote[]Get all quotes
acceptQuotequoteId, clientIdServiceBookingAccept quote → create booking
rejectQuotequoteId, clientIdQuoteReject quote
createBookinginput: CreateBookingInputServiceBookingDirect booking
getBookingByIdbookingIdServiceBookingGet booking details
getClientBookingsclientId, status?, page, limit{ bookings, total }Client's bookings
getWorkerBookingsworkerId, status?, page, limit{ bookings, total }Worker's bookings
updateBookingStatusbookingId, status, userIdServiceBookingUpdate with auth check

Booking Status Flow ​

pending → accepted → in_progress → completed
       ↘ declined    ↘ cancelled  ↘ disputed

MessagesService ​

File: messages.service.ts

Handles the messaging system for job inquiries and booking discussions.

Methods ​

MethodParametersReturnsDescription
createConversationForApplicationapplicationId, employerId, applicantIdConversationStart job inquiry chat
createConversationForQuotequoteId, workerId, clientIdConversationStart booking inquiry chat
createConversationForBookingbookingId, workerId, clientIdConversationDirect booking chat
getUserConversationsuserId, page, limit, includeArchived{ conversations, total }User's conversations
getConversationconversationId, userIdConversationGet with messages
archiveConversationconversationId, userIdConversationParticipantArchive for user
unarchiveConversationconversationId, userIdConversationParticipantUnarchive
sendMessageinput: SendMessageInputMessageSend message
getMessagesconversationId, userId, page, limit, before?{ messages, total }Get messages
markAsReadconversationId, userIdbooleanMark messages read
deleteMessagemessageId, userIdMessageSoft delete
updateTypingStatusconversationId, userId, isTypingbooleanUpdate typing indicator
getTypingUsersconversationId, excludeUserId{ id, name }[]Get who's typing
getTotalUnreadCountuserIdnumberTotal unread messages
togglePinConversationconversationId, userIdConversationParticipantPin/unpin
toggleMuteConversationconversationId, userIdConversationParticipantMute/unmute

WhatsApp Notifications ​

Sends SMS/WhatsApp when recipient is offline:

typescript
static async sendWhatsAppNotification(phoneNumber, senderName, messagePreview) {
  await fetch(`${NOTIFICATION_SERVICE_URL}/send`, {
    method: 'POST',
    headers: { 'X-API-KEY': NOTIFICATION_SERVICE_API_KEY },
    body: JSON.stringify({
      messageType: 'transactional',
      phoneNumber,
      message: `${senderName}: ${messagePreview}. Open YeboJobs to reply.`,
      channel: 'sms',
    }),
  });
}

InterviewService ​

File: interview.service.ts

Integrates with Okia AI service for candidate interviews.

Methods ​

MethodParametersReturnsDescription
createInterviewSessionapplicationId{ sessionId, interviewUrl }Create Okia session
handleInterviewCompletedpayload: InterviewWebhookPayloadApplicationProcess webhook
getInterviewStatusapplicationIdInterviewStatusResultGet interview status
getUserPendingInterviewsuserIdApplication[]Pending interviews
getUserInterviewsuserIdApplication[]All interviews
autoInviteIfEnabledapplicationIdvoidAuto-invite on apply
updateScoreVisibilityuserId, showScoresUserPrivacy toggle
getInterviewSessionForUserapplicationId, userId{ sessionToken, interviewUrl }Get session URL
getApplicationsWithInterviewsemployerId, jobId?, status?Application[]Applications with scores

Okia Integration ​

Creates session via HTTP to Okia service:

typescript
const sessionRequest = {
  candidate_name: user.name,
  job_title: job.title,
  max_questions: job.interviewQuestions,
  time_limit_minutes: job.interviewTimeLimit,
  webhook_url: `${YEBOJOBS_API_URL}/api/interviews/webhook/completed`,
  metadata: { yebojobs_application_id: applicationId }
};

const response = await fetch(`${OKIA_API_URL}/api/v1/sessions`, {
  method: 'POST',
  headers: { 'X-API-Key': OKIA_API_KEY },
  body: JSON.stringify(sessionRequest)
});

ExperienceService ​

File: experience.service.ts

Manages Experience Lab - skill building tracks with tasks and certificates.

Methods ​

MethodParametersReturnsDescription
getAllTracksoptions?ExperienceTrack[]List available tracks
getTrackBySlugslugExperienceTrackGet track by slug
getTrackByIdidExperienceTrackGet track by ID
getTaskByIdtaskIdExperienceTaskGet task details
getTaskWithUserProgresstaskId, userId{ task, submissions, attemptCount }Task with user's progress
enrollInTrackuserId, trackIdTrackEnrollmentEnroll user in track
getEnrollmentuserId, trackIdTrackEnrollmentGet enrollment
getUserEnrollmentsuserIdTrackEnrollment[]User's enrollments
submitTaskinput: SubmitTaskInput{ submission, passed, xpEarned, badgesEarned, certificateEarned? }Submit task answer
getUserXPuserIdUserXPGet XP and level
getUserBadgesuserIdUserBadge[]Get earned badges
getCertificateuserId, trackIdExperienceCertificateGet certificate
getUserCertificatesuserIdExperienceCertificate[]All certificates
verifyCertificatecodeExperienceCertificateVerify by code
getLeaderboardlimit{ userId, name, totalXP, level }[]Top users
getUserProgressuserIdProgressSummaryFull progress overview

Task Grading ​

Supports multiple choice and text response auto-grading:

typescript
private static gradeMultipleChoice(taskData, response) {
  let correct = 0;
  for (const q of taskData.questions) {
    if (answers[q.id] === q.correctIndex) correct++;
  }
  return {
    score: Math.round((correct / questions.length) * 100),
    feedback: `You got ${correct}/${questions.length} correct.`
  };
}

XP Leveling System ​

typescript
const XP_LEVELS = {
  1: 0, 2: 100, 3: 300, 4: 600, 5: 1000,
  6: 1500, 7: 2100, 8: 2800, 9: 3600, 10: 4500
};

VerificationService ​

File: verification.service.ts

Handles phone number verification via SMS OTP.

Methods ​

MethodParametersReturnsDescription
sendVerificationCodephone, userType{ success, message, error? }Send SMS OTP
verifyCodephone, code, userType{ success, verificationToken?, error? }Verify OTP
getVerifiedPhoneverificationToken{ phone, userType }Extract from token

Rate Limiting ​

  • Max 3 verification attempts per phone per 10 minutes
  • Max 5 code attempts before locked
  • Codes expire after 5 minutes

One chat. Everything done.