Skip to content

Commit

Permalink
Add streak to backend
Browse files Browse the repository at this point in the history
  • Loading branch information
nkalupahana committed Jan 17, 2025
1 parent 27d1513 commit f98bd3c
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 0 deletions.
2 changes: 2 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { beacon } from "./analytics.js";
import { graniteLink } from "./granite.js";
import { search } from "./spotify.js";
import { getAudio, getImage } from "./storage.js";
import { calculateStreak } from "./streak.js";

const app = express();
initializeApp({
Expand Down Expand Up @@ -69,6 +70,7 @@ app.post("/getImage", getImage);
app.post("/getAudio", getAudio);

app.post("/gap", gapFund);
app.post("/streak", calculateStreak);
app.post("/spotify/search", search);

app.use(Sentry.Handlers.errorHandler());
Expand Down
67 changes: 67 additions & 0 deletions backend/src/streak.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { getDatabase } from "firebase-admin/database";
import { AnyMap, UserRequest, validateKeys } from "./helpers.js";
import { Response } from "express";
import { DateTime } from "luxon";
import AES from "crypto-js/aes.js";
import aesutf8 from "crypto-js/enc-utf8.js";

const FETCH_LIMIT = 100;

export const calculateStreak = async (req: UserRequest, res: Response) => {
const db = getDatabase();
const data = req.body;
const encryptionKey = await validateKeys(data.keys, db, req.user!.user_id);

if (!encryptionKey) {
res.send(400);
return;
}

const logRef = db.ref(req.user!.user_id + "/logs").orderByKey();
let logs: AnyMap = await (await logRef.limitToLast(FETCH_LIMIT).get()).val();
if (!logs || Object.keys(logs).length === 0) {
res.send({ streak: 0, danger: false });
return;
}

const today = DateTime.fromISO(data.currentDate);
let latestLog: AnyMap = JSON.parse(AES.decrypt(logs[Object.keys(logs).at(-1)!].data, encryptionKey).toString(aesutf8));
let top = DateTime.fromObject({ year: latestLog.year, month: latestLog.month, day: latestLog.day });

// If the top log is not today or yesterday, the streak is 0
if (top.toISODate() !== today.toISODate() && top.toISODate() !== today.minus({ days: 1 }).toISODate()) {
res.send({ streak: 0, danger: false });
return;
}
// If the top log is yesterday, the user is in danger of losing their streak.
let danger = top.toISODate() === today.minus({ days: 1 }).toISODate();

let streak = 1;
let running = true;
while (running && Object.keys(logs).length > 0) {
// Same general logic as `calculateStreak` in the frontend
// (max change of one day to continue streak)
const logKeys = Object.keys(logs).reverse();
for (const key of logKeys) {
const log = JSON.parse(AES.decrypt(logs[key].data, encryptionKey).toString(aesutf8));
if (top.day !== log.day || top.month !== log.month || top.year !== log.year) {
const logDT = DateTime.fromObject({ year: log.year, month: log.month, day: log.day });
if (logDT.toISODate() === top.minus({ days: 1 }).toISODate()) {
top = logDT;
++streak;
} else {
running = false;
break;
}
}
}

// If the streak is going and we've run out of logs, try fetch more
if (running) {
logs = await (await logRef.endBefore(Object.keys(logs)[0]).limitToLast(FETCH_LIMIT).get()).val();
}
}


res.send({ streak, danger });
};

0 comments on commit f98bd3c

Please sign in to comment.