Let me explain the current streak implementation and how it works from frontend (Discord interactions) to backend (database and calculations).
Read , lines 1 to 30
Summarized conversation history
Read , lines 50 to 100
Summarized conversation history
Read , lines 100 to 150
Read , lines 150 to 264
Read , lines 1 to 50
Read , lines 80 to 120
Read , lines 1 to 80
Now I'll explain the complete streak implementation workflow from frontend to backend. Let me provide a comprehensive breakdown:
- User posts in the
basher-progresscategory threads - Message must contain:
- πΈ Image/screenshot attachment
- βοΈ At least 50 words description
- π Posted in their own thread (thread owner only)
/help - Bot guide and requirements
/streak - Check current streak (yours or someone else's)
/mystats - View detailed personal statistics
/ping - Bot health check// 1. Skip bot messages
if (message.author.bot) return;
// 2. Category validation - must be in basher-progress
if (!this.isInBasherProgressCategory(message)) return;
// 3. Thread ownership validation
if (message.author.id !== threadOwner) return;
// 4. Database-based duplicate prevention (deployment-safe)
const alreadyAwarded = await database.checkDailyPointsAwarded(memberId, description);
if (alreadyAwarded) {
console.log(`Already received points today - ignoring`);
return; // Silently skip validation for awarded users
}// Image requirement check
const hasImage = message.attachments.some(attachment =>
attachment.contentType?.startsWith('image/'));
// Word count validation
const wordCount = message.content.split(' ').filter(word =>
word.length > 0).length;
// Must meet both criteria
if (!hasImage || wordCount < config.points.minimumWords) {
await this.sendFeedback(message, 'Need screenshot + 50 words!');
return;
}// Create points record with timestamp
const pointsResult = await database.createPoints(
memberId,
config.points.dailyAmount, // 10 points
`PU-${dateString}`, // Daily unique description
'daily' // Point type
);The core streak logic uses daily 11:59 PM cutoff:
// Daily cutoff configuration
this.dailyCutoffHour = 23; // 11 PM
this.dailyCutoffMinute = 59; // 59 minutes
this.dailyCutoffSecond = 59; // 59 secondsStreak Algorithm:
- Fetch Historical Data: Get all points records for user
- Group by Date: Organize submissions by YYYY-MM-DD format
- Backward Date Traversal: Start from today, work backwards
- Cutoff Validation: Check if each submission was before 11:59:59 PM
- Consecutive Check: Count consecutive days with valid submissions
// Core streak calculation logic
while (true) {
const dateKey = currentDate.toISOString().split('T')[0]; // YYYY-MM-DD
if (dailySubmissions[dateKey] && dailySubmissions[dateKey].length > 0) {
const validSubmission = submissions.some(submission => {
const submissionTime = new Date(submission.created_at);
const cutoffTime = new Date(submissionTime);
cutoffTime.setHours(23, 59, 59, 999); // 11:59:59 PM
return submissionTime <= cutoffTime; // Before midnight
});
if (validSubmission) {
streak++;
} else {
console.log(`Submission after 11:59 PM - streak broken`);
break;
}
} else {
if (!isToday) {
console.log(`No submission found - streak broken`);
break;
}
}
currentDate.setDate(currentDate.getDate() - 1); // Previous day
}-- Points tracking with timestamps
points (id, member_id, amount, description, created_at, type)
-- Member statistics with streaks
member_stats (member_id, discord_streak, last_updated_at)
-- Member registration
members (id, discord_username, created_at)// Check daily points already awarded (prevents duplicates)
checkDailyPointsAwarded(memberId, description)
// Award points with timestamp
createPoints(memberId, amount, description, type)
// Update streak in member_stats
updateDiscordStreak(memberId, newStreak)
// Get historical points for streak calculation
getPointsByMember(memberId)const embed = new EmbedBuilder()
.setColor('#00ff00')
.setTitle('π Progress Recorded!')
.setDescription(`+${points} points awarded!`)
.addFields(
{ name: 'π₯ Current Streak', value: `${streakResult.currentStreak} days`, inline: true },
{ name: 'π― Status', value: this.getStreakStatusMessage(streak), inline: true },
{ name: 'β° Next Cutoff', value: `11:59 PM today`, inline: true }
);- β Before: Used in-memory cooldowns (lost on restart)
- β Now: Database-based duplicate prevention (persistent)
- β Before: 25-hour rolling window (confusing)
- β Now: Clear 11:59 PM daily boundary (predictable)
External monitoring endpoints:
GET /health - Bot status check
GET /streak/:member - Get member streak via API
GET /stats/:member - Get member statistics
POST /recalculate - Admin streak recalculation// Missing user registration
if (!memberId) {
await interaction.reply({
content: `β Member not registered in system`,
ephemeral: true
});
return;
}
// Database errors
try {
const streak = await streakService.calculateStreak(memberId);
} catch (error) {
console.error('Streak calculation failed:', error);
return 0; // Default to 0 instead of crashing
}Discord Message β Validation β Points Award β Streak Calculation β Database Update β User Feedback
β β β β β β
[User Posts] [Image+Words] [10 Points] [11:59 PM Rule] [Supabase Store] [Rich Embed]
β β β β β β
[Thread Only] [Already Today?] [PU-Date] [Historical Scan] [member_stats] [Streak Info]
This system ensures:
- β Persistent streaks across bot restarts
- β Clear daily boundaries at 11:59 PM
- β No duplicate points per day
- β Rich user feedback with embeds
- β Deployment-safe architecture with database backing
- β Admin monitoring via API endpoints
The streak calculation is completely database-driven and recalculates from historical data each time, making it bulletproof against deployment changes and ensuring accuracy.