Skip to content

Commit ee4c70d

Browse files
authored
Feat/stats (#251)
2 parents 1c6bf49 + 9adf061 commit ee4c70d

File tree

46 files changed

+682
-112
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+682
-112
lines changed

src/actions/answers/answer.ts

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ interface AnswerQuestionResponse {
2020

2121
const findOrCreateUserStreak = async (userUid: string) => {
2222
const streak = await prisma.streaks.findUnique({
23-
where: { userUid },
23+
where: { userUid }
2424
});
2525

2626
if (streak) return streak;
@@ -33,14 +33,14 @@ const findOrCreateUserStreak = async (userUid: string) => {
3333
createdAt: new Date(),
3434
currentstreakCount: 0,
3535
longestStreak: 0,
36-
updatedAt: new Date(),
37-
},
36+
updatedAt: new Date()
37+
}
3838
});
3939
};
4040

4141
const findQuestion = async (questionUid: string) => {
4242
const question = await prisma.questions.findUnique({
43-
where: { uid: questionUid },
43+
where: { uid: questionUid }
4444
});
4545

4646
if (!question) {
@@ -54,8 +54,8 @@ const findExistingAnswer = async (userUid: string, questionUid: string) => {
5454
return prisma.answers.findFirst({
5555
where: {
5656
user: { is: { uid: userUid } },
57-
question: { is: { uid: questionUid } },
58-
},
57+
question: { is: { uid: questionUid } }
58+
}
5959
});
6060
};
6161

@@ -116,8 +116,8 @@ const updateStreakDates = async (
116116
streakEnd: newStreakEnd,
117117
currentstreakCount: newCurrentStreak,
118118
longestStreak: newLongestStreak,
119-
updatedAt: new Date(),
120-
},
119+
updatedAt: new Date()
120+
}
121121
});
122122

123123
await tx.users.update({
@@ -126,8 +126,8 @@ const updateStreakDates = async (
126126
correctDailyStreak: newCurrentStreak,
127127
totalDailyStreak:
128128
currentStreak.totalDailyStreak +
129-
(isNextDay || (!isToday && correctAnswer) ? 1 : 0),
130-
},
129+
(isNextDay || (!isToday && correctAnswer) ? 1 : 0)
130+
}
131131
});
132132
};
133133

@@ -136,7 +136,7 @@ const handleStreakUpdates = async (
136136
dailyQuestion: boolean,
137137
{
138138
userUid,
139-
correctAnswer,
139+
correctAnswer
140140
}: {
141141
userUid: string;
142142
correctAnswer: boolean;
@@ -158,7 +158,7 @@ const updateOrCreateAnswer = async (
158158
questionUid,
159159
answerUid,
160160
correctAnswer,
161-
timeTaken,
161+
timeTaken
162162
}: {
163163
existingAnswer: any;
164164
userUid: string;
@@ -178,7 +178,7 @@ const updateOrCreateAnswer = async (
178178
) {
179179
return tx.answers.update({
180180
where: { uid: existingAnswer.uid },
181-
data: { userAnswerUid: answerUid, correctAnswer, timeTaken },
181+
data: { userAnswerUid: answerUid, correctAnswer, timeTaken }
182182
});
183183
}
184184
return existingAnswer;
@@ -191,59 +191,80 @@ const updateOrCreateAnswer = async (
191191
question: { connect: { uid: questionUid } },
192192
userAnswerUid: answerUid,
193193
correctAnswer,
194-
timeTaken,
195-
},
194+
timeTaken
195+
}
196196
});
197197
};
198198

199+
/**
200+
* Gets the total number of questions the user has answered.
201+
*
202+
* @param userUid
203+
* @returns
204+
*/
205+
export const getTotalQuestionCount = async (userUid: string) => {
206+
if (!userUid) {
207+
return null;
208+
}
209+
210+
const questions = await prisma.answers.count({
211+
where: {
212+
userUid
213+
}
214+
});
215+
216+
revalidateTag('statistics');
217+
218+
return questions;
219+
};
220+
199221
export async function answerQuestion({
200222
questionUid,
201223
answerUid,
202224
userUid,
203-
timeTaken,
225+
timeTaken
204226
}: AnswerQuestionInput): Promise<AnswerQuestionResponse> {
205-
try {
206-
const question = await findQuestion(questionUid);
207-
const correctAnswer = question.correctAnswer === answerUid;
208-
const existingAnswer = await findExistingAnswer(userUid, questionUid);
209-
210-
const { userData, userAnswer } = await prisma.$transaction(async (tx) => {
211-
// Only update streaks if this is a new answer
212-
if (!existingAnswer) {
213-
await handleStreakUpdates(tx, question.dailyQuestion, {
214-
userUid,
215-
correctAnswer,
216-
});
217-
}
218-
219-
// Get updated user data
220-
const userData = await tx.users.findUnique({
221-
where: { uid: userUid },
222-
});
227+
const question = await findQuestion(questionUid);
228+
const correctAnswer = question.correctAnswer === answerUid;
229+
const existingAnswer = await findExistingAnswer(userUid, questionUid);
223230

224-
// Handle answer creation or update
225-
const userAnswer = await updateOrCreateAnswer(tx, {
226-
existingAnswer,
231+
const { userData, userAnswer } = await prisma.$transaction(async (tx) => {
232+
// Only update streaks if this is a new answer
233+
if (!existingAnswer) {
234+
await handleStreakUpdates(tx, question.dailyQuestion, {
227235
userUid,
228-
questionUid,
229-
answerUid,
230-
correctAnswer,
231-
timeTaken,
236+
correctAnswer
232237
});
238+
}
233239

234-
return { userData, userAnswer };
240+
// Get updated user data
241+
const userData = await tx.users.findUnique({
242+
where: { uid: userUid }
235243
});
236244

237-
revalidateTag(`leaderboard-${questionUid}`);
238-
revalidateTag(`user-has-answered-daily-question-${questionUid}`);
239-
240-
return {
245+
// Handle answer creation or update
246+
const userAnswer = await updateOrCreateAnswer(tx, {
247+
existingAnswer,
248+
userUid,
249+
questionUid,
250+
answerUid,
241251
correctAnswer,
242-
userAnswer,
243-
userData,
244-
};
245-
} catch (error) {
246-
console.error(error);
247-
throw error;
248-
}
252+
timeTaken
253+
});
254+
255+
return { userData, userAnswer };
256+
});
257+
258+
// revalidate leaderboard
259+
revalidateTag(`leaderboard-${questionUid}`);
260+
// revalidate if the user has answered the streak
261+
revalidateTag(`user-has-answered-daily-question-${questionUid}`);
262+
// revalidate the user's statistics
263+
revalidateTag(`statistics`);
264+
265+
return {
266+
correctAnswer,
267+
userAnswer,
268+
userData
269+
};
249270
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use server';
2+
import { openai } from '@/lib/open-ai';
3+
4+
/**
5+
*
6+
* @param opts
7+
* @returns
8+
*/
9+
export const analyseCurrentStats = async (opts: {}) => {};
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
'use server';
2+
import { StatsChartData } from '@/types/Stats';
3+
import { prisma } from '@/utils/prisma';
4+
import { revalidateTag } from 'next/cache';
5+
6+
/**
7+
* Method to get all the question data for the user and return it
8+
* in a format to display in a chart.
9+
*
10+
* We return the data in the following format:
11+
* [month]: { totalQuestions: number, tags: string[], tagCounts: Record<string, number> }
12+
*/
13+
export const getStatsChartData = async (opts: {
14+
userUid: string;
15+
to: string;
16+
from: string;
17+
}) => {
18+
const { userUid, to, from } = opts;
19+
20+
if (!userUid) {
21+
return null;
22+
}
23+
24+
const questions = await prisma.answers.findMany({
25+
where: {
26+
userUid,
27+
createdAt: {
28+
gte: new Date(from),
29+
lte: new Date(to)
30+
}
31+
},
32+
include: {
33+
question: {
34+
select: {
35+
createdAt: true,
36+
tags: {
37+
include: {
38+
tag: true
39+
}
40+
}
41+
}
42+
}
43+
}
44+
});
45+
46+
const data: StatsChartData = {};
47+
48+
questions.forEach((answer) => {
49+
const month = answer.question.createdAt.toISOString().slice(0, 7);
50+
const tags = answer.question.tags.map((tag) => tag.tag.name);
51+
52+
if (data[month]) {
53+
data[month].totalQuestions++;
54+
tags.forEach((tag) => {
55+
data[month].tagCounts[tag] = (data[month].tagCounts[tag] || 0) + 1;
56+
});
57+
} else {
58+
const tagCounts: Record<string, number> = {};
59+
tags.forEach((tag) => {
60+
tagCounts[tag] = 1;
61+
});
62+
data[month] = { totalQuestions: 1, tagCounts };
63+
}
64+
});
65+
66+
revalidateTag('statistics');
67+
68+
return data;
69+
};
70+
71+
/**
72+
* Gets the total number of questions the user has answered.
73+
*
74+
* @param userUid
75+
* @returns
76+
*/
77+
export const getTotalQuestionCount = async (userUid: string) => {
78+
if (!userUid) {
79+
return null;
80+
}
81+
82+
const questions = await prisma.answers.count({
83+
where: {
84+
userUid
85+
}
86+
});
87+
88+
revalidateTag('statistics');
89+
90+
return questions;
91+
};
92+
93+
export const getTotalTimeTaken = async (userUid: string) => {
94+
if (!userUid) {
95+
return null;
96+
}
97+
98+
const answers = await prisma.answers.findMany({
99+
where: {
100+
userUid
101+
}
102+
});
103+
104+
const totalTime = answers.reduce((acc, answer) => {
105+
return acc + (answer.timeTaken || 0);
106+
}, 0);
107+
108+
revalidateTag('statistics');
109+
110+
return totalTime;
111+
};
112+
113+
export const getHighestScoringTag = async (userUid: string) => {
114+
if (!userUid) {
115+
return null;
116+
}
117+
118+
const answers = await prisma.answers.findMany({
119+
where: {
120+
userUid
121+
},
122+
include: {
123+
question: {
124+
include: {
125+
tags: {
126+
include: {
127+
tag: true
128+
}
129+
}
130+
}
131+
}
132+
}
133+
});
134+
135+
const tagCounts: Record<string, number> = {};
136+
137+
answers.forEach((answer) => {
138+
answer.question.tags.forEach((tag) => {
139+
const tagName = tag.tag.name;
140+
tagCounts[tagName] = (tagCounts[tagName] || 0) + 1;
141+
});
142+
});
143+
144+
const highestScoringTag = Object.keys(tagCounts).reduce((acc, tag) => {
145+
return tagCounts[tag] > tagCounts[acc] ? tag : acc;
146+
}, '');
147+
148+
revalidateTag('statistics');
149+
150+
return {
151+
tag: highestScoringTag,
152+
count: tagCounts[highestScoringTag]
153+
};
154+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client';
2+
import Hero from '@/components/global/hero';
3+
4+
export default function ClientPage({
5+
children
6+
}: {
7+
children: React.ReactNode;
8+
}) {
9+
return (
10+
<div>
11+
<Hero
12+
heading="All Questions"
13+
subheading=" Explore a diverse set of questions across multiple topics to enhance
14+
your knowledge."
15+
/>
16+
{children}
17+
</div>
18+
);
19+
}

0 commit comments

Comments
 (0)