Assets Badges & Referrals: Gamification and Growth Mechanics on Qbix
The Qbix Platform includes a lightweight but powerful gamification layer consisting of:
- Badges β achievements that users can earn
- Earned Events β timestamped records of earning badges
- Leaderboards β summarized daily points
- Referrals β a companion system awarding points when users invite others
These systems are deeply integrated with Qbix Streams, enabling notifications, subscriptions, and viral growth loops across communities.
Badges: Static Achievement Definitions
Each badge is defined in assets_badge. It belongs to an app and (optionally) a community.
CREATE TABLE assets_badge (
appId VARBINARY(31) NOT NULL,
communityId VARBINARY(31),
name VARCHAR(63) NOT NULL,
icon VARBINARY(255),
title VARCHAR(255) NOT NULL,
description TEXT,
points SMALLINT NOT NULL DEFAULT 0,
PRIMARY KEY (appId, name)
);
A badgeβs points value determines its contribution to leaderboards. Example badge definitions:
- first_post β 5 points
- event_host β 50 points
- invited_friend β 10 points
Earned Badges: Event Stream of Achievements
Every time a user earns a badge, Qbix inserts a row into assets_earned:
CREATE TABLE assets_earned (
appId VARBINARY(31) NOT NULL,
communityId VARBINARY(31),
earnedTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
userId VARCHAR(31) NOT NULL,
badgeName VARCHAR(255) NOT NULL,
publisherId VARBINARY(31),
streamName VARBINARY(255),
KEY byTime (appId, communityId, earnedTime),
KEY byUser (appId, communityId, userId)
);
These rows form the time-series history of user achievements.
Earning a badge
Assets_Earned::badge('abcd1234', 'first_post', [
'publisherId' => $thread->publisherId,
'streamName' => $thread->name,
'earnedTime' => time(),
'duplicate' => true
]);
If duplicate is false, the system prevents awarding a badge twice.
Computing Totals & Summaries
Use Assets_Badge::badgesAndTotals() to compute point totals from earned badges.
$leaders = Assets_Badge::badgesAndTotals(
strtotime('-30 days'),
time(),
[
'communityId' => 'JGR'
]
);
Returns structure:
[
"userA" => [
"badges" => [BadgeObj, BadgeObj, ...],
"total" => 37
],
"userB" => [
"badges" => [...],
"total" => 24
]
]
Leaderboards: Daily Summaries
The daily leaderboard table:
CREATE TABLE assets_leader (
communityId VARBINARY(31) NOT NULL,
day DATE NOT NULL,
userId VARCHAR(31) NOT NULL,
points SMALLINT NOT NULL DEFAULT 0,
PRIMARY KEY (communityId, day, userId)
);
A nightly cron job can compute the day's earned badges and store totals:
$today = date('Y-m-d');
$leaders = Assets_Badge::badgesAndTotals(
strtotime("$today 00:00:00"),
strtotime("$today 23:59:59")
);
foreach ($leaders as $userId => $info) {
$row = new Assets_Leader([
'communityId' => 'JGR',
'day' => $today,
'userId' => $userId,
'points' => $info['total']
]);
$row->save(true);
}
Users_Referred: Tracking Referrals & Qualification
The Users_Referred table tracks:
- which user was invited (userId)
- who invited them (byUserId)
- which community or entity they were referred to
- the points earned for this referral
- whether the referral reached a qualification threshold
The logic is implemented in Users_Referred::handleReferral().
Referrals table behavior
$r = new Users_Referred([ 'userId' => $userId, 'toCommunityId' => $publisherId, 'byUserId' => $invitingUserId ]);
If a referral row exists, it updates; otherwise it inserts. Each referral type carries:
- points β for leaderboard scoring
- qualified β threshold after which a referral is considered βsuccessfulβ
Example configuration:
Users/referred/event/points = 10 Users/referred/event/qualified = 10
Referral logic
Users_Referred::handleReferral( $asUserId, $invite->publisherId, $s->type, $invite->invitingUserId );
This method:
- Loads referral point values from config
- Creates or updates the referral record
- Tracks when the referral βqualifiesβ (crosses threshold)
- Stores cumulative points: max(previous, current)
The max() rule ensures referral points never decrease.
Referrals Triggered from Stream Subscriptions
In Qbix, joining or subscribing to streams via an invite token automatically creates a referral event:
// Inside Streams::subscribe
if ($token = $_SESSION['Streams']['invite']['token']) {
if ($invite = Streams_Invite::fromToken($token)) {
foreach ($streamsJoined as $s) {
Users_Referred::handleReferral(
$asUserId,
$invite->publisherId,
$s->type,
$invite->invitingUserId
);
}
}
}
This creates a closed loop:
- A user shares an invite link
- The invitee joins streams
- A referral row + points are recorded
- Badges or leaderboards can pick up these points
Integrating Referrals Into Leaderboards
Referral points live in users_referred and badge points live in assets_earned. Both can contribute to the same leaderboard.
Example daily point total:
$badgeTotals = Assets_Badge::badgesAndTotals($from, $until);
$referralTotals = Users_Referred::computeTotals($from, $until); // hypothetical aggregator
$combined = [];
foreach ($badgeTotals as $u => $info) {
$combined[$u] = $info['total'];
}
foreach ($referralTotals as $u => $pts) {
$combined[$u] = Q::ifset($combined, $u, 0) + $pts;
}
This creates a unified scoring model mixing achievements + referrals.
Summary of Badge & Referral Architecture
assets_badge β static definitions (title, points, icon) assets_earned β time-series of earned badges assets_leader β daily point totals users_referred β referral-based points & qualification tracking Assets_Earned::badge() β award a badge Assets_Badge::badgesAndTotals() β compute badge points Users_Referred::handleReferral()β track referrals Streams::subscribe() β triggers referral creation from invite tokens Cron jobs β compute daily leaderboard entries Streams posts β optional achievements/notifications
Together these systems form a cohesive gamification + growth engine for Qbix apps. Badges reward engagement, referrals reward virality, and leaderboards create competition.