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.