Skip to content

Zaptam Services Reference

Complete documentation of all backend services with methods, parameters, and return types.


Table of Contents

  1. AuthService
  2. MatchService
  3. MessageService
  4. UserService
  5. WalletService
  6. VerificationService
  7. ReportService
  8. TrustService
  9. NotificationService

AuthService

File: src/services/auth.service.ts

Handles user registration, authentication, OTP verification, and password management.

Methods

applyForAccount(params: ApplyForAccountParams): Promise<OtpResponse>

Creates a pending user account and sends verification OTP.

typescript
interface ApplyForAccountParams {
  phoneNumber: string;      // International format: +268XXXXXXXX
  gender: Gender;           // 'MALE' | 'FEMALE'
  email?: string;
  name?: string;
  occupation?: string;      // Required for MALE
  intro?: string;           // For FEMALE only
  netWorth?: string;        // Required for MALE: '50k-100k' | '100k-500k' | '500k-1m' | '1m-5m' | '5m+'
  photos?: Express.Multer.File[];  // Required for FEMALE
}

// Returns
interface OtpResponse {
  success: boolean;
  message: string;
  expiresAt?: Date;  // OTP expiration (10 minutes)
}

Validation Rules:

  • Female applicants must include at least 1 photo
  • Male applicants must specify netWorth
  • Throws ConflictError if phone already registered and active

sendPhoneOtp(phoneNumber: string, purpose: OtpPurpose): Promise<OtpResponse>

Sends OTP for login, registration, or password reset.

typescript
type OtpPurpose = 'REGISTRATION' | 'LOGIN' | 'PASSWORD_RESET';

verifyOtp(phoneNumber: string, code: string, purpose: OtpPurpose): Promise<boolean>

Validates OTP code. Marks OTP as used if valid.


completeRegistration(phoneNumber: string, password: string, bio?: string, dateOfBirth?: Date): Promise<TokenPair>

Finalizes registration after OTP verification.

typescript
interface TokenPair {
  accessToken: string;   // JWT, expires in 15m
  refreshToken: string;  // UUID, stored in DB, expires in 7d
}

Actions:

  • Hashes password (bcrypt)
  • Sets user status to ACTIVE
  • Creates default UserSettings record

login(phoneNumber: string, password: string): Promise<TokenPair>

Authenticates user with password.

Checks:

  • User exists and has password
  • Status is not BANNED, DELETED, or PENDING
  • Password matches

refreshAccessToken(refreshToken: string): Promise<TokenPair>

Rotates tokens. Revokes old refresh token.


logout(userId: string, refreshToken?: string): Promise<void>

Revokes refresh token(s). If no token provided, revokes all user tokens.


resetPassword(phoneNumber: string, code: string, newPassword: string): Promise<void>

Resets password after OTP verification. Revokes all existing tokens.


MatchService

File: src/services/match.service.ts

Handles profile discovery, interest expression, and matching.

Methods

discoverProfiles(userId: string, page: number, limit: number, filters?: DiscoveryFilters): Promise<PaginatedResponse<ProfilePreview>>

Returns paginated list of discoverable profiles.

typescript
interface DiscoveryFilters {
  gender?: Gender;
  verificationLevel?: 'NONE' | 'IDENTITY' | 'FULL';
}

interface ProfilePreview {
  id: string;
  alias: string | null;
  gender: Gender | null;
  bio: string | null;
  trustScore: number;
  verificationLevel: string;
  photos: {
    id: string;
    url: string;
    blurLevel: number;
    isPrimary: boolean;
  }[];
}

Ordering (priority):

  1. Verification level (FULL > IDENTITY > NONE)
  2. Trust score (highest first)
  3. Last active (most recent first)

Default filter: Shows opposite gender


getProfileById(userId: string, profileId: string): Promise<ProfileWithMatch>

Returns profile details with match status.

typescript
// If mutual match exists:
// - Photos returned with original blur levels
// - lastActiveAt visible if user allows

// If no match:
// - Photos have minimum 80% blur enforced
// - lastActiveAt hidden

expressInterest(senderId: string, recipientId: string): Promise<{ matched: boolean }>

Expresses interest in another user.

Logic:

typescript
// Check for reciprocal interest
const reciprocalInterest = await findExistingInterest(recipientId, senderId);

if (reciprocalInterest?.status === 'PENDING') {
  // MUTUAL MATCH!
  // 1. Update reciprocal to ACCEPTED
  // 2. Create new interest as ACCEPTED
  // 3. Create Conversation
  return { matched: true };
}

// Create pending interest
await createInterest(senderId, recipientId, 'PENDING');
return { matched: false };

getMatches(userId: string, page: number, limit: number): Promise<PaginatedResponse<Match>>

Returns all accepted matches for the user.


declineMatch(userId: string, matchId: string): Promise<void>

Removes a match (deletes interest record).


MessageService

File: src/services/message.service.ts

Manages conversations and messages.

Methods

getConversations(userId: string, page: number, limit: number): Promise<PaginatedResponse<ConversationPreview>>

Returns user's conversations with last message and unread count.

typescript
interface ConversationPreview {
  id: string;
  otherUser: {
    id: string;
    alias: string | null;
    photos: Photo[];  // Primary photo only
  };
  lastMessage: {
    content: string | null;
    createdAt: Date;
    isFromMe: boolean;
  } | null;
  unreadCount: number;
  updatedAt: Date;
}

getMessages(userId: string, conversationId: string, page: number, limit: number): Promise<PaginatedResponse<Message>>

Returns messages for a conversation.

Side effects:

  • Marks all unread messages as read for the requesting user
  • Filters out expired messages (expiresAt < now)
  • Filters out deleted messages (deletedAt != null)

sendMessage(senderId: string, conversationId: string, content?: string, mediaUrl?: string, expiresInHours?: number): Promise<Message>

Creates a new message.

typescript
interface Message {
  id: string;
  conversationId: string;
  senderId: string;
  recipientId: string;
  content: string | null;
  mediaUrl: string | null;
  isRead: boolean;
  expiresAt: Date | null;  // Disappearing messages
  deletedAt: Date | null;
  createdAt: Date;
}

Actions:

  • Validates user is part of conversation
  • Calculates expiresAt if disappearing messages enabled
  • Updates conversation updatedAt

deleteConversation(userId: string, conversationId: string): Promise<void>

Soft-deletes user's messages in conversation (sets deletedAt).


getOrCreateConversation(userId: string, otherUserId: string): Promise<string>

Returns existing conversation ID or creates new one.

Requirements:

  • Users must have a mutual match (accepted interest)
  • Throws ForbiddenError if no match exists

UserService

File: src/services/user.service.ts

User profile and settings management.

Methods

getUserById(userId: string): Promise<SafeUser>

Returns user with settings and photos (excludes password).


getUserSettings(userId: string): Promise<UserSettings>

Returns user settings. Creates default if none exist.

typescript
interface UserSettings {
  aliasMode: boolean;        // Show alias vs real name (default: true)
  showOnlineStatus: boolean; // Visible online status (default: false)
  regionMasking: boolean;    // Hide location (default: true)
  photoBlurLevel: number;    // 0-100 blur percentage (default: 100)
  disappearingMsgs: number | null;  // Hours until messages expire
}

updateProfile(userId: string, data: ProfileUpdate): Promise<SafeUser>

Updates profile fields.

typescript
interface ProfileUpdate {
  alias?: string;
  bio?: string;
  email?: string;
  dateOfBirth?: Date;
}

updateSettings(userId: string, data: SettingsUpdate): Promise<UserSettings>

Updates user settings.


changePassword(userId: string, currentPassword: string, newPassword: string): Promise<void>

Changes password with current password verification.


deleteAccount(userId: string): Promise<void>

Soft delete — sets status to DELETED, marks deletedAt.


panicDelete(userId: string): Promise<void>

HARD DELETE — Permanently removes all user data:

typescript
await prisma.$transaction([
  prisma.message.deleteMany({ ... }),
  prisma.conversation.deleteMany({ ... }),
  prisma.interest.deleteMany({ ... }),
  prisma.photo.deleteMany({ ... }),
  prisma.verification.deleteMany({ ... }),
  prisma.walletTransaction.deleteMany({ ... }),
  prisma.report.deleteMany({ ... }),
  prisma.refreshToken.deleteMany({ ... }),
  prisma.otpCode.deleteMany({ ... }),
  prisma.userSettings.deleteMany({ ... }),
  prisma.user.delete({ ... }),
]);

updateLastActive(userId: string): Promise<void>

Updates lastActiveAt timestamp. Called on socket connection.


WalletService

File: src/services/wallet.service.ts

Handles credits, earnings, and transactions.

Methods

getWalletBalance(userId: string): Promise<WalletBalance>

Calculates balance from transaction history.

typescript
interface WalletBalance {
  balance: number;
  currency: string;  // 'USD'
  userType: 'earner' | 'spender';  // Female = earner, Male = spender
}

Balance calculation:

typescript
let balance = 0;
for (const tx of completedTransactions) {
  if (tx.type === 'EARNING' || tx.type === 'CREDIT_PURCHASE') {
    balance += tx.amount;
  } else if (tx.type === 'WITHDRAWAL' || tx.type === 'BOOST' || tx.type === 'MEMBERSHIP') {
    balance -= tx.amount;
  }
}

getTransactionHistory(userId: string, page: number, limit: number): Promise<PaginatedResponse<Transaction>>

Returns paginated transaction history.


purchaseCredits(userId: string, amount: number, paymentReference: string): Promise<void>

Records credit purchase. Male users only.


purchaseMembership(userId: string, tierId: string, paymentReference: string): Promise<void>

Records membership purchase based on tier price.


requestWithdrawal(userId: string, amount: number, payoutDetails: string): Promise<void>

Creates pending withdrawal request. Female users only.

Requirements:

  • Minimum withdrawal: $10
  • Must have sufficient balance

processWithdrawal(transactionId: string, approved: boolean, adminId: string): Promise<void>

Admin approves/rejects withdrawal. Updates status to COMPLETED or FAILED.


getMembershipTiers(): Promise<MembershipTier[]>

Returns active membership tiers.


VerificationService

File: src/services/verification.service.ts

Manages identity and worth/value verification.

Methods

getVerificationStatus(userId: string): Promise<VerificationStatus>

Returns current verification level and history.

typescript
interface VerificationStatus {
  currentLevel: 'NONE' | 'IDENTITY' | 'FULL';
  verifications: Verification[];
}

submitVerification(userId: string, type: VerificationType, documents: string[]): Promise<void>

Submits verification request.

typescript
type VerificationType = 'IDENTITY' | 'WORTH' | 'VALUE';

Rules:

  • WORTH — Male users only
  • VALUE — Female users only
  • Cannot submit if pending verification of same type exists

reviewVerification(verificationId: string, reviewerId: string, approved: boolean, notes?: string): Promise<void>

Admin reviews verification.

On approval:

  • Updates verification status to APPROVED
  • Updates user verificationLevel:
    • IDENTITY approval: NONE → IDENTITY
    • WORTH/VALUE approval: IDENTITY → FULL

getPendingVerifications(page: number, limit: number): Promise<PaginatedResponse<Verification>>

Returns pending verifications for admin review.


ReportService

File: src/services/report.service.ts

User reporting and moderation.

Methods

createReport(data: CreateReportData): Promise<Report>

Creates a report against another user.

typescript
interface CreateReportData {
  reporterId: string;
  reportedId: string;
  type: ReportType;  // 'BLACKMAIL' | 'HARASSMENT' | 'FAKE_PROFILE' | 'INAPPROPRIATE_CONTENT' | 'OTHER'
  description: string;
  evidence?: any;  // JSON evidence
}

Validation: Cannot have duplicate active report (PENDING/INVESTIGATING) for same user+type.


createBlackmailReport(reporterId: string, reportedId: string, description: string, evidence?: any): Promise<Report>

High-priority blackmail report.

Automatic actions:

  • Status set to INVESTIGATING (not PENDING)
  • Immediate -20 trust score penalty to reported user

getReports(filters: ReportFilters): Promise<PaginatedReports>

Returns reports with optional filters.

typescript
interface ReportFilters {
  status?: ReportStatus;
  type?: ReportType;
  page?: number;
  limit?: number;
}

getReportById(reportId: string): Promise<Report>

Returns detailed report with reporter and reported user info.


getUserReports(userId: string): Promise<Report[]>

Returns reports submitted by a user.


TrustService

File: src/services/trust.service.ts

Trust score calculation and management.

Methods

calculateTrustScore(userId: string): Promise<number>

Calculates trust score (0-100) from multiple factors.

See Trust System Documentation for full algorithm.


updateTrustScore(userId: string): Promise<number>

Recalculates and saves trust score.


applyTrustPenalty(userId: string, penalty: number, reason: string): Promise<number>

Directly reduces trust score.

typescript
const newScore = Math.max(0, currentScore - penalty);

reportScreenshotDetected(userId: string): Promise<void>

Applies -10 penalty for screenshot detection.


NotificationService

File: src/services/notification.service.ts

WhatsApp/SMS OTP delivery.

Methods

sendOtpNotification(phoneNumber: string, code: string, purpose: string): Promise<NotificationResponse>

Sends OTP via Eneza notification service.

typescript
interface NotificationPayload {
  messageType: 'authentication';
  phoneNumber: string;
  authCode: string;
  buttonUrl: 'verify-code' | 'login-code' | 'reset-code';
  channel: 'whatsapp' | 'sms';
}

interface NotificationResponse {
  success: boolean;
  message?: string;
  error?: string;
}

Configuration:

typescript
const NOTIFICATION_SERVICE_URL = 'https://notifications.eneza.app';
const DEFAULT_CHANNEL = 'whatsapp';

One chat. Everything done.