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
| Method | Parameters | Returns | Description |
|---|---|---|---|
registerUser | userData: IUserRegister | { user, accessToken, refreshToken } | Register new job seeker |
registerEmployer | employerData: IEmployerRegister | { employer, accessToken, refreshToken } | Register new employer |
loginUser | phone: string, password: string | { user, accessToken, refreshToken } | User login |
loginEmployer | email: string, password: string | { employer, accessToken, refreshToken } | Employer login |
refreshToken | refreshToken: string, userType: 'user' | 'employer' | { accessToken, refreshToken } | Refresh JWT tokens |
logout | id: string, type: 'user' | 'employer' | void | Clear refresh token |
Password Hashing
Uses bcrypt with 10 salt rounds:
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
createJob | jobData: IJobCreate | Job | Create new job posting |
getJobById | jobId: string | Job | null | Get job with employer info |
getJobs | skip, limit, filters? | { jobs, total } | Paginated job list |
updateJob | jobId, updateData | Job | Update job details |
deleteJob | jobId: string | boolean | Delete job |
incrementApplicationCount | jobId: string | void | Bump application counter |
incrementSaveCount | jobId: string | void | Bump save counter |
incrementShareCount | jobId: string | void | Bump share counter |
searchJobs | searchTerm, skip, limit | { jobs, total } | Full-text search |
Full-Text Search
Uses PostgreSQL tsvector for search:
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
createApplication | applicationData | Application | Submit application |
getApplicationById | applicationId | Application | Get with relations |
getApplicationsByUser | userId, skip, limit | { applications, total } | User's applications |
getApplicationsByEmployer | employerId, skip, limit, status? | { applications, total } | Employer's received apps |
updateApplicationStatus | applicationId, updateData | Application | Change status |
deleteApplication | applicationId | boolean | Remove application |
Auto-Interview Invitation
When creating an application, checks if job has AI interviews enabled:
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
getUserById | userId | User | Get user with education/experience |
updateUser | userId, updateData | User | Update profile |
addSwipeHistory | userId, { jobId, action } | User | Record swipe |
saveJob | userId, jobId | User | Save job to favorites |
unsaveJob | userId, jobId | User | Remove from favorites |
getSavedJobs | userId, skip, limit | { jobs, total } | Get saved jobs |
addEducation | userId, education | Education | Add education entry |
updateEducation | educationId, data | Education | Update education |
deleteEducation | educationId | boolean | Remove education |
addExperience | userId, experience | Experience | Add work experience |
updateExperience | experienceId, data | Experience | Update experience |
deleteExperience | experienceId | boolean | Remove experience |
checkProfileCompleteness | userId | ProfileCompletenessResult | Profile completion % |
Profile Completeness
Checks required fields for a complete profile:
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
getEmployerById | employerId | Employer | Get employer profile |
updateEmployer | employerId, updateData | Employer | Update profile |
incrementJobsPosted | employerId | void | Bump job counters |
decrementActiveJobs | employerId | void | Decrement active jobs |
verifyEmployer | employerId | Employer | Mark as verified |
getEmployers | skip, limit, filters? | { employers, total } | List employers |
deleteEmployer | employerId | boolean | Remove employer |
ServicesService
File: services.service.ts
Manages service worker profiles, TikTok-style feed, and swiping.
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
createWorkerProfile | input | ServiceWorkerProfile | Create worker profile |
getWorkerProfileByUserId | userId | ServiceWorkerProfile | Get with categories & reviews |
updateWorkerProfile | userId, data | ServiceWorkerProfile | Update profile |
toggleAvailability | userId | ServiceWorkerProfile | Toggle availability status |
getNearbyWorkers | query: NearbyWorkersQuery | { workers, total } | GPS-based search |
getWorkerFeed | query: WorkerFeedQuery | { workers, total } | TikTok-style feed |
swipeWorker | userId, workerId, action | ServiceSwipeHistory | Record client swipe on worker |
swipeRequest | workerId, requestId, action | RequestSwipeHistory | Record worker swipe on request |
getWorkerLikes | workerId | swipes[] | Get who liked this worker |
getCategories | parentId? | ServiceCategory[] | Get service categories |
createCategory | name, parentId?, description?, icon? | ServiceCategory | Create category |
searchWorkers | query, lat?, lng?, page, limit | { workers, total } | Search workers |
Haversine Distance Calculation
GPS-based nearby workers use the Haversine formula:
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
createRequest | input: CreateServiceRequestInput | ServiceRequest | Client posts service need |
getRequestById | requestId | ServiceRequest | Get with quotes |
getClientRequests | clientId, status?, page, limit | { requests, total } | Client's requests |
getNearbyRequests | workerId, categoryIds, lat, lng, radius, page, limit | { requests, total } | Requests near worker |
updateRequestStatus | requestId, status | ServiceRequest | Update request status |
createQuote | input: CreateQuoteInput | Quote | Worker submits quote |
getQuotesForRequest | requestId | Quote[] | Get all quotes |
acceptQuote | quoteId, clientId | ServiceBooking | Accept quote → create booking |
rejectQuote | quoteId, clientId | Quote | Reject quote |
createBooking | input: CreateBookingInput | ServiceBooking | Direct booking |
getBookingById | bookingId | ServiceBooking | Get booking details |
getClientBookings | clientId, status?, page, limit | { bookings, total } | Client's bookings |
getWorkerBookings | workerId, status?, page, limit | { bookings, total } | Worker's bookings |
updateBookingStatus | bookingId, status, userId | ServiceBooking | Update with auth check |
Booking Status Flow
pending → accepted → in_progress → completed
↘ declined ↘ cancelled ↘ disputedMessagesService
File: messages.service.ts
Handles the messaging system for job inquiries and booking discussions.
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
createConversationForApplication | applicationId, employerId, applicantId | Conversation | Start job inquiry chat |
createConversationForQuote | quoteId, workerId, clientId | Conversation | Start booking inquiry chat |
createConversationForBooking | bookingId, workerId, clientId | Conversation | Direct booking chat |
getUserConversations | userId, page, limit, includeArchived | { conversations, total } | User's conversations |
getConversation | conversationId, userId | Conversation | Get with messages |
archiveConversation | conversationId, userId | ConversationParticipant | Archive for user |
unarchiveConversation | conversationId, userId | ConversationParticipant | Unarchive |
sendMessage | input: SendMessageInput | Message | Send message |
getMessages | conversationId, userId, page, limit, before? | { messages, total } | Get messages |
markAsRead | conversationId, userId | boolean | Mark messages read |
deleteMessage | messageId, userId | Message | Soft delete |
updateTypingStatus | conversationId, userId, isTyping | boolean | Update typing indicator |
getTypingUsers | conversationId, excludeUserId | { id, name }[] | Get who's typing |
getTotalUnreadCount | userId | number | Total unread messages |
togglePinConversation | conversationId, userId | ConversationParticipant | Pin/unpin |
toggleMuteConversation | conversationId, userId | ConversationParticipant | Mute/unmute |
WhatsApp Notifications
Sends SMS/WhatsApp when recipient is offline:
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
createInterviewSession | applicationId | { sessionId, interviewUrl } | Create Okia session |
handleInterviewCompleted | payload: InterviewWebhookPayload | Application | Process webhook |
getInterviewStatus | applicationId | InterviewStatusResult | Get interview status |
getUserPendingInterviews | userId | Application[] | Pending interviews |
getUserInterviews | userId | Application[] | All interviews |
autoInviteIfEnabled | applicationId | void | Auto-invite on apply |
updateScoreVisibility | userId, showScores | User | Privacy toggle |
getInterviewSessionForUser | applicationId, userId | { sessionToken, interviewUrl } | Get session URL |
getApplicationsWithInterviews | employerId, jobId?, status? | Application[] | Applications with scores |
Okia Integration
Creates session via HTTP to Okia service:
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
getAllTracks | options? | ExperienceTrack[] | List available tracks |
getTrackBySlug | slug | ExperienceTrack | Get track by slug |
getTrackById | id | ExperienceTrack | Get track by ID |
getTaskById | taskId | ExperienceTask | Get task details |
getTaskWithUserProgress | taskId, userId | { task, submissions, attemptCount } | Task with user's progress |
enrollInTrack | userId, trackId | TrackEnrollment | Enroll user in track |
getEnrollment | userId, trackId | TrackEnrollment | Get enrollment |
getUserEnrollments | userId | TrackEnrollment[] | User's enrollments |
submitTask | input: SubmitTaskInput | { submission, passed, xpEarned, badgesEarned, certificateEarned? } | Submit task answer |
getUserXP | userId | UserXP | Get XP and level |
getUserBadges | userId | UserBadge[] | Get earned badges |
getCertificate | userId, trackId | ExperienceCertificate | Get certificate |
getUserCertificates | userId | ExperienceCertificate[] | All certificates |
verifyCertificate | code | ExperienceCertificate | Verify by code |
getLeaderboard | limit | { userId, name, totalXP, level }[] | Top users |
getUserProgress | userId | ProgressSummary | Full progress overview |
Task Grading
Supports multiple choice and text response auto-grading:
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
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
| Method | Parameters | Returns | Description |
|---|---|---|---|
sendVerificationCode | phone, userType | { success, message, error? } | Send SMS OTP |
verifyCode | phone, code, userType | { success, verificationToken?, error? } | Verify OTP |
getVerifiedPhone | verificationToken | { 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