fix: add support for Clerk IDs and activity endpoint

- Update getUserProfile to accept both database ID and Clerk ID
- Update getUserReports to handle Clerk IDs
- Update getUserEvents to handle Clerk IDs
- Add getUserActivity endpoint to return timeline of user actions
- Add activity route GET /api/users/:id/activity
- Fix TypeScript errors with const assertions

This fixes 404 errors when frontend uses Clerk IDs to fetch user data
This commit is contained in:
2025-11-16 21:39:38 +00:00
parent c84bcb2fd6
commit bee80be87d
4 changed files with 200 additions and 8 deletions

View File

@@ -56,11 +56,14 @@ export class UserService {
}
/**
* Get user profile by database ID
* Get user profile by database ID or Clerk ID
*/
async getUserProfile(userId: string) {
// Check if this is a Clerk ID (starts with 'user_')
const isClerkId = userId.startsWith('user_');
const user = await prisma.user.findUnique({
where: { id: userId },
where: isClerkId ? { clerkId: userId } : { id: userId },
select: {
id: true,
clerkId: true,
@@ -142,11 +145,26 @@ export class UserService {
}
/**
* Get user's reports
* Get user's reports by database ID or Clerk ID
*/
async getUserReports(userId: string) {
// Check if this is a Clerk ID and convert to database ID
const isClerkId = userId.startsWith('user_');
let dbUserId = userId;
if (isClerkId) {
const user = await prisma.user.findUnique({
where: { clerkId: userId },
select: { id: true },
});
if (!user) {
throw new Error('User not found');
}
dbUserId = user.id;
}
const reports = await prisma.report.findMany({
where: { userId },
where: { userId: dbUserId },
include: {
environmentalConcerns: true,
verifications: true,
@@ -160,12 +178,27 @@ export class UserService {
}
/**
* Get user's events (both registered and organized)
* Get user's events (both registered and organized) by database ID or Clerk ID
*/
async getUserEvents(userId: string) {
// Check if this is a Clerk ID and convert to database ID
const isClerkId = userId.startsWith('user_');
let dbUserId = userId;
if (isClerkId) {
const user = await prisma.user.findUnique({
where: { clerkId: userId },
select: { id: true },
});
if (!user) {
throw new Error('User not found');
}
dbUserId = user.id;
}
// Get events user has registered for
const registeredEvents = await prisma.eventRegistration.findMany({
where: { userId },
where: { userId: dbUserId },
include: {
event: {
include: {
@@ -186,7 +219,7 @@ export class UserService {
// Get events user has organized
const organizedEvents = await prisma.event.findMany({
where: { organizerId: userId },
where: { organizerId: dbUserId },
include: {
organizer: {
select: {
@@ -237,6 +270,132 @@ export class UserService {
return updatedUser;
}
/**
* Get user's activity timeline
*/
async getUserActivity(userId: string) {
// Check if this is a Clerk ID and convert to database ID
const isClerkId = userId.startsWith('user_');
let dbUserId = userId;
if (isClerkId) {
const user = await prisma.user.findUnique({
where: { clerkId: userId },
select: { id: true },
});
if (!user) {
throw new Error('User not found');
}
dbUserId = user.id;
}
// Get user's reports
const reports = await prisma.report.findMany({
where: { userId: dbUserId },
select: {
id: true,
createdAt: true,
litterType: true,
quantity: true,
latitude: true,
longitude: true,
description: true,
},
orderBy: { createdAt: 'desc' },
take: 50,
});
// Get user's event registrations
const eventRegistrations = await prisma.eventRegistration.findMany({
where: { userId: dbUserId },
include: {
event: {
select: {
id: true,
title: true,
description: true,
scheduledDate: true,
latitude: true,
longitude: true,
locationName: true,
status: true,
},
},
},
orderBy: { registeredAt: 'desc' },
take: 50,
});
// Get user's organized events
const organizedEvents = await prisma.event.findMany({
where: { organizerId: dbUserId },
select: {
id: true,
title: true,
description: true,
createdAt: true,
scheduledDate: true,
latitude: true,
longitude: true,
locationName: true,
status: true,
},
orderBy: { createdAt: 'desc' },
take: 50,
});
// Combine and format activities
const activities = [
...reports.map(report => ({
id: report.id,
type: 'report' as const,
timestamp: report.createdAt || new Date(),
title: `Reported ${report.litterType} litter`,
description: report.description || `${report.quantity} amount of ${report.litterType}`,
location: {
latitude: Number(report.latitude),
longitude: Number(report.longitude),
},
})),
...eventRegistrations.map(reg => {
const activityType: 'event_attended' = 'event_attended';
return {
id: reg.event.id,
type: activityType,
timestamp: reg.registeredAt || new Date(),
title: reg.event.title,
description: reg.event.description,
location: {
latitude: Number(reg.event.latitude),
longitude: Number(reg.event.longitude),
locationName: reg.event.locationName,
},
};
}),
...organizedEvents.map(event => {
const activityType: 'event_created' | 'event_completed' =
event.status === 'completed' ? 'event_completed' : 'event_created';
return {
id: event.id,
type: activityType,
timestamp: event.createdAt || new Date(),
title: event.title,
description: event.description,
location: {
latitude: Number(event.latitude),
longitude: Number(event.longitude),
locationName: event.locationName,
},
};
}),
];
// Sort by timestamp descending
activities.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
return activities;
}
/**
* Calculate and update user impact score
* Impact score = (reports * 10) + (events attended * 20) + (events organized * 50)