I want to create a streak system for my website where when a user completes a game, it adds to their daily playing streak. If they play again 24hrs after then their streak should increase. however, if they miss a day, their streak should go back to 0. How do I implement this? Should I have backend functions that truly run on the server?
#Streak System
19 messages · Page 1 of 1 (latest)
Hi @bright granite. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
To implement a streak system for your website using Convex, you can indeed use backend functions that run on the server. This approach ensures the integrity of the streak calculation and prevents client-side manipulation. Here's how you can implement this using Convex:
-
Create a database table to store user streaks:
You'll need a table to store user IDs, their current streak count, and the last play timestamp. -
Implement a backend function to update streaks:
Create a Convex mutation function that runs on the server to update the streak when a user completes a game. This function should:- Check the user's last play timestamp
- If it's been less than 24 hours, increment the streak
- If it's been more than 24 hours but less than 48 hours, increment the streak
- If it's been more than 48 hours, reset the streak to 1
- Update the last play timestamp
-
Use Convex's scheduling feature to reset streaks:
You can use Convex's Cron Jobs (https://docs.convex.dev/scheduling/cron-jobs) to run a daily function that checks all users' last play timestamps and resets streaks for those who haven't played in the last 24 hours.
Here's a basic implementation of the streak update function:
import { mutation } from "./_generated/server";
export const updateStreak = mutation(async ({ db, auth }) => {
const identity = await auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthenticated");
}
const userId = identity.subject;
const user = await db
.query("users")
.filter(q => q.eq(q.field("userId"), userId))
.first();
const now = Date.now();
const lastPlay = user?.lastPlay ?? 0;
const timeSinceLastPlay = now - lastPlay;
let newStreak;
if (timeSinceLastPlay < 48 * 60 * 60 * 1000) {
// Less than 48 hours, increment streak
newStreak = (user?.streak ?? 0) + 1;
} else {
// More than 48 hours, reset streak
newStreak = 1;
}
if (user) {
await db.patch(user._id, { streak: newStreak, lastPlay: now });
} else {
await db.insert("users", { userId, streak: newStreak, lastPlay: now });
}
return newStreak;
});
To reset streaks daily, you can set up a cron job:
```typescript
import { cronJobs } from "convex/server";
const crons = cronJobs();
crons.daily(
"Reset inactive streaks",
{ hourUTC: 0, minuteUTC: 0 },
api.streaks.resetInactiveStreaks
);
This approach leverages Convex's backend capabilities to ensure accurate and secure streak tracking. The server-side functions prevent client-side manipulation, and the scheduling feature allows for automatic streak resets.
For more details on implementing cron jobs and scheduling in Convex, refer to the Scheduling documentation (https://docs.convex.dev/scheduling).
Convex Components (https://stack.convex.dev/backend-components) might also be useful for implementing additional features like leaderboards based on streaks.
Remember to adjust the code according to your specific data model and requirements.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@bright granite Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@errant wraith what is the code for resetInactiveStreaks?
Let me check the knowledge base for you again. One moment! :books:
Based on the knowledge sources provided, I don't have specific code for a resetInactiveStreaks function. The previous conversation mentioned this function, but the actual implementation was not provided in the knowledge sources.
However, I can give you a general idea of how you might implement such a function using Convex:
import { internalMutation } from "./_generated/server";
export const resetInactiveStreaks = internalMutation(async ({ db }) => {
const twentyFourHoursAgo = Date.now() - 24 * 60 * 60 * 1000;
const inactiveUsers = await db
.query("users")
.filter(q => q.lt(q.field("lastPlay"), twentyFourHoursAgo))
.collect();
for (const user of inactiveUsers) {
await db.patch(user._id, { streak: 0 });
}
});
This function would:
- Calculate the timestamp for 24 hours ago
- Query the database for all users who haven't played in the last 24 hours
- Reset the streak to 0 for all these inactive users
Remember, this is just a conceptual implementation. You would need to adjust it based on your specific data model and requirements.
To use this function in a cron job, you would set it up as mentioned in the previous conversation, using the Cron Jobs feature in Convex (https://docs.convex.dev/scheduling/cron-jobs):
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.daily(
"Reset inactive streaks",
{ hourUTC: 0, minuteUTC: 0 },
internal.streaks.resetInactiveStreaks
);
export default crons;
This setup would run the resetInactiveStreaks function daily at midnight UTC.
For more detailed information on implementing cron jobs in Convex, you can refer to the Cron Jobs documentation (https://docs.convex.dev/scheduling/cron-jobs).
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@bright granite Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@errant wraith do I want to call updateStreak after a user completes a game?
Let me check the knowledge base for you again. One moment! :books:
Yes, you would want to call the updateStreak function after a user completes a game. This is the correct approach to maintain an accurate streak count for each user.
Based on the implementation we discussed earlier, the updateStreak function is designed to be called whenever a user completes a game. It performs the following crucial tasks:
- It checks the time since the user's last play.
- It increments the streak if the user has played within the last 48 hours.
- It resets the streak to 1 if more than 48 hours have passed since the last play.
- It updates the user's last play timestamp.
By calling this function after each game completion, you ensure that the user's streak is always up-to-date and accurately reflects their playing pattern.
You would typically call this function from your client-side code after a game is completed. For example, in a React component, you might have something like this:
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
function GameComponent() {
const updateStreak = useMutation(api.streaks.updateStreak);
const handleGameCompletion = async () => {
// ... other game completion logic ...
// Update the user's streak
const newStreak = await updateStreak();
console.log(`Your new streak is: ${newStreak}`);
};
// ... rest of the component ...
}
This approach leverages Convex's mutation functions (https://docs.convex.dev/functions/mutations) to ensure that streak updates are handled server-side, maintaining data integrity and preventing client-side manipulation.
Remember, the exact implementation may vary depending on your specific game logic and data model, but the principle of calling updateStreak after game completion remains the same.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@bright granite Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@errant wraith If a user plays two games in the same day, the streak should only increase for the first one and not increase again/decrease until the next day
Let me check the knowledge base for you again. One moment! :books:
You're absolutely right, and we can modify the updateStreak function to handle this scenario. We want to ensure that the streak only increases once per day, regardless of how many games a user plays within that day. Here's how we can adjust the implementation:
import { mutation } from "./_generated/server";
export const updateStreak = mutation(async ({ db, auth }) => {
const identity = await auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthenticated");
}
const userId = identity.subject;
const user = await db
.query("users")
.filter(q => q.eq(q.field("userId"), userId))
.first();
const now = Date.now();
const lastPlay = user?.lastPlay ?? 0;
const timeSinceLastPlay = now - lastPlay;
// Check if the last play was on a different day
const lastPlayDate = new Date(lastPlay).setHours(0, 0, 0, 0);
const todayDate = new Date(now).setHours(0, 0, 0, 0);
let newStreak = user?.streak ?? 0;
if (lastPlayDate < todayDate) {
// Last play was on a different day
if (timeSinceLastPlay < 48 * 60 * 60 * 1000) {
// Less than 48 hours, increment streak
newStreak += 1;
} else {
// More than 48 hours, reset streak
newStreak = 1;
}
}
// If lastPlayDate === todayDate, we don't change the streak
if (user) {
await db.patch(user._id, { streak: newStreak, lastPlay: now });
} else {
await db.insert("users", { userId, streak: newStreak, lastPlay: now });
}
return newStreak;
});
In this updated version:
- We calculate the date of the last play and the current date, ignoring the time component.
- We only increment the streak if the last play was on a different day (yesterday or earlier) and within the 48-hour window.
- If the last play was today, we don't change the streak at all.
- We always update the
lastPlaytimestamp, even if the streak doesn't change.
This implementation ensures that:
- The streak only increases once per day, even if multiple games are played.
- The streak maintains its value for multiple games played on the same day.
- The streak still resets if more than 48 hours pass between plays.
Remember to call this updateStreak function after each game completion, as discussed in the previous answer. This approach leverages Convex's mutation functions (https://docs.convex.dev/functions/mutation-functions) to handle the streak logic server-side, ensuring data integrity and preventing client-side manipulation.