Merge pull request 'payload-validation' (#34) from payload-validation into staging
Reviewed-on: #34
This commit is contained in:
commit
d5a5488b9d
.vscode
src
models
routes
types
utils
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -52,6 +52,8 @@
|
||||
"Typica",
|
||||
"Verte",
|
||||
"VSOP",
|
||||
"wineyard",
|
||||
"wineyards",
|
||||
"Yamahai",
|
||||
"Yuzu"
|
||||
]
|
||||
|
@ -4,7 +4,9 @@ import {
|
||||
Viticulture,
|
||||
BottleClosure,
|
||||
VintageOptions,
|
||||
StandardDrinks100ml
|
||||
StandardDrinks,
|
||||
WineRegion,
|
||||
WineVolume
|
||||
} from '../types'
|
||||
import { Alpha2Code } from 'i18n-iso-countries'
|
||||
import { CurrencyCode } from 'currency-codes-ts/dist/types'
|
||||
@ -18,13 +20,14 @@ export class Wine {
|
||||
public style: string, // bubbles+fizz, table, dessert, fortified, vermouth
|
||||
public characteristic: string, // light aromatic, textural, fruit forward, structural & savoury, powerful
|
||||
public country: Alpha2Code, // two-letter country codes defined in ISO 3166-1 (https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||
public region: string, // appellation, village, sub-region, vineyard
|
||||
public region: WineRegion, // appellation, village, sub-region, vineyard
|
||||
public name: string, // label
|
||||
public producerId: ObjectId, // product producer
|
||||
public varietal: string, // if more than one, list as 'blend'
|
||||
public vintage: number | VintageOptions, // year, nv (non-vintage) or mv (multi-vintage)
|
||||
public volume: WineVolume, // bottle volume
|
||||
public alcohol: number, // alcohol percentage
|
||||
public standardDrinks100ml: StandardDrinks100ml, // an amount of standard drinks per 100ml in AU, UK and US
|
||||
public standardDrinks: StandardDrinks, // an amount of standard drinks per 100ml and bottle in AU, UK and US
|
||||
public viticulture: Viticulture, // viticulture
|
||||
public sulfites: number, // parts per million
|
||||
public filtered: boolean, // is wine filtered (fined (egg or fish))
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
wineValidation,
|
||||
handleReqError,
|
||||
handleReqSuccess,
|
||||
alcoholToStandardDrinks
|
||||
alcoholToStandardDrinks,
|
||||
volumeToMl
|
||||
} from '../utils'
|
||||
import Joi from 'joi'
|
||||
|
||||
@ -76,7 +77,10 @@ winesRouter.post('/', async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
|
||||
wine.standardDrinks100ml = alcoholToStandardDrinks(wine.alcohol)
|
||||
wine.standardDrinks = alcoholToStandardDrinks(
|
||||
wine.alcohol,
|
||||
volumeToMl(wine.volume)
|
||||
)
|
||||
|
||||
const result = await collections.wines?.insertOne(wine)
|
||||
|
||||
|
@ -66,8 +66,7 @@ export enum VintageOptions {
|
||||
MV = 'mv'
|
||||
}
|
||||
|
||||
export interface StandardDrinks100ml {
|
||||
AU: number
|
||||
UK: number
|
||||
US: number
|
||||
export interface StandardDrinks {
|
||||
'100ml': { AU: number; UK: number; US: number }
|
||||
bottle: { AU: number; UK: number; US: number }
|
||||
}
|
||||
|
@ -16,3 +16,21 @@ export enum BottleClosure {
|
||||
CrownSeal = 'crown-seal',
|
||||
Screwcap = 'screwcap'
|
||||
}
|
||||
|
||||
export interface WineRegion {
|
||||
[key: string]:
|
||||
| string[]
|
||||
| { [key: string]: string[] | { [key: string]: string[] } }
|
||||
}
|
||||
|
||||
export enum WineVolume {
|
||||
'0.05L' = '0.05L',
|
||||
'0.187L' = '0.187L',
|
||||
'0.375L' = '0.375L',
|
||||
'0.5L' = '0.5L',
|
||||
'0.75L' = '0.75L',
|
||||
'1.5L' = '1.5L',
|
||||
'3L' = '3L',
|
||||
'6L' = '6L',
|
||||
'12L' = '12L'
|
||||
}
|
||||
|
@ -1,10 +1,36 @@
|
||||
import { StandardDrinks100ml } from '../types'
|
||||
import { StandardDrinks, WineVolume } from '../types'
|
||||
import { roundToOneDecimal } from './'
|
||||
|
||||
export const alcoholToStandardDrinks = (
|
||||
alcohol: number
|
||||
): StandardDrinks100ml => ({
|
||||
UK: roundToOneDecimal(10 * alcohol),
|
||||
AU: roundToOneDecimal(7.91 * alcohol),
|
||||
US: roundToOneDecimal(5.64 * alcohol)
|
||||
})
|
||||
alcohol: number,
|
||||
bottle: number
|
||||
): StandardDrinks => {
|
||||
const UK100ml = roundToOneDecimal(10 * alcohol)
|
||||
const AU100ml = roundToOneDecimal(7.91 * alcohol)
|
||||
const US100ml = roundToOneDecimal(5.64 * alcohol)
|
||||
|
||||
const bottleMultiplier = bottle / 100
|
||||
|
||||
return {
|
||||
'100ml': {
|
||||
UK: UK100ml,
|
||||
AU: AU100ml,
|
||||
US: US100ml
|
||||
},
|
||||
bottle: {
|
||||
UK: UK100ml * bottleMultiplier,
|
||||
AU: AU100ml * bottleMultiplier,
|
||||
US: US100ml * bottleMultiplier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const volumeToMl = (volume: WineVolume): number => {
|
||||
if (volume.endsWith('L')) {
|
||||
const volumeMl = volume.replace('L', '')
|
||||
|
||||
return Number(volumeMl) * 1000
|
||||
}
|
||||
|
||||
throw new Error('Not supported volume type')
|
||||
}
|
||||
|
@ -3,3 +3,4 @@ export * from './nostr'
|
||||
export * from './route'
|
||||
export * from './utils'
|
||||
export * from './alcohol'
|
||||
export * from './wine'
|
||||
|
@ -1,2 +1,5 @@
|
||||
export const roundToOneDecimal = (number: number) =>
|
||||
Math.round(number * 10) / 10
|
||||
|
||||
export const isObject = (item: unknown) =>
|
||||
typeof item === 'object' && !Array.isArray(item) && item !== null
|
||||
|
@ -3,8 +3,11 @@ import {
|
||||
WineType,
|
||||
VintageOptions,
|
||||
BottleClosure,
|
||||
Viticulture
|
||||
Viticulture,
|
||||
WineRegion,
|
||||
WineVolume
|
||||
} from '../../types'
|
||||
import { wineRegionsMap, isObject } from '../'
|
||||
|
||||
export const wineValidation = (data: unknown): Joi.ValidationResult =>
|
||||
Joi.object({
|
||||
@ -17,18 +20,256 @@ export const wineValidation = (data: unknown): Joi.ValidationResult =>
|
||||
style: Joi.string().required(),
|
||||
characteristic: Joi.string().required(),
|
||||
country: Joi.string().length(2),
|
||||
region: Joi.string().required(),
|
||||
region: Joi.alternatives()
|
||||
.try(
|
||||
Joi.string().custom((value, helper) => {
|
||||
if (value) {
|
||||
if (helper.state.ancestors) {
|
||||
const { country } = helper.state.ancestors[0]
|
||||
|
||||
if (country) {
|
||||
const regionMap = wineRegionsMap[country]
|
||||
|
||||
if (regionMap) {
|
||||
const regions = Array.isArray(regionMap)
|
||||
? regionMap
|
||||
: Object.keys(regionMap)
|
||||
|
||||
if (!regions.includes(value)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`"region" contains an invalid value. Valid values for ${country} are [${regions.join(', ')}]`
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}),
|
||||
Joi.object().custom(
|
||||
(
|
||||
value: {
|
||||
[key: string]:
|
||||
| string
|
||||
| { [key: string]: string | { [key: string]: string } }
|
||||
},
|
||||
helper: Joi.CustomHelpers<unknown>
|
||||
) => {
|
||||
// return if no value
|
||||
if (!value) {
|
||||
return value
|
||||
}
|
||||
// return if no state ancestors
|
||||
if (!helper.state.ancestors) {
|
||||
return value
|
||||
}
|
||||
|
||||
const country: string = helper.state.ancestors[0].country
|
||||
|
||||
// return if no country
|
||||
if (!country) {
|
||||
return value
|
||||
}
|
||||
|
||||
const regionMap = wineRegionsMap[country]
|
||||
|
||||
// return if no region map
|
||||
if (!regionMap) {
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Region
|
||||
*/
|
||||
// list of supported regions
|
||||
const regions = Array.isArray(regionMap)
|
||||
? regionMap
|
||||
: Object.keys(regionMap)
|
||||
|
||||
const providedRegions = Object.keys(value)
|
||||
|
||||
// check if multiple regions provided
|
||||
if (providedRegions.length !== 1) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`"region" contains an invalid value. Valid values a single string or a region object`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const returnCustomMessage = (
|
||||
map: string[],
|
||||
options: string[]
|
||||
): Joi.ErrorReport =>
|
||||
helper.message({
|
||||
custom: Joi.expression(
|
||||
`"region" contains an invalid value. Valid values for ${map.join(' -> ')} are [${options.join(', ')}]`
|
||||
)
|
||||
})
|
||||
|
||||
const providedRegion = providedRegions[0]
|
||||
|
||||
// check if provided region is in list of supported regions
|
||||
if (!regions.includes(providedRegion)) {
|
||||
return returnCustomMessage([country], regions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subregion
|
||||
*/
|
||||
// list of supported subregions
|
||||
const subRegions: string[] = Object.keys(
|
||||
(regionMap as WineRegion)[providedRegion]
|
||||
)
|
||||
|
||||
const providedSubRegion:
|
||||
| string
|
||||
| { [key: string]: string | { [key: string]: string } } =
|
||||
value[providedRegion]
|
||||
|
||||
// return if provided subregion is not a string or an object
|
||||
if (
|
||||
typeof providedSubRegion !== 'string' &&
|
||||
!isObject(providedSubRegion)
|
||||
) {
|
||||
return returnCustomMessage([country, providedRegion], subRegions)
|
||||
}
|
||||
|
||||
// if providedSubRegion is a string, check if it is in the list of supported subregions
|
||||
if (
|
||||
typeof providedSubRegion === 'string' &&
|
||||
!subRegions.includes(providedSubRegion)
|
||||
) {
|
||||
return returnCustomMessage([country, providedRegion], subRegions)
|
||||
}
|
||||
// if providedSubRegion is an object, check if it is in the list of supported subregions
|
||||
else if (isObject(providedSubRegion)) {
|
||||
const providedSubRegions = Object.keys(providedSubRegion)
|
||||
const providedSubRegionName: string = providedSubRegions[0]
|
||||
|
||||
// return if provided multiple subregions or if provided subregion is not in the supported list
|
||||
if (
|
||||
providedSubRegions.length !== 1 ||
|
||||
!subRegions.includes(providedSubRegionName)
|
||||
) {
|
||||
return returnCustomMessage(
|
||||
[country, providedRegion],
|
||||
subRegions
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Village
|
||||
*/
|
||||
// list of supported villages
|
||||
const villages: string[] = Object.keys(
|
||||
(
|
||||
(regionMap as WineRegion)[providedRegion] as {
|
||||
[key: string]: string | { [key: string]: string[] }
|
||||
}
|
||||
)[providedSubRegionName]
|
||||
)
|
||||
|
||||
const providedVillage: string | { [key: string]: string } = (
|
||||
value[providedRegion] as {
|
||||
[key: string]: string
|
||||
}
|
||||
)[providedSubRegionName]
|
||||
|
||||
// return if provided village is not a string or an object
|
||||
if (
|
||||
typeof providedVillage !== 'string' &&
|
||||
!isObject(providedVillage)
|
||||
) {
|
||||
return returnCustomMessage(
|
||||
[country, providedRegion, providedSubRegionName],
|
||||
subRegions
|
||||
)
|
||||
}
|
||||
|
||||
// if village is a string, check if it is in the supported list
|
||||
if (
|
||||
typeof providedVillage === 'string' &&
|
||||
!villages.includes(providedVillage)
|
||||
) {
|
||||
return returnCustomMessage(
|
||||
[country, providedRegion, providedSubRegionName],
|
||||
villages
|
||||
)
|
||||
}
|
||||
// if providedVillage is an object, check if it is in the supported list
|
||||
else if (isObject(providedVillage)) {
|
||||
const providedVillages = Object.keys(providedVillage)
|
||||
|
||||
const providedVillageName: string = providedVillages[0]
|
||||
|
||||
// return if provided multiple villages or if provided village is not in the supported list
|
||||
if (
|
||||
providedVillages.length !== 1 ||
|
||||
!villages.includes(providedVillageName)
|
||||
) {
|
||||
return returnCustomMessage(
|
||||
[country, providedRegion, providedSubRegionName],
|
||||
villages
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wineyard
|
||||
*/
|
||||
// list of supported wineyards
|
||||
const wineyards: string[] = (
|
||||
(regionMap as WineRegion)[providedRegion] as {
|
||||
[key: string]: { [key: string]: string[] }
|
||||
}
|
||||
)[providedSubRegionName][providedVillageName]
|
||||
|
||||
const providedWineyard: string = (
|
||||
value[providedRegion] as {
|
||||
[key: string]: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
)[providedSubRegionName][providedVillageName]
|
||||
|
||||
// check if provided wineyard is in the supported list
|
||||
if (!wineyards.includes(providedWineyard)) {
|
||||
return returnCustomMessage(
|
||||
[
|
||||
country,
|
||||
providedRegion,
|
||||
providedSubRegionName,
|
||||
providedVillageName
|
||||
],
|
||||
wineyards
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
)
|
||||
)
|
||||
.allow('')
|
||||
.required(),
|
||||
name: Joi.string().required(),
|
||||
producerId: Joi.string().length(24).required(),
|
||||
varietal: Joi.string().required(),
|
||||
vintage: Joi.alternatives()
|
||||
.try(Joi.string().valid(...Object.values(VintageOptions)), Joi.number())
|
||||
.required(),
|
||||
volume: Joi.string()
|
||||
.valid(...Object.values(WineVolume))
|
||||
.required(),
|
||||
alcohol: Joi.number().min(0).max(0.99).required(),
|
||||
viticulture: Joi.string()
|
||||
.valid(...Object.values(Viticulture))
|
||||
.required(),
|
||||
sulfites: Joi.number().required(),
|
||||
sulfites: Joi.number().min(0).max(400),
|
||||
filtered: Joi.boolean().required(),
|
||||
vegan: Joi.boolean().required(),
|
||||
kosher: Joi.boolean().required(),
|
||||
|
689
src/utils/wine.ts
Normal file
689
src/utils/wine.ts
Normal file
@ -0,0 +1,689 @@
|
||||
import { WineRegion } from '../types'
|
||||
|
||||
export const wineRegionsMap: { [key: string]: string[] | WineRegion } = {
|
||||
IT: {
|
||||
Piedmont: {
|
||||
Langhe: {
|
||||
Barolo: ['La Morra', 'Castiglione Falletto', `Serralunga d'Alba`],
|
||||
Barbaresco: ['Neive', 'Alba', 'Treiso']
|
||||
},
|
||||
Monferrato: ['Gavi', 'Asti', 'Canelli']
|
||||
},
|
||||
Tuscany: [
|
||||
'Chianti',
|
||||
'Montalcino',
|
||||
'Montepulciano',
|
||||
'Scansano',
|
||||
'Carmignano'
|
||||
],
|
||||
Veneto: ['Valpolicella', 'Soave'],
|
||||
'Friuli-Venezia Giulia': [
|
||||
'Collio',
|
||||
'Colli Orientali del Friuli',
|
||||
'Friuli Grave del Friuli'
|
||||
],
|
||||
'Trentino-Alto Adige': ['Trentino', 'Alto Adige'],
|
||||
Lombardy: [],
|
||||
Franciacorta: [],
|
||||
Valtellina: [],
|
||||
'Emilia-Romagna': ['Lambrusco'],
|
||||
Umbria: ['Orvieto', 'Torgiano'],
|
||||
Lazio: ['Frascati'],
|
||||
Abruzzo: [],
|
||||
Molise: ['Biferno', 'Pentro'],
|
||||
Campania: ['Taurasi', 'Aglianico del Vulture', 'Fiano di Avellino'],
|
||||
Puglia: ['Primitivo di Manduria', 'Salice Salentino'],
|
||||
Basilicata: [],
|
||||
Calabria: ['Cirò', 'Greco di Bianco'],
|
||||
Sicily: ['Etna', 'Marsala', 'Pantelleria'],
|
||||
Sardinia: ['Vermentino di Gallura', 'Cannonau di Sardegna']
|
||||
},
|
||||
FR: {
|
||||
Alsace: [],
|
||||
Ardeche: [],
|
||||
Aquitaine: [],
|
||||
Bordeaux: {
|
||||
Médoc: ['Pauillac', 'Margaux', 'Saint-Estèphe', 'Saint-Julien'],
|
||||
'Saint-Émilion': [],
|
||||
Pomerol: [],
|
||||
Graves: ['Pessac-Léognan'],
|
||||
Sauternes: ['Barsac']
|
||||
},
|
||||
Burgundy: {
|
||||
Chablis: [],
|
||||
'Côte de Nuits': [
|
||||
'Gevrey-Chambertin',
|
||||
'Vosne-Romanée',
|
||||
'Nuits-Saint-Georges'
|
||||
],
|
||||
'Côte de Beaune': ['Meursault', 'Puligny-Montrachet', 'Beaune'],
|
||||
'Côte Chalonnaise': ['Mercurey'],
|
||||
Mâconnais: ['Pouilly-Fuissé'],
|
||||
Beaujolais: ['Morgon', 'Fleurie', 'Moulin a Vent']
|
||||
},
|
||||
Champagne: ['Montagne de Reims', 'Vallée de la Marne', 'Côte des Blancs'],
|
||||
'Loire Valley': [
|
||||
'Sancerre',
|
||||
'Pouilly-Fumé',
|
||||
'Vouvray',
|
||||
'Muscadet',
|
||||
'Anjou',
|
||||
'Saumur',
|
||||
'Chinon'
|
||||
],
|
||||
'Rhône Valley': [
|
||||
'Côte-Rôtie',
|
||||
'Hermitage',
|
||||
'Condrieu',
|
||||
'Saint-Joseph',
|
||||
'Châteauneuf-du-Pape',
|
||||
'Côtes du Rhône',
|
||||
'Gigondas',
|
||||
'Vacqueyras'
|
||||
],
|
||||
Provence: ['Côtes de Provence', 'Bandol'],
|
||||
'Languedoc-Roussillon': [],
|
||||
'Southwest France': ['Cahors', 'Madiran', 'Jurançon'],
|
||||
Jura: ['Arbois', 'Côtes du Jura'],
|
||||
Savoie: [],
|
||||
Corsica: ['Patrimonio']
|
||||
},
|
||||
DE: {
|
||||
Mosel: ['Saar', 'Ruwer'],
|
||||
Pfalz: [],
|
||||
Rheinhessen: [],
|
||||
Nahe: [],
|
||||
Rheingau: [],
|
||||
'Hessische Bergstraße': [],
|
||||
Franken: [],
|
||||
Baden: [],
|
||||
Württemberg: [],
|
||||
'Saale-Unstrut': [],
|
||||
Sachsen: [],
|
||||
Ahr: []
|
||||
},
|
||||
CH: {
|
||||
Vaud: ['Lavaux', 'Vevey', 'Morges', 'Nyon'],
|
||||
Valais: ['Sierre', 'Sion', 'Martigny'],
|
||||
Geneva: ['Lancy', 'Carouge'],
|
||||
Ticino: ['Locarno', 'Bellinzona', 'Ascona'],
|
||||
Neuchâtel: ['Val-de-Travers', 'Val-de-Ruz'],
|
||||
Fribourg: ['Glâne', 'Sarine'],
|
||||
Bern: ['Thun', 'Biel'],
|
||||
Aargau: ['Baden', 'Brugg'],
|
||||
Thurgau: ['Frauenfeld', 'Arbon'],
|
||||
Zürich: ['Affoltern', 'Dietikon']
|
||||
},
|
||||
ES: {
|
||||
Rioja: ['Rioja Alta', 'Rioja Alavesa', 'Rioja Oriental'],
|
||||
Navarra: [],
|
||||
'Rías Baixas': [],
|
||||
'Ribeira Sacra': [],
|
||||
Valdeorras: [],
|
||||
Txakoli: ['Getariako Txakolina', 'Bizkaiko Txakolina', 'Arabako Txakolina'],
|
||||
'Ribera del Duero': [],
|
||||
Rueda: [],
|
||||
Toro: [],
|
||||
Cigales: [],
|
||||
'La Mancha': [],
|
||||
Valdepeñas: [],
|
||||
Priorat: [],
|
||||
Penedès: [],
|
||||
Cava: [],
|
||||
Montsant: [],
|
||||
'Conca de Barberà': [],
|
||||
'Jerez-Xérès': [],
|
||||
'Montilla-Moriles': [],
|
||||
Málaga: [],
|
||||
'Sierras de Málaga': [],
|
||||
'Condado de Huelva': [],
|
||||
'Utiel-Requena': [],
|
||||
Valencia: [],
|
||||
Jumilla: [],
|
||||
Yecla: [],
|
||||
Bullas: [],
|
||||
'Canary Islands': [],
|
||||
'Balearic Islands': ['Binissalem', 'Pla i Llevant'],
|
||||
Bierzo: [],
|
||||
'Campo de Borja': [],
|
||||
Aragón: ['Calatayud', 'Somontano'],
|
||||
'Ribera del Guadiana': []
|
||||
},
|
||||
PT: {
|
||||
Douro: ['Baixo Corgo', 'Cima Corgo', 'Douro Superior'],
|
||||
Alentejo: ['Central', 'Litoral', 'Interior Norte', 'Interior Sul'],
|
||||
Porto: ['Gaia'],
|
||||
'Vinho Verde': [
|
||||
'Amarante',
|
||||
'Ave',
|
||||
'Baião',
|
||||
'Basto',
|
||||
'Cávado',
|
||||
'Lima',
|
||||
'Lima Interior',
|
||||
'Lima Litoral',
|
||||
'Monção',
|
||||
'Paiva',
|
||||
'Sousa'
|
||||
],
|
||||
Tejo: [
|
||||
'Alcobaça',
|
||||
'Arruda dos Vinhos',
|
||||
'Azeiteira',
|
||||
'Benavente e Santarém',
|
||||
'Bico do Cachorro',
|
||||
'Borba',
|
||||
'Casaínhos',
|
||||
'Chamusca',
|
||||
'Colares',
|
||||
'Coruche',
|
||||
'Fátima',
|
||||
'Figueira da Foz',
|
||||
'Golegã',
|
||||
'Gualtar',
|
||||
'Gândara',
|
||||
'Mação',
|
||||
'Mafra',
|
||||
'Montemor-o-Novo',
|
||||
'Montijo',
|
||||
'Odivelas',
|
||||
'Palmela',
|
||||
'Pataias',
|
||||
'Peniche',
|
||||
'Porto de Mós',
|
||||
'Reguengos de Monsaraz',
|
||||
'Ribatejo',
|
||||
'Sabugal',
|
||||
'Setúbal',
|
||||
'Tomar',
|
||||
'Torres Vedras',
|
||||
'Vila Franca de Xira'
|
||||
],
|
||||
Lisbon: [
|
||||
'Alenquer',
|
||||
'Arruda',
|
||||
'Carcavelos e Cascais',
|
||||
'Colares',
|
||||
'Encoro',
|
||||
'Estremoz',
|
||||
'Oeiras',
|
||||
'Palmela',
|
||||
'Setúbal'
|
||||
],
|
||||
Madeira: [
|
||||
'Machico',
|
||||
'Santa Cruz',
|
||||
'Caniço',
|
||||
'Santo António da Serra',
|
||||
'Ponta do Sol',
|
||||
'Calheta',
|
||||
'Funchal',
|
||||
'Câmara de Lobos',
|
||||
'Ribeira Brava',
|
||||
'Paul do Mar',
|
||||
'Canhas',
|
||||
'Arco da Calheta',
|
||||
'São Vicente',
|
||||
'Porto Moniz',
|
||||
'Paul da Serra'
|
||||
]
|
||||
},
|
||||
AT: {
|
||||
Wagram: ['Marchfeld'],
|
||||
Kremstal: ['Krems', 'Spitz'],
|
||||
Kamptal: ['Langenlois', 'Gumpoldskirchen'],
|
||||
Wachau: [],
|
||||
Thermenregion: ['Baden', 'Mödling'],
|
||||
Wienerwald: [],
|
||||
Südsteiermark: ['Grazer Bergland', 'Leibnitz', 'Deutschlandsberg'],
|
||||
Burgenland: ['Neusiedlersee', 'Eisenberg', 'Rosalia'],
|
||||
Styria: ['Südsteiermark', 'Weststeiermark'],
|
||||
Salzburg: ['Flachgau'],
|
||||
Tyrol: ['Innsbruck', 'Hall'],
|
||||
Carinthia: ['Villach', 'Klagenfurt']
|
||||
},
|
||||
HU: {
|
||||
Tokaj: [],
|
||||
Eger: [],
|
||||
Badacsony: [],
|
||||
Balaton: [
|
||||
'Balatonalmádi',
|
||||
'Balatonboglár',
|
||||
'Balatonfüred-Csopak',
|
||||
'Balatonlelle',
|
||||
'Balatonfüred',
|
||||
'Somló'
|
||||
],
|
||||
Mátra: [],
|
||||
Hegyalja: [],
|
||||
Pannon: ['Szekszárd', 'Pécs', 'Baja', 'Sopron'],
|
||||
Villány: []
|
||||
},
|
||||
CZ: {
|
||||
'South Moravia': ['Znojmo', 'Břeclav', 'Hodonín', 'Kyjov', 'Vranov'],
|
||||
'Zlín Region': ['Valašské Meziříčí', 'Kroměříž', 'Vsetín'],
|
||||
'Olomouc Region': ['Prostějov', 'Olomouc', 'Litomyšl'],
|
||||
'South Bohemia': ['České Budějovice', 'Písek', 'Strakonice'],
|
||||
Plzeň: ['Rokycany', 'Domažlice']
|
||||
},
|
||||
MD: ['Iași', 'Botoșani', 'Vaslui', 'Neamț'],
|
||||
RO: {
|
||||
Transylvania: ['Cluj', 'Sibiu', 'Brașov', 'Mureș'],
|
||||
Muntenia: ['Buzău', 'Dâmbovița', 'Prahova', 'Vrancea'],
|
||||
Oltenia: ['Dolj', 'Gorj', 'Vâlcea', 'Mehedinți'],
|
||||
Banat: ['Arad', 'Timiș', 'Caraș-Severin'],
|
||||
Dobrogea: ['Constanța', 'Tulcea'],
|
||||
Crisana: ['Bihor'],
|
||||
Maramureș: ['Suceava', 'Maramureș']
|
||||
},
|
||||
GR: {
|
||||
Attica: [],
|
||||
'Central Greece': [
|
||||
'Euboea',
|
||||
'Boeotia',
|
||||
'Phocis',
|
||||
'Locris',
|
||||
'Phthiotis',
|
||||
'Phocis',
|
||||
'Locris',
|
||||
'Phthiotis'
|
||||
],
|
||||
Peloponnese: [
|
||||
'Nemea',
|
||||
'Mantineia',
|
||||
'Achaia',
|
||||
'Messenia',
|
||||
'Laconia',
|
||||
'Arcadia',
|
||||
'Argolis',
|
||||
'Corinthia'
|
||||
],
|
||||
Thrace: [],
|
||||
Macedonia: [
|
||||
'Thessaloniki',
|
||||
'Kilkis',
|
||||
'Pella',
|
||||
'Florina',
|
||||
'Kastoria',
|
||||
'Imathia',
|
||||
'Pella',
|
||||
'Florina',
|
||||
'Kastoria'
|
||||
],
|
||||
Thessaly: ['Trikala', 'Larissa', 'Magnesia', 'Karditsa', 'Trikala'],
|
||||
'Ionian Islands': [
|
||||
'Corfu',
|
||||
'Zakynthos',
|
||||
'Kefalonia',
|
||||
'Lefkada',
|
||||
'Ithaca',
|
||||
'Paxos',
|
||||
'Corfu',
|
||||
'Zakynthos',
|
||||
'Kefalonia',
|
||||
'Lefkada',
|
||||
'Ithaca',
|
||||
'Paxos'
|
||||
],
|
||||
Cyclades: [
|
||||
'Santorini',
|
||||
'Paros',
|
||||
'Naxos',
|
||||
'Mykonos',
|
||||
'Sifnos',
|
||||
'Tinos',
|
||||
'Santorini',
|
||||
'Paros',
|
||||
'Naxos',
|
||||
'Mykonos',
|
||||
'Sifnos',
|
||||
'Tinos'
|
||||
],
|
||||
Crete: [],
|
||||
'Aegean Islands': [
|
||||
'Samos',
|
||||
'Lesbos',
|
||||
'Chios',
|
||||
'Limnos',
|
||||
'Ikaria',
|
||||
'Samos',
|
||||
'Lesbos',
|
||||
'Chios',
|
||||
'Limnos',
|
||||
'Ikaria'
|
||||
]
|
||||
},
|
||||
HR: {
|
||||
Dalmatia: ['Hvar', 'Brac', 'Korcula', 'Peljesac', 'Dubrovnik-Riviera'],
|
||||
Istria: [],
|
||||
Slavonia: [],
|
||||
Kvarner: ['Kvarner Bay', 'Labin'],
|
||||
'Continental Croatia': ['Srijem', 'Moslavina', 'Podravina'],
|
||||
'Central Croatia': ['Zagorje', 'Medgurje', 'Bilogora']
|
||||
},
|
||||
GE: {
|
||||
Kakheti: ['Telavi', 'Sighnaghi', 'Akhasheni', 'Kvareli'],
|
||||
Imereti: ['Kutaisi', 'Khoni', 'Tsqaltubo', 'Sachkhere'],
|
||||
'Racha-Lechkhumi': [],
|
||||
'Kvemo Svaneti': [],
|
||||
Kartli: ['Tbilisi', 'Gori', 'Kaspi', 'Rustavi'],
|
||||
Adjara: ['Batumi', 'Kobuleti'],
|
||||
'Samegrelo-Zemo Svaneti': ['Poti', 'Zugdidi'],
|
||||
Abkhazia: ['Gagra', 'Gudauta', 'Ochamchire'],
|
||||
'South Georgia': ['Adigeni', 'Akhalkalaki', 'Bolnisi', 'Ninotsminda']
|
||||
},
|
||||
IL: {
|
||||
Galilee: ['Golan Heights', 'Carmel Mountains'],
|
||||
'Central District': ['Shomron', 'Shfela'],
|
||||
Negev: ['Southern Negev', 'Central Negev'],
|
||||
'Coastal Plain': ['Central Coastal Plain', 'Southern Coastal Plain']
|
||||
},
|
||||
LB: ['Bekaa Valley', 'Mount Lebanon', 'North Lebanon', 'South Lebanon'],
|
||||
TR: {
|
||||
Anatolia: [
|
||||
'Konya',
|
||||
'Aksaray',
|
||||
'Nevşehir',
|
||||
'Erzurum',
|
||||
'Van',
|
||||
'Bitlis',
|
||||
'İzmir',
|
||||
'Manisa',
|
||||
'Ayvalık'
|
||||
],
|
||||
'Aegean Region': ['Urla', 'Foça', 'Bornova'],
|
||||
Ayvalık: [],
|
||||
'Marmara Region': ['İstanbul', 'Edirne'],
|
||||
'Black Sea Region': ['Rize', 'Artvin', 'Trabzon'],
|
||||
'Southeastern Anatolia': ['Gaziantep', 'Şanlıurfa', 'Adana']
|
||||
},
|
||||
AM: ['Vayots Dzor', 'Ararat Valley', 'Gegharkunik', 'Tavush', 'Syunik'],
|
||||
CA: {
|
||||
Ontario: [
|
||||
'Niagara Peninsula',
|
||||
'Lake Erie North Shore',
|
||||
'Pelee Island',
|
||||
'Prince Edward County'
|
||||
],
|
||||
'British Columbia': [
|
||||
'Okanagan Valley',
|
||||
'Similkameen Valley',
|
||||
'Fraser Valley',
|
||||
'Southern Gulf Islands'
|
||||
],
|
||||
Quebec: ['Montreal', 'Eastern Townships', 'Charlevoix'],
|
||||
'Nova Scotia': ['Annapolis Valley', 'Gaspereau Valley', 'North Shore'],
|
||||
'New Brunswick': ['Annapolis Valley', 'Gaspereau Valley'],
|
||||
'Prince Edward Island': ['Eastern PEI', 'Western PEI']
|
||||
},
|
||||
US: {
|
||||
California: [
|
||||
'Napa Valley',
|
||||
'Sonoma Valley',
|
||||
'Paso Robles',
|
||||
'Santa Barbara County',
|
||||
'Central Coast',
|
||||
'Lodi',
|
||||
'Mendocino County',
|
||||
'Santa Cruz Mountains',
|
||||
'Russian River Valley'
|
||||
],
|
||||
Oregon: [
|
||||
'Willamette Valley',
|
||||
'Rogue Valley',
|
||||
'Umpqua Valley',
|
||||
'Southern Oregon',
|
||||
'Oregon Coast'
|
||||
],
|
||||
Washington: [
|
||||
'Columbia Valley',
|
||||
'Walla Walla Valley',
|
||||
'Red Mountain',
|
||||
'Snake River'
|
||||
],
|
||||
'New York': [
|
||||
'Finger Lakes',
|
||||
'Long Island',
|
||||
'Hudson River',
|
||||
'Western New York'
|
||||
],
|
||||
Virginia: [
|
||||
'Monticello',
|
||||
'Shenandoah Valley',
|
||||
'Northern Virginia',
|
||||
'Virginia Piedmont'
|
||||
],
|
||||
'Texas Hill Country': [],
|
||||
'Arizona Sonoita': [],
|
||||
'Colorado Grand Valley': [],
|
||||
'Idaho Snake River Valley': [],
|
||||
Michigan: ['Michigan Lake', 'Michigan Shore']
|
||||
},
|
||||
AR: {
|
||||
Mendoza: ['Luján de Cuyo', 'Uco Valley', 'Maipú', 'San Rafael'],
|
||||
Salta: ['Calchaquí Valleys', 'Quebrada de Humahuaca'],
|
||||
'San Juan': ['Ullum', 'Calingasta', 'Tulum'],
|
||||
'La Rioja': [],
|
||||
Catamarca: ['Andalgalá', 'Belén'],
|
||||
'San Luis': [
|
||||
'Valle del Conlara',
|
||||
'Valle del Potrerillos',
|
||||
'Valle de las Carreras'
|
||||
],
|
||||
'Buenos Aires': [
|
||||
'Partido de General Belgrano',
|
||||
'Partido de Lobos',
|
||||
'Partido de Tandil'
|
||||
],
|
||||
Neuquén: ['Valle de Río Negro', 'Valle de Agrelo']
|
||||
},
|
||||
CL: {
|
||||
'Central Valley': [
|
||||
'Maipo Valley',
|
||||
'Casablanca Valley',
|
||||
'San Antonio Valley',
|
||||
'Maule Valley',
|
||||
'Colchagua Valley'
|
||||
],
|
||||
'Coastal Regions': [
|
||||
'Valle de Aconcagua',
|
||||
'Valle de Limarí',
|
||||
'Valle de Elqui'
|
||||
],
|
||||
'Southern Chile': ['Valle de Itata', 'Valle de Bío-Bío'],
|
||||
'Northern Chile': ['Valle de Elqui']
|
||||
},
|
||||
ZA: {
|
||||
'Western Cape': {
|
||||
Stellenbosch: [
|
||||
'Simonsberg-Stellenbosch',
|
||||
'Jonkershoek-Stellenbosch',
|
||||
'Stellenbosch Mountain'
|
||||
],
|
||||
Paarl: ['Franschhoek', 'Paarl Mountain'],
|
||||
Constantia: ['Constantia Valley'],
|
||||
Durbanville: ['Durbanville Hills'],
|
||||
'Walker Bay': ['Hemel-en-Aarde'],
|
||||
Swartland: ['Paardeberg']
|
||||
},
|
||||
'Eastern Cape': ['Jeffreys Bay'],
|
||||
'Northern Cape': ['Augrabies']
|
||||
},
|
||||
AU: {
|
||||
'South Eastern Australia': [],
|
||||
'New South Wales': [
|
||||
'Hunter Valley',
|
||||
'Riverina',
|
||||
'Mudgee',
|
||||
'Orange',
|
||||
'Cowra',
|
||||
'Hilltops',
|
||||
'Tumbarumba',
|
||||
'Gundagai',
|
||||
'Shoalhaven Coast',
|
||||
'Southern Highlands',
|
||||
'New England'
|
||||
],
|
||||
'South Australia': [
|
||||
'Barossa Valley',
|
||||
'McLaren Vale',
|
||||
'Clare Valley',
|
||||
'Coonawarra',
|
||||
'Adelaide Hills',
|
||||
'Eden Valley',
|
||||
'Langhorne Creek',
|
||||
'Padthaway',
|
||||
'Kangaroo Island',
|
||||
'Mount Gambier',
|
||||
'Robe',
|
||||
'Wrattonbully',
|
||||
'Fleurieu Peninsula',
|
||||
'Currency Creek',
|
||||
'Southern Fleurieu'
|
||||
],
|
||||
Victoria: [
|
||||
'Yarra Valley',
|
||||
'Mornington Peninsula',
|
||||
'Geelong',
|
||||
'Bellarine Peninsula',
|
||||
'Sunbury',
|
||||
'Macedon Ranges',
|
||||
'Heathcote',
|
||||
'Bendigo',
|
||||
'Pyrenees',
|
||||
'Grampians',
|
||||
'Great Western',
|
||||
'Henty',
|
||||
'Otway Ranges',
|
||||
'King Valley',
|
||||
'Alpine Valleys',
|
||||
'Rutherglen',
|
||||
'Glenrowan',
|
||||
'Goulburn Valley',
|
||||
'Strathbogie Ranges'
|
||||
],
|
||||
'Western Australia': [
|
||||
'Margaret River',
|
||||
'Great Southern',
|
||||
'Swan Valley',
|
||||
'Pemberton',
|
||||
'Manjimup',
|
||||
'Blackwood Valley',
|
||||
'Geographe',
|
||||
'Peel',
|
||||
'Perth Hills',
|
||||
'Frankland River',
|
||||
'Mount Barker',
|
||||
'Porongurup',
|
||||
'Denmark',
|
||||
'Albany',
|
||||
'Esperance'
|
||||
],
|
||||
Tasmania: [
|
||||
'Tamar Valley',
|
||||
'Pipers River',
|
||||
'East Coast',
|
||||
'Coal Valley',
|
||||
'Derwent Valley',
|
||||
'Huon Valley'
|
||||
],
|
||||
Queensland: [
|
||||
'Granite Belt',
|
||||
'South Burnett',
|
||||
'Darling Downs',
|
||||
'Scenic Rim'
|
||||
],
|
||||
'Canberra District': [
|
||||
'Canberra',
|
||||
'Murrumbateman',
|
||||
'Yass Valley',
|
||||
'Gundaroo',
|
||||
'Lake George'
|
||||
]
|
||||
},
|
||||
NZ: {
|
||||
'North Island': [
|
||||
'Auckland',
|
||||
'Gisborne',
|
||||
`Hawke's Bay`,
|
||||
'Wairarapa',
|
||||
'Bay of Islands',
|
||||
'Coromandel',
|
||||
'Waikato'
|
||||
],
|
||||
'South Island': [
|
||||
'Marlborough',
|
||||
'Nelson',
|
||||
'Waipara',
|
||||
'Canterbury',
|
||||
'Central Otago',
|
||||
'Waipara Valley',
|
||||
'North Canterbury',
|
||||
'North Otago'
|
||||
]
|
||||
},
|
||||
CN: {
|
||||
Ningxia: {
|
||||
Yinchuan: [],
|
||||
'Helan Mountain': ['Qingtongxia', 'Wucaiwan', 'Qingshuihe', 'Jinfeng']
|
||||
},
|
||||
Shaanxi: ['Lishan', 'Fengxiang'],
|
||||
Shandong: ['Yantai', 'Penglai', 'Weihai'],
|
||||
Xinjiang: ['Turpan', 'Yili', 'Jinghe'],
|
||||
Hebei: ['Changli', 'Qinhuangdao'],
|
||||
Heilongjiang: ['Yichun', 'Qiqihar'],
|
||||
Jilin: ['Baishan', 'Changchun'],
|
||||
Liaoning: ['Dalian', 'Shenyang'],
|
||||
'Inner Mongolia': ['Hohhot', 'Baotou'],
|
||||
Guangxi: ['Nanning', 'Liuzhou'],
|
||||
Hunan: ['Changsha', 'Yongzhou'],
|
||||
Hubei: ['Wuhan'],
|
||||
Sichuan: ['Chengdu', `Ya'an`],
|
||||
Guizhou: ['Guiyang', 'Zunyi'],
|
||||
Yunnan: ['Kunming', 'Dali']
|
||||
},
|
||||
JP: {
|
||||
'Yamanashi Prefecture': ['Kofu Basin', 'Katsunuma', 'Fuefuki'],
|
||||
'Hokkaido Prefecture': ['Biei', 'Tomakomai', 'Nanae'],
|
||||
'Niigata Prefecture': ['Uonuma', 'Sannosawa'],
|
||||
'Nagano Prefecture': ['Kiso Valley', 'Nagano'],
|
||||
'Gifu Prefecture': ['Gujo', 'Mino'],
|
||||
'Aichi Prefecture': ['Nishio', 'Seto'],
|
||||
'Shizuoka Prefecture': ['Suruga', 'Shizuoka'],
|
||||
'Kanagawa Prefecture': ['Yokohama', 'Kawasaki'],
|
||||
'Chiba Prefecture': ['Noda', 'Chiba'],
|
||||
'Ibaraki Prefecture': ['Tsukuba', 'Hitachi'],
|
||||
'Tochigi Prefecture': ['Utsunomiya', 'Tochigi'],
|
||||
'Gunma Prefecture': ['Maebashi', 'Gunma']
|
||||
},
|
||||
UK: {
|
||||
Kent: ['Ashford', 'Canterbury', 'Dover', 'Maidstone', 'Sevenoaks'],
|
||||
Sussex: ['Brighton', 'Eastbourne', 'Hastings', 'Chichester', 'Horsham'],
|
||||
Hampshire: ['Winchester', 'Petersfield', 'Basingstoke', 'Andover'],
|
||||
Wiltshire: ['Swindon', 'Chippenham', 'Salisbury', 'Trowbridge'],
|
||||
Dorset: ['Poole', 'Bournemouth', 'Dorset'],
|
||||
Devon: ['Exeter', 'Newton Abbot', 'Plymouth', 'Torquay'],
|
||||
Somerset: ['Bath', 'Weston-super-Mare', 'Taunton', 'Yeovil'],
|
||||
Gloucestershire: ['Cheltenham', 'Gloucester', 'Cirencester', 'Stroud'],
|
||||
Worcestershire: ['Worcester', 'Malvern', 'Kidderminster', 'Bewdley'],
|
||||
Warwickshire: [
|
||||
'Warwick',
|
||||
'Stratford-upon-Avon',
|
||||
'Leamington Spa',
|
||||
'Kenilworth'
|
||||
],
|
||||
Herefordshire: ['Hereford', 'Leominster', 'Ross-on-Wye', 'Kington'],
|
||||
Shropshire: ['Shrewsbury', 'Telford', 'Ludlow', 'Oswestry']
|
||||
},
|
||||
UY: {
|
||||
'Canelones Department': ['San Carlos', 'Melo', 'Paso de los Toros'],
|
||||
'Montevideo Department': ['Carrasco', 'Punta del Este'],
|
||||
'Colonia Department': ['Colonia del Sacramento', 'Villa Soriano'],
|
||||
'San José Department': ['Progreso', 'Treinta y Tres'],
|
||||
'Rocha Department': ['La Paloma', 'Rocha Sur'],
|
||||
'Florida Department': ['Florida', 'Sarandí del Yí'],
|
||||
'Soriano Department': ['Soriano', 'Río Negro'],
|
||||
'Tacuarembó Department': ['Tacuarembó', 'Paso de los Toros'],
|
||||
'Treinta y Tres Department': ['Treinta y Tres', 'Rivera']
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user