TreasureTrails/backend/src/routes/users.ts

229 lines
6.5 KiB
TypeScript

import { Router, Response } from 'express';
import { prisma } from '../index';
import { authenticate, AuthRequest } from '../middleware/auth';
const router = Router();
router.get('/me', authenticate, async (req: AuthRequest, res: Response) => {
try {
const user = await prisma.user.findUnique({
where: { id: req.user!.id },
select: {
id: true,
email: true,
name: true,
screenName: true,
avatarUrl: true,
createdAt: true
}
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
console.error('Get user error:', error);
res.status(500).json({ error: 'Failed to get user' });
}
});
router.put('/me', authenticate, async (req: AuthRequest, res: Response) => {
try {
const { name, screenName, avatarUrl } = req.body;
const updated = await prisma.user.update({
where: { id: req.user!.id },
data: {
name: name || undefined,
screenName: screenName !== undefined ? screenName || null : undefined,
avatarUrl: avatarUrl !== undefined ? avatarUrl || null : undefined
},
select: {
id: true,
email: true,
name: true,
screenName: true,
avatarUrl: true,
createdAt: true
}
});
res.json(updated);
} catch (error) {
console.error('Update user error:', error);
res.status(500).json({ error: 'Failed to update user' });
}
});
router.get('/me/location-history', authenticate, async (req: AuthRequest, res: Response) => {
try {
const locations = await prisma.locationHistory.findMany({
where: { userId: req.user!.id },
include: {
game: {
select: { id: true, name: true }
}
},
orderBy: { recordedAt: 'desc' }
});
const games = await prisma.game.findMany({
where: {
teams: {
some: {
members: {
some: { userId: req.user!.id }
}
}
}
},
select: { id: true, name: true }
});
const locationByGame = games.map(game => {
const gameLocations = locations.filter(l => l.gameId === game.id);
return {
game: game,
locations: gameLocations,
locationCount: gameLocations.length
};
}).filter(g => g.locationCount > 0);
res.json({
totalLocations: locations.length,
byGame: locationByGame
});
} catch (error) {
console.error('Get location history error:', error);
res.status(500).json({ error: 'Failed to get location history' });
}
});
router.get('/me/games', authenticate, async (req: AuthRequest, res: Response) => {
try {
const memberships = await prisma.teamMember.findMany({
where: { userId: req.user!.id },
include: {
team: {
include: {
game: {
select: {
id: true,
name: true,
status: true,
startDate: true,
locationLat: true,
locationLng: true,
gameMasterId: true,
gameMaster: { select: { name: true } }
}
},
teamRoutes: {
include: {
route: {
include: {
routeLegs: {
orderBy: { sequenceNumber: 'asc' }
}
}
}
}
},
photoSubmissions: true
}
}
}
});
const gamesWithDetails = memberships.map(m => {
const team = m.team;
const game = team.game;
const teamRoute = team.teamRoutes[0];
const route = teamRoute?.route;
const photoSubmissions = team.photoSubmissions;
const routeLegs = route?.routeLegs || [];
const proofLocations = routeLegs.filter(leg =>
photoSubmissions.some(p => p.routeLegId === leg.id)
);
let totalDistance = 0;
if (game.locationLat && game.locationLng) {
let prevLat = game.locationLat;
let prevLng = game.locationLng;
for (const leg of routeLegs) {
if (leg.locationLat && leg.locationLng) {
const R = 6371;
const dLat = (leg.locationLat - prevLat) * Math.PI / 180;
const dLng = (leg.locationLng - prevLng) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(prevLat * Math.PI / 180) * Math.cos(leg.locationLat * Math.PI / 180) *
Math.sin(dLng/2) * Math.sin(dLng/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
totalDistance += R * c;
prevLat = leg.locationLat;
prevLng = leg.locationLng;
}
}
}
return {
gameId: game.id,
gameName: game.name,
gameStatus: game.status,
gameMaster: game.gameMaster.name,
startDate: game.startDate,
teamId: team.id,
teamName: team.name,
teamStatus: team.status,
routeId: route?.id || null,
routeName: route?.name || null,
routeColor: route?.color || null,
totalLegs: routeLegs.length,
totalDistance: Math.round(totalDistance * 100) / 100,
proofLocations: proofLocations.map(leg => ({
legNumber: leg.sequenceNumber,
description: leg.description,
locationLat: leg.locationLat,
locationLng: leg.locationLng,
hasPhotoProof: photoSubmissions.some(p => p.routeLegId === leg.id)
}))
};
});
res.json(gamesWithDetails);
} catch (error) {
console.error('Get user games error:', error);
res.status(500).json({ error: 'Failed to get user games' });
}
});
router.delete('/me/location-data', authenticate, async (req: AuthRequest, res: Response) => {
try {
await prisma.locationHistory.deleteMany({
where: { userId: req.user!.id }
});
res.json({ message: 'Location data deleted' });
} catch (error) {
console.error('Delete location data error:', error);
res.status(500).json({ error: 'Failed to delete location data' });
}
});
router.delete('/me/account', authenticate, async (req: AuthRequest, res: Response) => {
try {
await prisma.user.delete({
where: { id: req.user!.id }
});
res.json({ message: 'Account deleted' });
} catch (error) {
console.error('Delete account error:', error);
res.status(500).json({ error: 'Failed to delete account' });
}
});
export default router;