Skip to content

Commit d36785a

Browse files
committed
feat(cards): add card analytics and view tracking
1 parent b9ddb38 commit d36785a

2 files changed

Lines changed: 67 additions & 7 deletions

File tree

apps/backend/src/routes/cards.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ export async function cardRoutes(app: FastifyInstance): Promise<void> {
6262
});
6363

6464
// ─── List Cards ───
65-
6665
app.get('/', async (request: FastifyRequest, reply: FastifyReply): Promise<CardResponse[] | void> => {
6766
const userId = (request.user as { id: string }).id;
6867
try {
@@ -108,7 +107,6 @@ export async function cardRoutes(app: FastifyInstance): Promise<void> {
108107
});
109108

110109
// ─── Delete Card ───
111-
112110
app.delete('/:id', async (request: FastifyRequest<{ Params: CardParams }>, reply: FastifyReply): Promise<void> => {
113111
const userId = (request.user as { id: string }).id;
114112
const { id } = request.params;
@@ -209,6 +207,10 @@ export async function cardRoutes(app: FastifyInstance): Promise<void> {
209207
}
210208
})
211209

210+
// TODO:
211+
// Determine view source dynamically (url, qr, app, etc.).
212+
// The shared card endpoint is currently used by multiple entry points,
213+
// so source should not be hardcoded to "link".
212214
//Get shared card
213215
app.get('/share/:slug', async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => {
214216
const paramsSlug = request.params.slug;
@@ -256,6 +258,7 @@ export async function cardRoutes(app: FastifyInstance): Promise<void> {
256258
}
257259
})
258260

261+
//Generates qr
259262
app.get('/:id/qr', async(request: FastifyRequest<{Params: {id: string}}>, reply:FastifyReply) => {
260263
const cardId = request.params.id
261264
const userId = request.user.id
@@ -274,18 +277,37 @@ export async function cardRoutes(app: FastifyInstance): Promise<void> {
274277
error: error.message,
275278
});
276279
}
277-
if (error?.code === 'QR_ENABLED') {
278-
return reply.status(404).send({
280+
if (error?.code === 'QR_DISABLED') {
281+
return reply.status(403).send({
279282
error: error.message,
280283
});
281284
}
282285
if (error?.code === 'QR_IMAGE') {
283-
return reply.status(403).send({
286+
return reply.status(500).send({
284287
error: error.message,
285288
});
286289
}
287290
app.log.error(error)
288291
handleDbError(error,request, reply)
289292
}
290293
})
294+
295+
//Get analytics
296+
app.get('/:id/analytics', async(request:FastifyRequest<{Params: {id:string}}>, reply: FastifyReply) => {
297+
const cardId = request.params.id
298+
const userId = request.user.id
299+
300+
try {
301+
const analytics = await cardService.cardAnalytics(app, userId,cardId)
302+
return reply.status(200).send(analytics)
303+
} catch (error:any) {
304+
if (error?.code === 'CARD_NOT_FOUND') {
305+
return reply.status(404).send({
306+
error: error.message,
307+
});
308+
}
309+
app.log.error(error)
310+
handleDbError(error , request, reply)
311+
}
312+
})
291313
}

apps/backend/src/services/cardService.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,6 @@ export async function getSharedCard(app:FastifyInstance, slug:string){
303303
return card
304304
}
305305

306-
307306
export async function genrateQr(app: FastifyInstance,userId:string, id: string){
308307
const card = await app.prisma.card.findFirst({
309308
where:{
@@ -330,7 +329,7 @@ export async function genrateQr(app: FastifyInstance,userId:string, id: string){
330329
if(!card.qrEnabled){
331330
throw Object.assign(
332331
new Error('QR is not availbled for this card'),
333-
{ code: 'QR_ENABLED' }
332+
{ code: 'QR_DISABLED' }
334333
);
335334
}
336335

@@ -347,4 +346,43 @@ export async function genrateQr(app: FastifyInstance,userId:string, id: string){
347346
return qrImage;
348347

349348

349+
}
350+
351+
//TODO:Add pagination
352+
export async function cardAnalytics(app: FastifyInstance, userId:string, id: string){
353+
const cardAnalytics = await app.prisma.card.findFirst({
354+
where: {
355+
id,
356+
userId
357+
},
358+
include: {
359+
views: {
360+
orderBy: {
361+
createdAt: 'desc'
362+
},
363+
include: {
364+
viewer : {
365+
select: {
366+
id:true,
367+
username: true,
368+
avatarUrl: true,
369+
displayName: true,
370+
role: true,
371+
accentColor: true
372+
}
373+
}
374+
}
375+
}
376+
},
377+
378+
})
379+
380+
if (!cardAnalytics) {
381+
throw Object.assign(
382+
new Error('Card not found'),
383+
{ code: 'CARD_NOT_FOUND' }
384+
);
385+
}
386+
387+
return cardAnalytics
350388
}

0 commit comments

Comments
 (0)