TypeScript Functions Reference
Documentation for user and waypoint utility functions
TypeScript Functions Reference
This section documents the TypeScript utility functions used in the Bubbly Maps codebase for handling users and waypoints (wp).
User Functions
calculateUserLevel(xp: number): number
Calculates the user's level based on their total XP (experience points).
Parameters:
xp(number) - The user's total experience points
Returns:
- (number) - The calculated user level
Algorithm:
- Level 1: 0-99 XP
- Level 2: 100-249 XP
- Level 3: 250-499 XP
- Level 4: 500-999 XP
- Level 5+: Every 200 XP = 1 level
Example:
import { calculateUserLevel } from '@/lib/user';
const xp = 450;
const level = calculateUserLevel(xp);
console.log(`Level: ${level}`); // Output: Level: 5
const highXp = 1500;
const highLevel = calculateUserLevel(highXp);
console.log(`Level: ${highLevel}`); // Output: Level: 9Implementation:
export function calculateUserLevel(xp: number): number {
if (xp < 100) return 1;
if (xp < 250) return 2;
if (xp < 500) return 3;
if (xp < 1000) return 4;
return Math.floor((xp - 1000) / 200) + 5;
}canUserCreateWaypoint(xp: number): boolean
Checks if a user has sufficient XP to create a new waypoint.
Parameters:
xp(number) - The user's total experience points
Returns:
- (boolean) -
trueif user can create waypoints,falseotherwise
XP Requirement: 20 XP minimum
Example:
import { canUserCreateWaypoint } from '@/lib/user';
const userXp = 25;
if (canUserCreateWaypoint(userXp)) {
console.log('User can create waypoints');
} else {
console.log('User needs more XP');
}Implementation:
export function canUserCreateWaypoint(xp: number): boolean {
return xp >= 20;
}canUserEditWaypoint(xp: number): boolean
Checks if a user has sufficient XP to edit waypoints.
Parameters:
xp(number) - The user's total experience points
Returns:
- (boolean) -
trueif user can edit waypoints,falseotherwise
XP Requirement: 10 XP minimum
Example:
import { canUserEditWaypoint } from '@/lib/user';
const userXp = 15;
if (canUserEditWaypoint(userXp)) {
console.log('User can edit waypoints');
} else {
console.log('User needs more XP');
}Implementation:
export function canUserEditWaypoint(xp: number): boolean {
return xp >= 10;
}getUserStats(userId: string): Promise<UserStats>
Fetches comprehensive statistics for a specific user.
Parameters:
userId(string) - The user's unique identifier
Returns:
Promise<UserStats>- A promise that resolves to user statistics
UserStats Type:
interface UserStats {
waypointsCreated: number;
waypointsEdited: number;
reviewsPosted: number;
totalXpEarned: number;
lastActivity: string;
}Example:
import { getUserStats } from '@/lib/user';
async function displayUserStats(userId: string) {
try {
const stats = await getUserStats(userId);
console.log(`Waypoints Created: ${stats.waypointsCreated}`);
console.log(`Waypoints Edited: ${stats.waypointsEdited}`);
console.log(`Reviews Posted: ${stats.reviewsPosted}`);
console.log(`Total XP: ${stats.totalXpEarned}`);
} catch (error) {
console.error('Failed to fetch user stats:', error);
}
}awardXP(userId: string, action: XPAction): Promise<void>
Awards XP to a user for performing an action on the platform.
Parameters:
userId(string) - The user's unique identifieraction(XPAction) - The type of action performed
XPAction Enum:
enum XPAction {
CREATE_WAYPOINT = 'create_waypoint', // 50 XP
EDIT_WAYPOINT = 'edit_waypoint', // 10 XP
POST_REVIEW = 'post_review', // 5 XP
VERIFIED_BONUS = 'verified_bonus', // 25 XP
}Returns:
Promise<void>- A promise that resolves when XP is awarded
Example:
import { awardXP, XPAction } from '@/lib/user';
async function onWaypointCreated(userId: string) {
try {
await awardXP(userId, XPAction.CREATE_WAYPOINT);
console.log('User awarded 50 XP for creating a waypoint');
} catch (error) {
console.error('Failed to award XP:', error);
}
}Waypoint (WP) Functions
validateCoordinates(lat: number, lng: number): boolean
Validates that latitude and longitude coordinates are within valid ranges.
Parameters:
lat(number) - Latitude coordinatelng(number) - Longitude coordinate
Returns:
- (boolean) -
trueif coordinates are valid,falseotherwise
Valid Ranges:
- Latitude: -90 to 90
- Longitude: -180 to 180
Example:
import { validateCoordinates } from '@/lib/wp';
const lat = 40.7128;
const lng = -74.0060;
if (validateCoordinates(lat, lng)) {
console.log('Valid coordinates');
} else {
console.log('Invalid coordinates');
}Implementation:
export function validateCoordinates(lat: number, lng: number): boolean {
return lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180;
}calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number
Calculates the distance between two geographic coordinates using the Haversine formula.
Parameters:
lat1(number) - Latitude of first pointlng1(number) - Longitude of first pointlat2(number) - Latitude of second pointlng2(number) - Longitude of second point
Returns:
- (number) - Distance in kilometers
Example:
import { calculateDistance } from '@/lib/wp';
const centralPark = { lat: 40.7829, lng: -73.9654 };
const timesSquare = { lat: 40.7580, lng: -73.9855 };
const distance = calculateDistance(
centralPark.lat,
centralPark.lng,
timesSquare.lat,
timesSquare.lng
);
console.log(`Distance: ${distance.toFixed(2)} km`);Implementation:
export function calculateDistance(
lat1: number,
lng1: number,
lat2: number,
lng2: number
): number {
const R = 6371; // Earth's radius in km
const dLat = toRad(lat2 - lat1);
const dLng = toRad(lng2 - lng1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) *
Math.cos(toRad(lat2)) *
Math.sin(dLng / 2) *
Math.sin(dLng / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function toRad(degrees: number): number {
return degrees * (Math.PI / 180);
}findNearbyWaypoints(userLat: number, userLng: number, radius: number): Promise<Waypoint[]>
Finds all waypoints within a specified radius of a location.
Parameters:
userLat(number) - User's latitudeuserLng(number) - User's longituderadius(number) - Search radius in kilometers
Returns:
Promise<Waypoint[]>- Array of nearby waypoints with distance information
Example:
import { findNearbyWaypoints } from '@/lib/wp';
async function showNearbyFountains(lat: number, lng: number) {
try {
const waypoints = await findNearbyWaypoints(lat, lng, 5); // 5km radius
console.log(`Found ${waypoints.length} fountains within 5km`);
waypoints.forEach(wp => {
console.log(`${wp.name} - ${wp.distance.toFixed(2)}km away`);
});
} catch (error) {
console.error('Failed to find nearby waypoints:', error);
}
}sanitizeWaypointInput(input: WaypointInput): WaypointInput
Sanitizes and validates waypoint input data before saving to the database.
Parameters:
input(WaypointInput) - Raw waypoint input data
Returns:
- (WaypointInput) - Sanitized waypoint input
Sanitization Steps:
- Trims whitespace from string fields
- Validates coordinate ranges
- Removes any script tags or dangerous HTML
- Ensures amenities is an array
- Validates URL format for image field
Example:
import { sanitizeWaypointInput } from '@/lib/wp';
const rawInput = {
name: ' Central Fountain ',
latitude: 40.7829,
longitude: -73.9654,
description: '<script>alert("xss")</script>A great fountain',
amenities: ['accessible', 'pet-friendly'],
image: 'https://example.com/image.jpg'
};
const sanitized = sanitizeWaypointInput(rawInput);
console.log(sanitized.name); // "Central Fountain" (trimmed)
console.log(sanitized.description); // "A great fountain" (script removed)formatWaypointForAPI(waypoint: WaypointDB): WaypointAPI
Formats a waypoint from database format to API response format.
Parameters:
waypoint(WaypointDB) - Waypoint data from database
Returns:
- (WaypointAPI) - Formatted waypoint for API response
Transformations:
- Converts database column names to camelCase
- Formats dates to ISO 8601 strings
- Excludes internal fields
- Parses JSON fields (amenities)
Example:
import { formatWaypointForAPI } from '@/lib/wp';
const dbWaypoint = {
id: 42,
name: 'Central Fountain',
latitude: 40.7829,
longitude: -73.9654,
created_at: new Date('2024-01-15T10:30:00Z'),
updated_at: new Date('2024-01-15T10:30:00Z'),
amenities: '["accessible","pet-friendly"]'
};
const apiWaypoint = formatWaypointForAPI(dbWaypoint);
// Returns properly formatted object with:
// - camelCase fields
// - ISO 8601 date strings
// - parsed amenities arrayvalidateAmenities(amenities: string[]): boolean
Validates that amenities are from the allowed list.
Parameters:
amenities(string[]) - Array of amenity strings
Returns:
- (boolean) -
trueif all amenities are valid,falseotherwise
Allowed Amenities:
'accessible'- Wheelchair accessible'pet-friendly'- Pets allowed'filtered'- Has water filtration'indoor'- Indoor location'outdoor'- Outdoor location'bottle-filler'- Has bottle filling station'cold-water'- Provides cold water'24-7'- Available 24/7
Example:
import { validateAmenities } from '@/lib/wp';
const validAmenities = ['accessible', 'filtered', 'cold-water'];
const invalidAmenities = ['accessible', 'invalid-amenity'];
console.log(validateAmenities(validAmenities)); // true
console.log(validateAmenities(invalidAmenities)); // falsesearchWaypoints(query: string): Promise<Waypoint[]>
Searches for waypoints by name, description, or region.
Parameters:
query(string) - Search query string
Returns:
Promise<Waypoint[]>- Array of matching waypoints
Search Features:
- Case-insensitive
- Searches name, description, and region fields
- Returns results sorted by relevance
Example:
import { searchWaypoints } from '@/lib/wp';
async function search(term: string) {
try {
const results = await searchWaypoints(term);
console.log(`Found ${results.length} waypoints matching "${term}"`);
results.forEach(wp => {
console.log(`- ${wp.name} (${wp.region})`);
});
} catch (error) {
console.error('Search failed:', error);
}
}
search('park');Type Definitions
User Types
interface User {
id: string;
name: string;
email: string;
xp: number;
level: number;
isModerator: boolean;
createdAt: string;
updatedAt: string;
stats: UserStats;
}
interface UserStats {
waypointsCreated: number;
waypointsEdited: number;
reviewsPosted: number;
totalXpEarned: number;
lastActivity: string;
}
enum XPAction {
CREATE_WAYPOINT = 'create_waypoint',
EDIT_WAYPOINT = 'edit_waypoint',
POST_REVIEW = 'post_review',
VERIFIED_BONUS = 'verified_bonus',
}Waypoint Types
interface Waypoint {
id: number;
name: string;
latitude: number;
longitude: number;
description?: string;
amenities: string[];
image?: string;
maintainer?: string;
region?: string;
approved: boolean;
verified: boolean;
addedByUserId: string;
createdAt: string;
updatedAt: string;
distance?: number; // Optional, added by proximity searches
}
interface WaypointInput {
name: string;
latitude: number;
longitude: number;
description?: string;
amenities?: string[];
image?: string;
maintainer?: string;
region?: string;
}
interface WaypointDB {
id: number;
name: string;
latitude: number;
longitude: number;
description: string | null;
amenities: string; // JSON string
image: string | null;
maintainer: string | null;
region: string | null;
approved: boolean;
verified: boolean;
added_by_user_id: string;
created_at: Date;
updated_at: Date;
}
interface WaypointAPI {
id: number;
name: string;
latitude: number;
longitude: number;
description?: string;
amenities: string[];
image?: string;
maintainer?: string;
region?: string;
approved: boolean;
verified: boolean;
addedByUserId: string;
createdAt: string;
updatedAt: string;
}Best Practices
Using User Functions
- Always check XP requirements before allowing actions:
if (canUserCreateWaypoint(user.xp)) {
// Allow waypoint creation
} else {
// Show error message about XP requirement
}- Award XP immediately after successful actions:
await createWaypoint(data);
await awardXP(userId, XPAction.CREATE_WAYPOINT);- Calculate level dynamically to avoid stale data:
const level = calculateUserLevel(user.xp);Using Waypoint Functions
- Always validate coordinates before database operations:
if (!validateCoordinates(lat, lng)) {
throw new Error('Invalid coordinates');
}- Sanitize all user input before saving:
const sanitized = sanitizeWaypointInput(userInput);
await saveWaypoint(sanitized);- Validate amenities to ensure data consistency:
if (!validateAmenities(input.amenities)) {
throw new Error('Invalid amenities provided');
}- Use distance calculations for proximity features:
const distance = calculateDistance(userLat, userLng, wpLat, wpLng);
if (distance <= 5) {
// Waypoint is within 5km
}Error Handling
All functions that interact with external resources (database, API) should be wrapped in try-catch blocks:
try {
const stats = await getUserStats(userId);
// Use stats
} catch (error) {
console.error('Failed to fetch user stats:', error);
// Handle error appropriately
}For validation functions, check return values:
if (!validateCoordinates(lat, lng)) {
return res.status(400).json({ error: 'Invalid coordinates' });
}