Skip to main content

Points

What it is​

A loyalty-points system that rewards two different behaviors around event memories (photo/video uploads): hosts earn points when guests upload memories to their events, and guests earn points for uploading memories themselves. Points can be converted to cash.

Core concepts​

  • Two point types, PointType.HOST_POINTS and PointType.GUEST_POINTS, sharing the same point_balances / point_transactions tables but with different earning rules and cash-conversion rates.
  • Host points: 1 point per unique guest, across all of the host's events — a guest who uploads memories to five of your events still only earns you one point, but a guest uploading to another host's event earns that host a separate point. Enforced by a (host_id, guest_id) unique constraint in host_guest_memory_tracking.
  • Guest points: 1 point per event — a guest uploading 10 memories to the same event earns one point; uploading to a different event earns another. Enforced by a (guest_id, event_id) unique constraint in guest_event_memory_tracking.
  • Both are issued asynchronously via Google Pub/Sub, not inline with the upload request — the memory upload endpoint returns immediately, and a subscriber (HostPointsSubscriberHandler / GuestPointsSubscriberHandler) does the eligibility check and award in the background.

How it works​

  1. Guest uploads a memory: POST /activity-log/:eventId/upload-memory.
  2. ActivityLogService publishes to both points.host.issue and points.guest.issue Pub/Sub topics (non-blocking) — host points skip if guest == host.
  3. Each subscriber independently checks its own uniqueness table, and if the pair is new, creates a tracking row, awards the point, and writes a point_transactions record.
  4. Guests can then:
    • GET /v1/points/list — see all their point-type balances at a glance.
    • GET /v1/points/:pointType/details — balance detail for one type.
    • GET /v1/points/:pointType/history — transaction history.
    • POST /v1/points/:pointType/convert-to-cash — cash out, subject to the rules below.

Cash conversion rates​

Conversion rate and minimums are per point type, defined in PointsService's pointConfigs:

Point typeRateMinimum conversion
Host points1 point = ₦10010 points (₦1,000)
Guest points1 point = ₦20100 points (₦2,000)

Host points are worth 5x more per point — reflects that hosting an event that attracts unique-guest memory uploads is a stronger signal than any individual guest's single upload.

Key business rules & edge cases​

  • Uniqueness is enforced by a DB constraint, not application logic alone — (host_id, guest_id) and (guest_id, event_id) are both UNIQUE indexes, so even a race between two concurrent uploads can't double-award a point.
  • A conversion request below the type's minimumConversion is rejected outright, not partially processed — check the current balance and minimum before calling convert-to-cash, or expect a 400.
  • Since issuance is async over Pub/Sub, there's a delay (typically seconds) between a memory upload succeeding and the point balance updating — don't assume GET /details reflects a memory uploaded moments ago.

GET /v1/points/list, GET /v1/points/{pointType}/details, GET /v1/points/{pointType}/history, POST /v1/points/{pointType}/convert-to-cash. Not yet in the generated OpenAPI spec — this module hasn't been through the pilot OpenAPI pass.

  • src/modules/points/points.route.ts, points.controller.ts, points.service.ts
  • src/modules/points/host-points-issuance.service.ts, host-points-subscriber.handler.ts
  • src/modules/points/guest-points-issuance.service.ts, guest-points-subscriber.handler.ts
  • src/shared/config/pubsub-topics.config.ts — Pub/Sub topic/subscription config (retries, ack deadlines)
  • Migration: src/database/migrations/20251009010228_create_points_tables.ts