Spaces:
Sleeping
Sleeping
| // import { Request, Response } from 'express'; | |
| // import { TrackModel } from '../models/TrackModel'; | |
| // import { TrackFeature, TrackFeatureCollection } from '../types/types'; | |
| // /** | |
| // * TrackController - Handles HTTP requests for public ski tracks | |
| // */ | |
| // export class TrackController { | |
| // private static parseOptionalString(value: unknown): string | undefined { | |
| // if (typeof value !== 'string') return undefined; | |
| // const trimmed = value.trim(); | |
| // if (!trimmed || trimmed === 'undefined' || trimmed === 'null') return undefined; | |
| // return trimmed; | |
| // } | |
| // private static normalizeStringList(values: unknown[]): string[] | undefined { | |
| // const normalized = values | |
| // .filter((v): v is string => typeof v === 'string') | |
| // .map(v => v.trim().toLowerCase()) | |
| // .filter(v => v.length > 0 && v !== 'undefined' && v !== 'null'); | |
| // if (normalized.length === 0) return undefined; | |
| // return Array.from(new Set(normalized)); | |
| // } | |
| // private static parseActivitiesParam(value: unknown): string[] | undefined { | |
| // if (Array.isArray(value)) { | |
| // return TrackController.normalizeStringList(value); | |
| // } | |
| // if (typeof value === 'string') { | |
| // return TrackController.normalizeStringList(value.split(',')); | |
| // } | |
| // return undefined; | |
| // } | |
| // /** | |
| // * GET /api/tracks | |
| // * Fetch all public tracks with optional location filtering and pagination | |
| // */ | |
| // static async getTracks(req: Request, res: Response): Promise<void> { | |
| // console.log('--- Start getTracks Controller ---'); | |
| // try { | |
| // const { lat, lng, radius, q, activities, difficulty } = req.query; | |
| // console.log(`Received query parameters: lat=${lat}, lng=${lng}, radius=${radius}, q=${q}, activities=${activities}, difficulty=${difficulty}`); | |
| // // Prepare filters for DB query | |
| // const filters: { activities?: string[], searchQuery?: string, difficulty?: string } = {}; | |
| // const parsedActivities = TrackController.parseActivitiesParam(activities); | |
| // if (parsedActivities) { | |
| // filters.activities = parsedActivities; | |
| // } | |
| // const parsedQuery = TrackController.parseOptionalString(q); | |
| // if (parsedQuery) { | |
| // filters.searchQuery = parsedQuery; | |
| // } | |
| // const parsedDifficulty = TrackController.parseOptionalString(difficulty); | |
| // if (parsedDifficulty) { | |
| // filters.difficulty = parsedDifficulty; | |
| // } | |
| // // Pagination (must be applied at DB level to avoid huge Supabase egress) | |
| // const page = Math.max(1, parseInt(req.query.page as string) || 1); | |
| // const requestedLimit = parseInt(req.query.limit as string) || 100; | |
| // const limit = Math.min(Math.max(1, requestedLimit), 200); | |
| // const offset = (page - 1) * limit; | |
| // const locationFilter = (lat && lng && radius) | |
| // ? { | |
| // lat: parseFloat(lat as string), | |
| // lng: parseFloat(lng as string), | |
| // radiusKm: parseFloat(radius as string), | |
| // } | |
| // : undefined; | |
| // // Fetch filtered + paginated tracks from DB | |
| // const { tracks, total } = await TrackModel.findWithFiltersPaginated( | |
| // filters, | |
| // { limit, offset }, | |
| // locationFilter | |
| // ); | |
| // console.log(`Fetched ${tracks.length} tracks from the database (after DB filtering + pagination). Total matching: ${total}.`); | |
| // // Convert tracks to GeoJSON features | |
| // const features: TrackFeature[] = tracks.map(track => { | |
| // return { | |
| // type: 'Feature', | |
| // geometry: typeof track.geojson === 'string' | |
| // ? JSON.parse(track.geojson as any) | |
| // : track.geojson, | |
| // properties: { | |
| // id: track.id, | |
| // external_id: track.external_id, | |
| // name: track.name, | |
| // description: track.description, | |
| // ...track.properties, | |
| // difficulty: track.difficulty, | |
| // difficulty_convention: track.difficulty_convention, | |
| // type: track.type, | |
| // uses: track.uses, | |
| // ref: track.ref, | |
| // oneway: track.oneway, | |
| // gladed: track.gladed, | |
| // patrolled: track.patrolled, | |
| // lit: track.lit, | |
| // grooming: track.grooming, | |
| // status: track.status, | |
| // websites: track.websites, | |
| // wikidata_id: track.wikidata_id, | |
| // elevation_profile: track.elevation_profile | |
| // } | |
| // }; | |
| // }); | |
| // const totalPages = Math.ceil(total / limit); | |
| // console.log(`Response features count: ${features.length}. Total pages: ${totalPages}.`); | |
| // const response: TrackFeatureCollection & { pagination: any } = { | |
| // type: 'FeatureCollection', | |
| // features, | |
| // pagination: { | |
| // total, | |
| // page, | |
| // limit, | |
| // pages: totalPages | |
| // } | |
| // }; | |
| // res.json(response); | |
| // console.log('--- End getTracks Controller (Success) ---'); | |
| // } catch (err) { | |
| // console.error('Error in getTracks:', err); | |
| // res.status(500).json({ error: 'Server Error' }); | |
| // console.log('--- End getTracks Controller (Error) ---'); | |
| // } | |
| // } | |
| // /** | |
| // * GET /api/tracks/:id | |
| // * Fetch a single track by ID | |
| // */ | |
| // static async getTrackById(req: Request, res: Response): Promise<void> { | |
| // console.log('--- Start getTrackById Controller ---'); | |
| // try { | |
| // const id = parseInt(req.params.id); | |
| // console.log(`Fetching track with ID: ${id}`); | |
| // const track = await TrackModel.findById(id); | |
| // if (!track) { | |
| // console.log(`Track with ID ${id} not found.`); | |
| // res.status(404).json({ error: 'Track not found' }); | |
| // return; | |
| // } | |
| // const feature: TrackFeature = { | |
| // type: 'Feature', | |
| // geometry: typeof track.geojson === 'string' | |
| // ? JSON.parse(track.geojson) | |
| // : track.geojson, | |
| // properties: { | |
| // id: track.id, | |
| // name: track.name, | |
| // description: track.description, | |
| // ...track.properties, | |
| // difficulty: track.difficulty, | |
| // type: track.type | |
| // } | |
| // }; | |
| // res.json(feature); | |
| // console.log('--- End getTrackById Controller (Success) ---'); | |
| // } catch (err) { | |
| // console.error('Error in getTrackById:', err); | |
| // res.status(500).json({ error: 'Server Error' }); | |
| // console.log('--- End getTrackById Controller (Error) ---'); | |
| // } | |
| // } | |
| // } | |
| import { Request, Response } from 'express'; | |
| import { TrackModel } from '../models/TrackModel'; | |
| import { TrackFeature, TrackFeatureCollection } from '../types/types'; | |
| import { getDistanceFromLatLonInKm } from '../utils/geoUtils'; | |
| /** | |
| * TrackController - Handles HTTP requests for public ski tracks | |
| */ | |
| export class TrackController { | |
| /** | |
| * GET /api/tracks | |
| * Fetch all public tracks with optional location filtering and pagination | |
| */ | |
| static async getTracks(req: Request, res: Response): Promise<void> { | |
| console.log('--- Start getTracks Controller ---'); | |
| try { | |
| const { lat, lng, radius } = req.query; | |
| console.log(`Received query parameters: lat=${lat}, lng=${lng}, radius=${radius}`); | |
| const tracks = await TrackModel.findAll(); | |
| console.log(`Fetched ${tracks.length} tracks from the database.`); | |
| // Convert tracks to GeoJSON features | |
| let features: TrackFeature[] = tracks.map(track => { | |
| return { | |
| type: 'Feature', | |
| geometry: typeof track.geojson === 'string' | |
| ? JSON.parse(track.geojson) | |
| : track.geojson, | |
| properties: { | |
| id: track.id, | |
| name: track.name, | |
| description: track.description, | |
| ...track.properties, | |
| difficulty: track.difficulty, | |
| type: track.type | |
| } | |
| }; | |
| }); | |
| console.log(`Initial features count after mapping: ${features.length}`); | |
| // Filter by location if provided | |
| if (lat && lng && radius) { | |
| console.log('Location filtering is active.'); | |
| const centerLat = parseFloat(lat as string); | |
| const centerLng = parseFloat(lng as string); | |
| const radiusKm = parseFloat(radius as string); | |
| console.log(`Filter criteria: Center Lat=${centerLat}, Lng=${centerLng}, Radius=${radiusKm} km.`); | |
| const initialFeatureCount = features.length; | |
| features = features.filter(feature => { | |
| if (!feature.geometry || !feature.geometry.coordinates || feature.geometry.coordinates.length === 0) { | |
| return false; | |
| } | |
| // Use the first point of the track for distance check | |
| // GeoJSON coordinates are [lng, lat] | |
| const trackPoint = feature.geometry.coordinates[0]; | |
| const trackLng = trackPoint[0]; | |
| const trackLat = trackPoint[1]; | |
| const dist = getDistanceFromLatLonInKm(centerLat, centerLng, trackLat, trackLng); | |
| return dist <= radiusKm; | |
| }); | |
| console.log(`Features remaining after location filter: ${features.length}. Filtered out ${initialFeatureCount - features.length} features.`); | |
| } else { | |
| console.log('No location filter applied.'); | |
| } | |
| // Pagination | |
| const page = parseInt(req.query.page as string) || 1; | |
| const limit = parseInt(req.query.limit as string) || 100; | |
| const startIndex = (page - 1) * limit; | |
| const endIndex = page * limit; | |
| console.log(`Pagination parameters: Page=${page}, Limit=${limit}. Total features: ${features.length}`); | |
| console.log(`Slicing features from index ${startIndex} to ${endIndex}.`); | |
| const paginatedFeatures = features.slice(startIndex, endIndex); | |
| const totalPages = Math.ceil(features.length / limit); | |
| console.log(`Response features count: ${paginatedFeatures.length}. Total pages: ${totalPages}.`); | |
| const response: TrackFeatureCollection & { pagination: any } = { | |
| type: 'FeatureCollection', | |
| features: paginatedFeatures, | |
| pagination: { | |
| total: features.length, | |
| page, | |
| limit, | |
| pages: totalPages | |
| } | |
| }; | |
| res.json(response); | |
| console.log('--- End getTracks Controller (Success) ---'); | |
| } catch (err) { | |
| console.error('Error in getTracks:', err); | |
| res.status(500).json({ error: 'Server Error' }); | |
| console.log('--- End getTracks Controller (Error) ---'); | |
| } | |
| } | |
| /** | |
| * GET /api/tracks/:id | |
| * Fetch a single track by ID | |
| */ | |
| static async getTrackById(req: Request, res: Response): Promise<void> { | |
| console.log('--- Start getTrackById Controller ---'); | |
| try { | |
| const id = parseInt(req.params.id); | |
| console.log(`Fetching track with ID: ${id}`); | |
| const track = await TrackModel.findById(id); | |
| if (!track) { | |
| console.log(`Track with ID ${id} not found.`); | |
| res.status(404).json({ error: 'Track not found' }); | |
| return; | |
| } | |
| const feature: TrackFeature = { | |
| type: 'Feature', | |
| geometry: typeof track.geojson === 'string' | |
| ? JSON.parse(track.geojson) | |
| : track.geojson, | |
| properties: { | |
| id: track.id, | |
| name: track.name, | |
| description: track.description, | |
| ...track.properties, | |
| difficulty: track.difficulty, | |
| type: track.type | |
| } | |
| }; | |
| res.json(feature); | |
| console.log('--- End getTrackById Controller (Success) ---'); | |
| } catch (err) { | |
| console.error('Error in getTrackById:', err); | |
| res.status(500).json({ error: 'Server Error' }); | |
| console.log('--- End getTrackById Controller (Error) ---'); | |
| } | |
| } | |
| } |