mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
17245c5a8c
The optimistic mark/unmark updates bumped the country total but never the per-continent counts, so the continent column froze until a full reload. Move the country to continent map into @trek/shared (single source for server and client) and adjust the matching continent count at every optimistic site: the country confirm flow plus the choose / region mark and region unmark handlers.
97 lines
4.7 KiB
TypeScript
97 lines
4.7 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
/**
|
|
* Atlas API contract — single source of truth for the /api/addons/atlas endpoints
|
|
* (visited countries/regions, region GeoJSON, and the travel bucket list).
|
|
*
|
|
* Parity note: unlike the journey addon, the legacy atlas route is NOT gated by
|
|
* an addon-enabled check (app.ts mounts it without one), so the migration does
|
|
* not add a gate either — adding one would be a breaking 404.
|
|
*
|
|
* Stats, visited-regions and GeoJSON are wide, externally-derived shapes kept as
|
|
* open records; the request schemas and the bespoke 400/404 controller messages
|
|
* pin the parts the client depends on.
|
|
*/
|
|
|
|
const open = z.record(z.string(), z.unknown());
|
|
|
|
export const markRegionRequestSchema = z.object({
|
|
name: z.string().min(1),
|
|
country_code: z.string().min(1),
|
|
});
|
|
export type MarkRegionRequest = z.infer<typeof markRegionRequestSchema>;
|
|
|
|
export const createBucketItemRequestSchema = z.object({
|
|
name: z.string().min(1),
|
|
lat: z.number().nullable().optional(),
|
|
lng: z.number().nullable().optional(),
|
|
country_code: z.string().nullable().optional(),
|
|
notes: z.string().nullable().optional(),
|
|
target_date: z.string().nullable().optional(),
|
|
});
|
|
export type CreateBucketItemRequest = z.infer<
|
|
typeof createBucketItemRequestSchema
|
|
>;
|
|
|
|
export const updateBucketItemRequestSchema = z.object({
|
|
name: z.string().optional(),
|
|
notes: z.string().optional(),
|
|
lat: z.number().nullable().optional(),
|
|
lng: z.number().nullable().optional(),
|
|
country_code: z.string().nullable().optional(),
|
|
target_date: z.string().nullable().optional(),
|
|
});
|
|
export type UpdateBucketItemRequest = z.infer<
|
|
typeof updateBucketItemRequestSchema
|
|
>;
|
|
|
|
/** A bucket-list item row (DB-shaped; kept open). */
|
|
export const bucketItemSchema = open;
|
|
|
|
export const bucketListResponseSchema = z.object({
|
|
items: z.array(bucketItemSchema),
|
|
});
|
|
export type BucketListResponse = z.infer<typeof bucketListResponseSchema>;
|
|
|
|
/** GeoJSON FeatureCollection (kept open — provider-derived geometry). */
|
|
export const regionGeoSchema = z.object({
|
|
type: z.literal('FeatureCollection'),
|
|
features: z.array(z.unknown()),
|
|
});
|
|
export type RegionGeo = z.infer<typeof regionGeoSchema>;
|
|
|
|
/**
|
|
* ISO 3166-1 alpha-2 country code → continent. Single source of truth for the
|
|
* Atlas continent breakdown, used by the server (stats aggregation) and the
|
|
* client (keeping the per-continent counts in sync on optimistic mark/unmark).
|
|
*/
|
|
export const CONTINENT_MAP: Record<string, string> = {
|
|
AF:'Asia',AL:'Europe',DZ:'Africa',AD:'Europe',AO:'Africa',AR:'South America',AM:'Asia',AU:'Oceania',AT:'Europe',AZ:'Asia',
|
|
BA:'Europe',BD:'Asia',BF:'Africa',BH:'Asia',BI:'Africa',BJ:'Africa',BN:'Asia',BO:'South America',
|
|
BR:'South America',BE:'Europe',BG:'Europe',BW:'Africa',
|
|
CA:'North America',CD:'Africa',CG:'Africa',CI:'Africa',CL:'South America',CM:'Africa',CN:'Asia',CO:'South America',
|
|
CR:'North America',CU:'North America',CV:'Africa',CY:'Europe',HR:'Europe',CZ:'Europe',
|
|
DJ:'Africa',DK:'Europe',DO:'North America',EC:'South America',EG:'Africa',EE:'Europe',ER:'Africa',ET:'Africa',
|
|
FI:'Europe',FR:'Europe',DE:'Europe',GE:'Asia',GH:'Africa',GN:'Africa',GR:'Europe',GT:'North America',
|
|
HN:'North America',HT:'North America',HU:'Europe',IS:'Europe',IN:'Asia',ID:'Asia',IR:'Asia',IQ:'Asia',
|
|
IE:'Europe',IL:'Asia',IT:'Europe',JM:'North America',JO:'Asia',JP:'Asia',KE:'Africa',KG:'Asia',KH:'Asia',
|
|
KR:'Asia',KW:'Asia',KZ:'Asia',LA:'Asia',LB:'Asia',LK:'Asia',LV:'Europe',LT:'Europe',LU:'Europe',LY:'Africa',
|
|
MA:'Africa',MD:'Europe',ME:'Europe',MG:'Africa',MK:'Europe',ML:'Africa',MM:'Asia',MN:'Asia',MR:'Africa',
|
|
MT:'Europe',MU:'Africa',MV:'Asia',MW:'Africa',MY:'Asia',MX:'North America',MZ:'Africa',
|
|
NA:'Africa',NE:'Africa',NI:'North America',NL:'Europe',NP:'Asia',NZ:'Oceania',NO:'Europe',OM:'Asia',
|
|
PA:'North America',PG:'Oceania',PK:'Asia',PE:'South America',PH:'Asia',PL:'Europe',PS:'Asia',
|
|
PT:'Europe',PY:'South America',QA:'Asia',RO:'Europe',RU:'Europe',RW:'Africa',SA:'Asia',SC:'Africa',
|
|
SD:'Africa',SG:'Asia',SI:'Europe',SK:'Europe',SN:'Africa',SO:'Africa',RS:'Europe',SV:'North America',
|
|
SY:'Asia',TG:'Africa',TJ:'Asia',TM:'Asia',TN:'Africa',TT:'North America',TW:'Asia',TZ:'Africa',
|
|
ZA:'Africa',SE:'Europe',CH:'Europe',TH:'Asia',TR:'Europe',UA:'Europe',UG:'Africa',UY:'South America',
|
|
UZ:'Asia',VE:'South America',AE:'Asia',GB:'Europe',US:'North America',VN:'Asia',XK:'Europe',
|
|
YE:'Asia',ZM:'Africa',ZW:'Africa',NG:'Africa',
|
|
HK:'Asia',MO:'Asia',SM:'Europe',VA:'Europe',MC:'Europe',LI:'Europe',GI:'Europe',PR:'North America',
|
|
};
|
|
|
|
/** Continent for an ISO alpha-2 country code; 'Other' when unknown. */
|
|
export function continentForCountry(code: string | null | undefined): string {
|
|
if (!code) return 'Other';
|
|
return CONTINENT_MAP[code.toUpperCase()] || 'Other';
|
|
}
|