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.