Merge pull request 'tasting-notes' (#47) from tasting-notes into staging
Reviewed-on: #47
This commit is contained in:
commit
5136cb2a74
.vscode
src
index.ts
models
routes
services
types
utils
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -11,6 +11,8 @@
|
||||
"Blanco",
|
||||
"Cachaça",
|
||||
"Caturra",
|
||||
"colour",
|
||||
"Colours",
|
||||
"Coren",
|
||||
"EAN",
|
||||
"espadín",
|
||||
|
16
src/index.ts
16
src/index.ts
@ -10,7 +10,7 @@ import {
|
||||
spiritsRouter,
|
||||
coffeeRouter
|
||||
} from './routes'
|
||||
import { Routes } from './types'
|
||||
import { Route } from './types'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
@ -19,13 +19,13 @@ const port = process.env.PORT || 3000
|
||||
|
||||
connectToDatabase()
|
||||
.then(() => {
|
||||
app.use(Routes.Users, usersRouter)
|
||||
app.use(Routes.NostrEvents, nostrRouter)
|
||||
app.use(Routes.Reviews, reviewsRouter)
|
||||
app.use(Routes.Wines, winesRouter)
|
||||
app.use(Routes.Sake, sakeRouter)
|
||||
app.use(Routes.Spirits, spiritsRouter)
|
||||
app.use(Routes.Coffee, coffeeRouter)
|
||||
app.use(Route.Users, usersRouter)
|
||||
app.use(Route.NostrEvents, nostrRouter)
|
||||
app.use(Route.Reviews, reviewsRouter)
|
||||
app.use(Route.Wines, winesRouter)
|
||||
app.use(Route.Sake, sakeRouter)
|
||||
app.use(Route.Spirits, spiritsRouter)
|
||||
app.use(Route.Coffee, coffeeRouter)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server started at http://localhost:${port}`)
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { ObjectId } from 'mongodb'
|
||||
import { RatingOptions } from '../types'
|
||||
import { ProductType, RatingOption, TastingNote } from '../types'
|
||||
|
||||
export class Review {
|
||||
constructor(
|
||||
public eventId: string, // foreign key referencing the nostrEvents collection
|
||||
public productId: string, // unique identifier for the product
|
||||
public rating: number | RatingOptions, // numerical rating, e.g., 84-100 or NS (no score)
|
||||
public productType: ProductType, // product type
|
||||
public rating: number | RatingOption, // numerical rating, e.g., 84-100 or NS (no score)
|
||||
public reviewText: string, // text content of the review
|
||||
public tastingNotes: string[], // array of tasting notes, e.g., flavours, aromas
|
||||
public tastingNote: TastingNote, // an object representing tasting notes
|
||||
public id?: ObjectId // database object id
|
||||
) {}
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ import { ObjectId } from 'mongodb'
|
||||
import {
|
||||
SakeDesignation,
|
||||
SakeStarter,
|
||||
VintageOptions,
|
||||
SakeCharacteristics,
|
||||
StandardDrinks,
|
||||
VintageOption,
|
||||
SakeCharacteristic,
|
||||
StandardDrink,
|
||||
SakeVolume,
|
||||
RiceVarietal,
|
||||
SakeYeastStrain,
|
||||
@ -24,15 +24,15 @@ export class Sake {
|
||||
public producerId: ObjectId, // product producer
|
||||
public designation: SakeDesignation, // table, pure, blended
|
||||
public polishRate: number, // %
|
||||
public characteristics: SakeCharacteristics[],
|
||||
public characteristic: SakeCharacteristic,
|
||||
public starter: SakeStarter, // sake starter
|
||||
public yeastStrain: SakeYeastStrain,
|
||||
public volume: SakeVolume, // bottle volume
|
||||
public alcohol: number, // alcohol percentage
|
||||
public standardDrinks: StandardDrinks, // number representing an amount of standard drinks per bottle per 100ml
|
||||
public standardDrinks: StandardDrink, // number representing an amount of standard drinks per bottle per 100ml
|
||||
public riceVarietal: RiceVarietal[], // if more than one, list as 'blend'
|
||||
public koji: SakeKoji, // if more than one, list as 'blend'
|
||||
public vintage: number | VintageOptions, // year, nv (non-vintage) or mv (multi-vintage)
|
||||
public vintage: number | VintageOption, // year, nv (non-vintage) or mv (multi-vintage)
|
||||
public RRPamount: number, // 20
|
||||
public RRPcurrency: CurrencyCode, // USD
|
||||
public description: string, // detailed description of the product
|
||||
|
@ -3,10 +3,10 @@ import {
|
||||
SpiritType,
|
||||
SpiritVariant,
|
||||
Ingredient,
|
||||
VintageOptions,
|
||||
StandardDrinks,
|
||||
VintageOption,
|
||||
StandardDrink,
|
||||
SpiritVolume,
|
||||
SpiritCharacteristics
|
||||
SpiritCharacteristic
|
||||
} from '../types'
|
||||
import { Alpha2Code } from 'i18n-iso-countries'
|
||||
import { CurrencyCode } from 'currency-codes-ts/dist/types'
|
||||
@ -22,12 +22,12 @@ export class Spirit {
|
||||
public producerId: ObjectId, // product producer
|
||||
public type: SpiritType, // spirit type
|
||||
public variant: SpiritVariant, // vodka, rum, liqueur cream, etc
|
||||
public characteristics: SpiritCharacteristics, // light aromatic, textural, fruit forward, structural & savoury, powerful
|
||||
public characteristic: SpiritCharacteristic, // light aromatic, textural, fruit forward, structural & savoury, powerful
|
||||
public ingredients: Ingredient[], // an array of ingredients(flavouring)
|
||||
public volume: SpiritVolume, // bottle volume
|
||||
public alcohol: number, // alcohol percentage
|
||||
public vintage: number | VintageOptions, // year, nv (non-vintage) or mv (multi-vintage)
|
||||
public standardDrinks: StandardDrinks, // an amount of standard drinks per 100ml and bottle in AU, UK and US
|
||||
public vintage: number | VintageOption, // year, nv (non-vintage) or mv (multi-vintage)
|
||||
public standardDrinks: StandardDrink, // an amount of standard drinks per 100ml and bottle in AU, UK and US
|
||||
public RRPamount: number, // 20
|
||||
public RRPcurrency: CurrencyCode, // USD
|
||||
public description: string, // detailed description of the product
|
||||
|
@ -3,8 +3,8 @@ import {
|
||||
WineType,
|
||||
Viticulture,
|
||||
BottleClosure,
|
||||
VintageOptions,
|
||||
StandardDrinks,
|
||||
VintageOption,
|
||||
StandardDrink,
|
||||
WineRegion,
|
||||
WineVolume,
|
||||
WineStyle,
|
||||
@ -24,7 +24,7 @@ export class Wine {
|
||||
public productCodeSKU: string, // Stock keeping unit (https://en.wikipedia.org/wiki/Stock_keeping_unit)
|
||||
public type: WineType, // numerical rating, e.g., 1-100
|
||||
public style: WineStyle, // bubbles+fizz, table, dessert, fortified, vermouth
|
||||
public characteristics: (
|
||||
public characteristic: (
|
||||
| WhiteWineCharacteristic
|
||||
| AmberWineCharacteristic
|
||||
| RoseWineCharacteristic
|
||||
@ -35,10 +35,10 @@ export class Wine {
|
||||
public name: string, // label
|
||||
public producerId: ObjectId, // product producer
|
||||
public grapeVarietal: GrapeVarietal[], // if more than one, list as 'blend'
|
||||
public vintage: number | VintageOptions, // year, nv (non-vintage) or mv (multi-vintage)
|
||||
public vintage: number | VintageOption, // year, nv (non-vintage) or mv (multi-vintage)
|
||||
public volume: WineVolume, // bottle volume
|
||||
public alcohol: number, // alcohol percentage
|
||||
public standardDrinks: StandardDrinks, // an amount of standard drinks per 100ml and bottle in AU, UK and US
|
||||
public standardDrinks: StandardDrink, // 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))
|
||||
|
@ -3,6 +3,8 @@ import { collections } from '../services/database.service'
|
||||
import { Review } from '../models'
|
||||
import { reviewValidation, handleReqError, handleReqSuccess } from '../utils'
|
||||
import Joi from 'joi'
|
||||
import { DBcollection } from '../types'
|
||||
import { BSON } from 'mongodb'
|
||||
|
||||
export const reviewsRouter = express.Router()
|
||||
|
||||
@ -11,7 +13,7 @@ reviewsRouter.use(express.json())
|
||||
// GET
|
||||
reviewsRouter.get('/', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const reviews = await collections.reviews?.find({}).toArray()
|
||||
const reviews = await collections[DBcollection.Reviews]?.find({}).toArray()
|
||||
|
||||
res.status(200).send(reviews)
|
||||
} catch (error: unknown) {
|
||||
@ -36,7 +38,7 @@ reviewsRouter.post('/', async (req: Request, res: Response) => {
|
||||
throw error.details[0].message
|
||||
}
|
||||
|
||||
const existingReview = await collections.reviews?.findOne({
|
||||
const existingReview = await collections[DBcollection.Reviews]?.findOne({
|
||||
eventId: review.eventId
|
||||
})
|
||||
|
||||
@ -44,7 +46,40 @@ reviewsRouter.post('/', async (req: Request, res: Response) => {
|
||||
throw new Error('review with provided "eventId" exists')
|
||||
}
|
||||
|
||||
const result = await collections.reviews?.insertOne(review)
|
||||
const { productId } = review
|
||||
const _id = new BSON.ObjectId(productId)
|
||||
|
||||
const existingWine = await collections[DBcollection.Wines]?.findOne({
|
||||
_id
|
||||
})
|
||||
|
||||
if (!existingWine) {
|
||||
const existingSake = await collections[DBcollection.Sake]?.findOne({
|
||||
_id
|
||||
})
|
||||
|
||||
if (!existingSake) {
|
||||
const existingSpirit = await collections[DBcollection.Spirits]?.findOne(
|
||||
{
|
||||
_id
|
||||
}
|
||||
)
|
||||
|
||||
if (!existingSpirit) {
|
||||
const existingCoffee = await collections[
|
||||
DBcollection.Coffee
|
||||
]?.findOne({
|
||||
_id
|
||||
})
|
||||
|
||||
if (!existingCoffee) {
|
||||
throw new Error('product with provided "productId" does not exists')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await collections[DBcollection.Reviews]?.insertOne(review)
|
||||
|
||||
handleReqSuccess(res, result, 'review')
|
||||
} catch (error: unknown) {
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
volumeToMl
|
||||
} from '../utils'
|
||||
import Joi from 'joi'
|
||||
import { DBcollections, ProductType } from '../types'
|
||||
import { DBcollection, ProductType } from '../types'
|
||||
|
||||
export const sakeRouter = express.Router()
|
||||
|
||||
@ -51,7 +51,7 @@ sakeRouter.post('/', async (req: Request, res: Response) => {
|
||||
productCodeEAN,
|
||||
productCodeUPC,
|
||||
productCodeSKU,
|
||||
DBcollections.Sake,
|
||||
DBcollection.Sake,
|
||||
ProductType.Sake
|
||||
)
|
||||
|
||||
@ -60,7 +60,7 @@ sakeRouter.post('/', async (req: Request, res: Response) => {
|
||||
volumeToMl(sake.volume)
|
||||
)
|
||||
|
||||
const result = await collections[DBcollections.Sake]?.insertOne(sake)
|
||||
const result = await collections[DBcollection.Sake]?.insertOne(sake)
|
||||
|
||||
handleReqSuccess(res, result, ProductType.Spirit)
|
||||
} catch (error: unknown) {
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
volumeToMl
|
||||
} from '../utils'
|
||||
import Joi from 'joi'
|
||||
import { DBcollections, ProductType } from '../types'
|
||||
import { DBcollection, ProductType } from '../types'
|
||||
|
||||
export const spiritsRouter = express.Router()
|
||||
|
||||
@ -50,7 +50,7 @@ spiritsRouter.post('/', async (req: Request, res: Response) => {
|
||||
productCodeEAN,
|
||||
productCodeUPC,
|
||||
productCodeSKU,
|
||||
DBcollections.Spirits,
|
||||
DBcollection.Spirits,
|
||||
ProductType.Spirit
|
||||
)
|
||||
|
||||
@ -59,7 +59,7 @@ spiritsRouter.post('/', async (req: Request, res: Response) => {
|
||||
volumeToMl(spirit.volume)
|
||||
)
|
||||
|
||||
const result = await collections[DBcollections.Spirits]?.insertOne(spirit)
|
||||
const result = await collections[DBcollection.Spirits]?.insertOne(spirit)
|
||||
|
||||
handleReqSuccess(res, result, ProductType.Spirit)
|
||||
} catch (error: unknown) {
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
volumeToMl
|
||||
} from '../utils'
|
||||
import Joi from 'joi'
|
||||
import { DBcollections, ProductType } from '../types'
|
||||
import { DBcollection, ProductType } from '../types'
|
||||
|
||||
export const winesRouter = express.Router()
|
||||
|
||||
@ -19,7 +19,7 @@ winesRouter.use(express.json())
|
||||
// GET
|
||||
winesRouter.get('/', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const wines = await collections[DBcollections.Wines]?.find({}).toArray()
|
||||
const wines = await collections[DBcollection.Wines]?.find({}).toArray()
|
||||
|
||||
res.status(200).send(wines)
|
||||
} catch (error: unknown) {
|
||||
@ -51,7 +51,7 @@ winesRouter.post('/', async (req: Request, res: Response) => {
|
||||
productCodeEAN,
|
||||
productCodeUPC,
|
||||
productCodeSKU,
|
||||
DBcollections.Wines,
|
||||
DBcollection.Wines,
|
||||
ProductType.Wine
|
||||
)
|
||||
|
||||
@ -60,7 +60,7 @@ winesRouter.post('/', async (req: Request, res: Response) => {
|
||||
volumeToMl(wine.volume)
|
||||
)
|
||||
|
||||
const result = await collections[DBcollections.Wines]?.insertOne(wine)
|
||||
const result = await collections[DBcollection.Wines]?.insertOne(wine)
|
||||
|
||||
handleReqSuccess(res, result, ProductType.Wine)
|
||||
} catch (error: unknown) {
|
||||
|
@ -1,16 +1,16 @@
|
||||
import * as mongoDB from 'mongodb'
|
||||
import * as dotenv from 'dotenv'
|
||||
import { DBcollections } from '../types'
|
||||
import { DBcollection } from '../types'
|
||||
|
||||
// Global Variables
|
||||
export const collections: {
|
||||
[DBcollections.Users]?: mongoDB.Collection
|
||||
[DBcollections.NostrEvents]?: mongoDB.Collection
|
||||
[DBcollections.Reviews]?: mongoDB.Collection
|
||||
[DBcollections.Wines]?: mongoDB.Collection
|
||||
[DBcollections.Sake]?: mongoDB.Collection
|
||||
[DBcollections.Spirits]?: mongoDB.Collection
|
||||
[DBcollections.Coffee]?: mongoDB.Collection
|
||||
[DBcollection.Users]?: mongoDB.Collection
|
||||
[DBcollection.NostrEvents]?: mongoDB.Collection
|
||||
[DBcollection.Reviews]?: mongoDB.Collection
|
||||
[DBcollection.Wines]?: mongoDB.Collection
|
||||
[DBcollection.Sake]?: mongoDB.Collection
|
||||
[DBcollection.Spirits]?: mongoDB.Collection
|
||||
[DBcollection.Coffee]?: mongoDB.Collection
|
||||
} = {}
|
||||
|
||||
// Initialize Connection
|
||||
@ -29,20 +29,20 @@ export async function connectToDatabase() {
|
||||
|
||||
const db: mongoDB.Db = client.db(process.env.DB_NAME)
|
||||
|
||||
const usersCollection: mongoDB.Collection = db.collection(DBcollections.Users)
|
||||
const usersCollection: mongoDB.Collection = db.collection(DBcollection.Users)
|
||||
const nostrEventsCollection: mongoDB.Collection = db.collection(
|
||||
DBcollections.NostrEvents
|
||||
DBcollection.NostrEvents
|
||||
)
|
||||
const reviewsCollection: mongoDB.Collection = db.collection(
|
||||
DBcollections.Reviews
|
||||
DBcollection.Reviews
|
||||
)
|
||||
const winesCollection: mongoDB.Collection = db.collection(DBcollections.Wines)
|
||||
const sakeCollection: mongoDB.Collection = db.collection(DBcollections.Sake)
|
||||
const winesCollection: mongoDB.Collection = db.collection(DBcollection.Wines)
|
||||
const sakeCollection: mongoDB.Collection = db.collection(DBcollection.Sake)
|
||||
const spiritsCollection: mongoDB.Collection = db.collection(
|
||||
DBcollections.Spirits
|
||||
DBcollection.Spirits
|
||||
)
|
||||
const coffeeCollection: mongoDB.Collection = db.collection(
|
||||
DBcollections.Coffee
|
||||
DBcollection.Coffee
|
||||
)
|
||||
|
||||
collections.users = usersCollection
|
||||
|
@ -1,4 +1,4 @@
|
||||
export enum DBcollections {
|
||||
export enum DBcollection {
|
||||
Users = 'users',
|
||||
NostrEvents = 'nostrEvents',
|
||||
Reviews = 'reviews',
|
||||
|
@ -6,3 +6,5 @@ export * from './wine'
|
||||
export * from './sake'
|
||||
export * from './spirit'
|
||||
export * from './coffee'
|
||||
export * from './review'
|
||||
export * from './review/'
|
||||
|
@ -61,20 +61,16 @@ export enum Ingredient {
|
||||
Walnut = 'Walnut'
|
||||
}
|
||||
|
||||
export enum VintageOptions {
|
||||
export enum VintageOption {
|
||||
NV = 'NV',
|
||||
MV = 'MV'
|
||||
}
|
||||
|
||||
export interface StandardDrinks {
|
||||
export interface StandardDrink {
|
||||
'100ml': { AU: number; UK: number; US: number }
|
||||
bottle: { AU: number; UK: number; US: number }
|
||||
}
|
||||
|
||||
export enum RatingOptions {
|
||||
NoScore = 'NS'
|
||||
}
|
||||
|
||||
export enum ProductType {
|
||||
Wine = 'Wine',
|
||||
Spirit = 'Spirit',
|
||||
|
125
src/types/review.ts
Normal file
125
src/types/review.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import {
|
||||
VisualAssessmentKey,
|
||||
ClarityVisualAssessment,
|
||||
NatureVisualAssessment,
|
||||
WhiteColour,
|
||||
AmberColour,
|
||||
RoseColour,
|
||||
RedColour,
|
||||
BlueColour,
|
||||
GreenColour,
|
||||
PrimaryFlavoursAndAromasKey,
|
||||
Condition,
|
||||
Intensity,
|
||||
Age,
|
||||
CitrusFruit,
|
||||
AppleFruit,
|
||||
StoneFruit,
|
||||
RedFruit,
|
||||
BlackFruit,
|
||||
ChocolateFruit,
|
||||
TropicalFruit,
|
||||
MelonFruit,
|
||||
Floral,
|
||||
Vegetal,
|
||||
Earth,
|
||||
Microbial,
|
||||
Oak,
|
||||
Chocolate,
|
||||
Oxidation,
|
||||
Umami,
|
||||
Balsamic,
|
||||
Grain,
|
||||
Dairy,
|
||||
Anisoles,
|
||||
Brettanomyces,
|
||||
VolatileAcidity,
|
||||
Reduction,
|
||||
TextureAndBalanceKey,
|
||||
Sweetness,
|
||||
Concentration,
|
||||
TanninType,
|
||||
RipeTannin,
|
||||
UnripeTannin,
|
||||
Body,
|
||||
FlavourIntensity,
|
||||
PalateLength,
|
||||
ReasoningConcentration,
|
||||
Quality,
|
||||
ReadinessToDrink,
|
||||
ReasoningKey
|
||||
} from './review/'
|
||||
|
||||
export enum RatingOption {
|
||||
NoScore = 'NS'
|
||||
}
|
||||
|
||||
export enum TastingNoteKey {
|
||||
VisualAssessment = 'visualAssessment',
|
||||
PrimaryFlavoursAndAromas = 'primaryFlavoursAndAromas',
|
||||
TextureAndBalance = 'textureAndBalance'
|
||||
}
|
||||
|
||||
export interface TastingNote {
|
||||
[TastingNoteKey.VisualAssessment]: {
|
||||
[VisualAssessmentKey.Clarity]: ClarityVisualAssessment
|
||||
[VisualAssessmentKey.Nature]: NatureVisualAssessment
|
||||
[VisualAssessmentKey.Colour]:
|
||||
| WhiteColour
|
||||
| AmberColour
|
||||
| RoseColour
|
||||
| RedColour
|
||||
| BlueColour
|
||||
| GreenColour
|
||||
}
|
||||
[TastingNoteKey.PrimaryFlavoursAndAromas]: {
|
||||
[PrimaryFlavoursAndAromasKey.Condition]: Condition
|
||||
[PrimaryFlavoursAndAromasKey.Intensity]: Intensity
|
||||
[PrimaryFlavoursAndAromasKey.Age]: Age
|
||||
[PrimaryFlavoursAndAromasKey.Fruit]:
|
||||
| CitrusFruit
|
||||
| AppleFruit
|
||||
| StoneFruit
|
||||
| RedFruit
|
||||
| BlackFruit
|
||||
| BlackFruit
|
||||
| ChocolateFruit
|
||||
| TropicalFruit
|
||||
| MelonFruit
|
||||
[PrimaryFlavoursAndAromasKey.Floral]: Floral
|
||||
[PrimaryFlavoursAndAromasKey.Vegetal]: Vegetal
|
||||
[PrimaryFlavoursAndAromasKey.Earth]: Earth
|
||||
[PrimaryFlavoursAndAromasKey.Microbial]: Microbial
|
||||
[PrimaryFlavoursAndAromasKey.Oak]: Oak
|
||||
[PrimaryFlavoursAndAromasKey.Chocolate]: Chocolate
|
||||
[PrimaryFlavoursAndAromasKey.Oxidation]: Oxidation
|
||||
[PrimaryFlavoursAndAromasKey.Umami]: Umami
|
||||
[PrimaryFlavoursAndAromasKey.Balsamic]: Balsamic
|
||||
[PrimaryFlavoursAndAromasKey.Grain]: Grain
|
||||
[PrimaryFlavoursAndAromasKey.Dairy]: Dairy
|
||||
[PrimaryFlavoursAndAromasKey.Faults]:
|
||||
| Anisoles
|
||||
| Brettanomyces
|
||||
| VolatileAcidity
|
||||
| Reduction
|
||||
}
|
||||
[TastingNoteKey.TextureAndBalance]: {
|
||||
[TextureAndBalanceKey.Sweetness]: Sweetness
|
||||
[TextureAndBalanceKey.Acidity]: Concentration
|
||||
[TextureAndBalanceKey.Tannin]: {
|
||||
[key in Concentration]: { [key in TanninType]: RipeTannin | UnripeTannin }
|
||||
}
|
||||
[TextureAndBalanceKey.Alcohol]: Concentration
|
||||
[TextureAndBalanceKey.Body]: Body
|
||||
[TextureAndBalanceKey.FlavourIntensity]: FlavourIntensity
|
||||
[TextureAndBalanceKey.PalateLength]: PalateLength
|
||||
[TextureAndBalanceKey.Reasoning]: {
|
||||
[ReasoningKey.Balance]: boolean
|
||||
[ReasoningKey.Concentration]: ReasoningConcentration
|
||||
[ReasoningKey.Complex]: boolean
|
||||
}
|
||||
[TextureAndBalanceKey.Quality]: Quality
|
||||
[TextureAndBalanceKey.Age]: number // tasting date-producers bottling date
|
||||
[TextureAndBalanceKey.ReadinessToDrink]: ReadinessToDrink
|
||||
}
|
||||
}
|
93
src/types/review/colour.ts
Normal file
93
src/types/review/colour.ts
Normal file
@ -0,0 +1,93 @@
|
||||
// Free Run (sake, wine, spirits)
|
||||
export enum WhiteColour {
|
||||
WaterWhite = 'Water white',
|
||||
LemonGreen = 'Lemon-Green',
|
||||
Lemon = 'Lemon',
|
||||
Gold = 'Gold',
|
||||
WhiteBrown = 'White-Brown' // FIXME: duplicate
|
||||
}
|
||||
|
||||
// Skin Contact (wine, spirits)
|
||||
export enum AmberColour {
|
||||
AmberOrange = 'Amber-Orange',
|
||||
Amber = 'Amber'
|
||||
}
|
||||
|
||||
// Blush (wine, spirits)
|
||||
export enum RoseColour {
|
||||
Pink = 'Pink',
|
||||
Salmon = 'Salmon',
|
||||
RoseOrange = 'Rose-Orange', // FIXME: duplicate
|
||||
OnionSkin = 'Onion-Skin'
|
||||
}
|
||||
|
||||
// Extended Maceration (wine, spirits)
|
||||
export enum RedColour {
|
||||
Purple = 'Purple',
|
||||
Ruby = 'Ruby',
|
||||
Garnet = 'Garnet',
|
||||
Tawny = 'Tawny',
|
||||
RedBrown = 'Red-Brown'
|
||||
}
|
||||
|
||||
// Liqueurs (spirits)
|
||||
export enum BlueColour {
|
||||
BluePale = 'Blue-Pale',
|
||||
BlueDark = 'Blue-Dark'
|
||||
}
|
||||
|
||||
// Liqueurs (spirits)
|
||||
export enum GreenColour {
|
||||
GreenPale = 'Green-Pale',
|
||||
GreenDark = 'Green-Dark'
|
||||
}
|
||||
|
||||
export enum WineColour {
|
||||
WaterWhite = WhiteColour.WaterWhite,
|
||||
LemonGreen = WhiteColour.LemonGreen,
|
||||
Lemon = WhiteColour.Lemon,
|
||||
Gold = WhiteColour.Gold,
|
||||
WhiteBrown = WhiteColour.WhiteBrown,
|
||||
AmberOrange = AmberColour.AmberOrange,
|
||||
Amber = AmberColour.Amber,
|
||||
Pink = RoseColour.Pink,
|
||||
Salmon = RoseColour.Salmon,
|
||||
OnionSkin = RoseColour.OnionSkin,
|
||||
RoseOrange = RoseColour.RoseOrange,
|
||||
Purple = RedColour.Purple,
|
||||
Ruby = RedColour.Ruby,
|
||||
Garnet = RedColour.Garnet,
|
||||
Tawny = RedColour.Tawny,
|
||||
RedBrown = RedColour.RedBrown
|
||||
}
|
||||
|
||||
export enum SakeColour {
|
||||
WaterWhite = WhiteColour.WaterWhite,
|
||||
LemonGreen = WhiteColour.LemonGreen,
|
||||
Lemon = WhiteColour.Lemon,
|
||||
Gold = WhiteColour.Gold,
|
||||
WhiteBrown = WhiteColour.WhiteBrown
|
||||
}
|
||||
|
||||
export enum SpiritColour {
|
||||
WaterWhite = WhiteColour.WaterWhite,
|
||||
LemonGreen = WhiteColour.LemonGreen,
|
||||
Lemon = WhiteColour.Lemon,
|
||||
Gold = WhiteColour.Gold,
|
||||
WhiteBrown = WhiteColour.WhiteBrown,
|
||||
Amber = AmberColour.Amber,
|
||||
AmberOrange = AmberColour.AmberOrange,
|
||||
Pink = RoseColour.Pink,
|
||||
Salmon = RoseColour.Salmon,
|
||||
OnionSkin = RoseColour.OnionSkin,
|
||||
RoseOrange = RoseColour.RoseOrange,
|
||||
Purple = RedColour.Purple,
|
||||
Ruby = RedColour.Ruby,
|
||||
Garnet = RedColour.Garnet,
|
||||
Tawny = RedColour.Tawny,
|
||||
RedBrown = RedColour.RedBrown,
|
||||
BluePale = BlueColour.BluePale,
|
||||
BlueDark = BlueColour.BlueDark,
|
||||
GreenPale = GreenColour.GreenPale,
|
||||
GreenDark = GreenColour.GreenDark
|
||||
}
|
4
src/types/review/index.ts
Normal file
4
src/types/review/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './visualAssessment'
|
||||
export * from './colour'
|
||||
export * from './primaryFlavoursAndAromas'
|
||||
export * from './textureAndBalance'
|
226
src/types/review/primaryFlavoursAndAromas.ts
Normal file
226
src/types/review/primaryFlavoursAndAromas.ts
Normal file
@ -0,0 +1,226 @@
|
||||
export enum PrimaryFlavoursAndAromasKey {
|
||||
Condition = 'condition',
|
||||
Intensity = 'intensity',
|
||||
Age = 'age',
|
||||
Fruit = 'fruit',
|
||||
Floral = 'floral',
|
||||
Vegetal = 'vegetal',
|
||||
Earth = 'earth',
|
||||
Microbial = 'microbial',
|
||||
Oak = 'oak',
|
||||
Chocolate = 'chocolate',
|
||||
Oxidation = 'oxidation',
|
||||
Umami = 'umami',
|
||||
Balsamic = 'balsamic',
|
||||
Grain = 'grain',
|
||||
Dairy = 'dairy',
|
||||
Faults = 'faults'
|
||||
}
|
||||
|
||||
export enum Condition {
|
||||
Clean = 'Clean',
|
||||
Unclean = 'Unclean'
|
||||
}
|
||||
|
||||
export enum Intensity {
|
||||
Light = 'Light',
|
||||
Medium = 'Medium',
|
||||
Pronounced = 'Pronounced'
|
||||
}
|
||||
|
||||
export enum Age {
|
||||
Youthful = 'Youthful',
|
||||
Developing = 'Developing',
|
||||
Developed = 'Developed',
|
||||
Oxidised = 'Oxidised',
|
||||
Passed = 'Passed'
|
||||
}
|
||||
|
||||
export enum CitrusFruit {
|
||||
Grapefruit = 'Grapefruit',
|
||||
Lemon = 'Lemon',
|
||||
Lime = 'Lime',
|
||||
Marmalade = 'Marmalade',
|
||||
Orange = 'Orange',
|
||||
Yuzu = 'Yuzu'
|
||||
}
|
||||
|
||||
export enum AppleFruit {
|
||||
Green = 'Green',
|
||||
Red = 'Red',
|
||||
Ripe = 'Ripe'
|
||||
}
|
||||
|
||||
export enum StoneFruit {
|
||||
Apricot = 'Apricot ',
|
||||
Nectarine = 'Nectarine',
|
||||
Peach = 'Peach',
|
||||
Plum = 'Plum '
|
||||
}
|
||||
|
||||
export enum RedFruit {
|
||||
Cherry = 'Cherry',
|
||||
Cranberry = 'Cranberry',
|
||||
Pomegranate = 'Pomegranate',
|
||||
Raspberry = 'Raspberry',
|
||||
SourCherry = 'Sour Cherry',
|
||||
Strawberry = 'Strawberry'
|
||||
}
|
||||
|
||||
export enum BlackFruit {
|
||||
Blackberry = 'Blackberry',
|
||||
Blackcurrant = 'Blackcurrant',
|
||||
Boysenberry = 'Boysenberry',
|
||||
Blueberry = 'Blueberry',
|
||||
Olive = 'Olive'
|
||||
}
|
||||
|
||||
export enum ChocolateFruit {
|
||||
Chocolate = 'Chocolate'
|
||||
}
|
||||
|
||||
export enum TropicalFruit {
|
||||
Banana = 'Banana',
|
||||
Mango = 'Mango',
|
||||
Passionfruit = 'Passionfruit',
|
||||
Pineapple = 'Pineapple'
|
||||
}
|
||||
|
||||
export enum MelonFruit {
|
||||
Cantaloupe = 'Cantaloupe',
|
||||
Honeydew = 'Honeydew'
|
||||
}
|
||||
|
||||
export enum Floral {
|
||||
Acacia = 'Acacia',
|
||||
Elderflower = 'Elderflower',
|
||||
Hibiscus = 'Hibiscus',
|
||||
Honeysuckle = 'Honeysuckle',
|
||||
Jasmine = 'Jasmine',
|
||||
Lavender = 'Lavender',
|
||||
Lilac = 'Lilac',
|
||||
OrangeBlossom = 'Orange Blossom',
|
||||
Potpourri = 'Potpourri',
|
||||
Rose = 'Rose',
|
||||
Vanilla = 'Vanilla',
|
||||
Violet = 'Violet'
|
||||
}
|
||||
|
||||
export enum Vegetal {
|
||||
Grass = 'Grass',
|
||||
Hay = 'Hay',
|
||||
Herbaceous = 'Herbaceous',
|
||||
Basil = 'Basil',
|
||||
Mint = 'Mint',
|
||||
Eucalyptus = 'Eucalyptus',
|
||||
BlackTea = 'Black Tea',
|
||||
Capsicum = 'Capsicum',
|
||||
Gooseberry = 'Gooseberry',
|
||||
Tomato = 'Tomato'
|
||||
}
|
||||
|
||||
export enum Earth {
|
||||
Gravel = 'Gravel',
|
||||
Kerosene = 'Kerosene',
|
||||
RedBeet = 'Red Beet',
|
||||
Rocks = 'Rocks',
|
||||
Slate = 'Slate',
|
||||
Soil = 'Soil',
|
||||
Terracotta = 'Terracotta'
|
||||
}
|
||||
|
||||
export enum Microbial {
|
||||
Mushroom = 'Mushroom',
|
||||
Botrytis = 'Botrytis',
|
||||
Beeswax = 'Beeswax',
|
||||
Ginger = 'Ginger',
|
||||
Saffron = 'Saffron',
|
||||
Yeast = 'Yeast',
|
||||
Bread = 'Bread',
|
||||
Brioche = 'Brioche',
|
||||
Toast = 'Toast'
|
||||
}
|
||||
|
||||
export enum Oak {
|
||||
CigarBox = 'Cigar Box',
|
||||
Coconut = 'Coconut',
|
||||
Dill = 'Dill',
|
||||
Smoke = 'Smoke',
|
||||
Spices = 'Spices',
|
||||
Cinnamon = 'Cinnamon',
|
||||
Nutmeg = 'Nutmeg',
|
||||
Cloves = 'Cloves'
|
||||
}
|
||||
|
||||
export enum Chocolate {
|
||||
MilkChocolate = 'Milk Chocolate',
|
||||
DarkChocolate = 'Dark Chocolate'
|
||||
}
|
||||
|
||||
export enum Oxidation {
|
||||
Aldehydes = 'Aldehydes',
|
||||
Caramel = 'Caramel',
|
||||
Sherry = 'Sherry',
|
||||
Staleness = 'Staleness',
|
||||
Toffee = 'Toffee'
|
||||
}
|
||||
|
||||
export enum Umami {
|
||||
FishSauce = 'Fish Sauce',
|
||||
Soy = 'Soy'
|
||||
}
|
||||
|
||||
export enum Balsamic {
|
||||
Balsamic = 'Balsamic'
|
||||
}
|
||||
|
||||
export enum Grain {
|
||||
CookedRice = 'Cooked Rice',
|
||||
RawRice = 'Raw Rice',
|
||||
SteamedRice = 'Steamed Rice',
|
||||
Cereal = 'Cereal',
|
||||
Barley = 'Barley',
|
||||
Oat = 'Oat',
|
||||
Wheat = 'Wheat',
|
||||
Grains = 'Grains',
|
||||
Corn = 'Corn',
|
||||
Malt = 'Malt'
|
||||
}
|
||||
|
||||
export enum Dairy {
|
||||
Butter = 'Butter',
|
||||
Cream = 'Cream',
|
||||
Milk = 'Milk',
|
||||
Yogurt = 'Yogurt'
|
||||
}
|
||||
|
||||
export enum Anisoles {
|
||||
Mustiness = 'Mustiness',
|
||||
Trichloroanisole = 'Trichloroanisole',
|
||||
WetCardboard = 'Wet cardboard'
|
||||
}
|
||||
|
||||
export enum Brettanomyces {
|
||||
Animal = 'Animal',
|
||||
Farmyard = 'Farmyard',
|
||||
Iodine = 'Iodine',
|
||||
Leather = 'Leather',
|
||||
Meaty = 'Meaty',
|
||||
Vinyl = 'Vinyl'
|
||||
}
|
||||
|
||||
export enum VolatileAcidity {
|
||||
Solvent = 'Solvent',
|
||||
NailVarnishRemover = 'Nail varnish remover',
|
||||
Vinegar = 'Vinegar'
|
||||
}
|
||||
|
||||
export enum Reduction {
|
||||
Cabbage = 'Cabbage',
|
||||
Eggs = 'Eggs',
|
||||
Garlic = 'Garlic',
|
||||
Mercaptans = 'Mercaptans',
|
||||
Onion = 'Onion',
|
||||
Rubber = 'Rubber',
|
||||
Sweat = 'Sweat'
|
||||
}
|
90
src/types/review/textureAndBalance.ts
Normal file
90
src/types/review/textureAndBalance.ts
Normal file
@ -0,0 +1,90 @@
|
||||
export enum TextureAndBalanceKey {
|
||||
Sweetness = 'sweetness',
|
||||
Acidity = 'acidity',
|
||||
Tannin = 'tannin',
|
||||
Alcohol = 'alcohol',
|
||||
Body = 'body',
|
||||
FlavourIntensity = 'flavourIntensity',
|
||||
PalateLength = 'palateLength',
|
||||
Reasoning = 'reasoning',
|
||||
Quality = 'quality',
|
||||
Age = 'age',
|
||||
ReadinessToDrink = 'readinessToDrink'
|
||||
}
|
||||
|
||||
export enum Sweetness {
|
||||
Dry = 'Dry',
|
||||
OffDry = 'Off-dry',
|
||||
Medium = 'Medium',
|
||||
Sweet = 'Sweet',
|
||||
Luscious = 'Luscious'
|
||||
}
|
||||
|
||||
export enum Concentration {
|
||||
Low = 'Low',
|
||||
Medium = 'Medium',
|
||||
High = 'High'
|
||||
}
|
||||
|
||||
export enum TanninType {
|
||||
Ripe = 'Ripe',
|
||||
Unripe = 'Unripe'
|
||||
}
|
||||
|
||||
export enum RipeTannin {
|
||||
Soft = 'Soft',
|
||||
FineGrained = 'Fine-grained',
|
||||
Coarse = 'Coarse'
|
||||
}
|
||||
|
||||
export enum UnripeTannin {
|
||||
Green = 'Green',
|
||||
Stalky = 'Stalky'
|
||||
}
|
||||
|
||||
export enum Body {
|
||||
Light = 'Light',
|
||||
Medium = 'Medium',
|
||||
Full = 'Full'
|
||||
}
|
||||
|
||||
export enum FlavourIntensity {
|
||||
Light = 'Light',
|
||||
Medium = 'Medium',
|
||||
Pronounced = 'Pronounced'
|
||||
}
|
||||
|
||||
export enum PalateLength {
|
||||
Short = 'Short',
|
||||
Medium = 'Medium',
|
||||
Pronounced = 'Pronounced',
|
||||
Exceptional = 'Exceptional'
|
||||
}
|
||||
|
||||
export enum ReasoningConcentration {
|
||||
Low = 'Low',
|
||||
High = 'High'
|
||||
}
|
||||
|
||||
export enum Quality {
|
||||
NS = 'NS',
|
||||
Poor = 'Poor',
|
||||
Acceptable = 'Acceptable',
|
||||
Good = 'Good',
|
||||
VeryGood = 'Very good',
|
||||
Excellent = 'Excellent',
|
||||
Outstanding = 'Outstanding'
|
||||
}
|
||||
|
||||
export enum ReadinessToDrink {
|
||||
TooYoung = 'Too young',
|
||||
DrinkWithPotentialForAgeing = 'Drink with potential for ageing',
|
||||
DrinkNow = 'Drink Now',
|
||||
TooOldPassed = 'Too old/Passed'
|
||||
}
|
||||
|
||||
export enum ReasoningKey {
|
||||
Balance = 'balance',
|
||||
Concentration = 'concentration',
|
||||
Complex = 'complex'
|
||||
}
|
17
src/types/review/visualAssessment.ts
Normal file
17
src/types/review/visualAssessment.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export enum VisualAssessmentKey {
|
||||
Clarity = 'clarity',
|
||||
Nature = 'nature',
|
||||
Colour = 'colour'
|
||||
}
|
||||
|
||||
export enum ClarityVisualAssessment {
|
||||
Clear = 'Clear',
|
||||
Cloudy = 'Cloudy',
|
||||
Opaque = 'Opaque'
|
||||
}
|
||||
|
||||
export enum NatureVisualAssessment {
|
||||
Still = 'Still',
|
||||
Frizzante = 'Frizzante',
|
||||
Sparkling = 'Sparkling'
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export enum Routes {
|
||||
export enum Route {
|
||||
Users = '/users',
|
||||
NostrEvents = '/nostr',
|
||||
Reviews = '/reviews',
|
||||
|
@ -80,7 +80,7 @@ export interface SakePolishMin {
|
||||
min: number
|
||||
}
|
||||
|
||||
export enum SakeCharacteristics {
|
||||
export enum SakeCharacteristic {
|
||||
LightAndRefreshing = 'Light and Refreshing',
|
||||
CleanAndCrisp = 'Clean and Crisp',
|
||||
FruityAndAromatic = 'Fruity and Aromatic',
|
||||
|
@ -42,7 +42,7 @@ export enum SpiritVolume {
|
||||
'1L' = '1L'
|
||||
}
|
||||
|
||||
export enum SpiritCharacteristics {
|
||||
export enum SpiritCharacteristic {
|
||||
LightAndNeutral = 'Light and Neutral',
|
||||
FruityAndAromatic = 'Fruity and Aromatic',
|
||||
HerbalAndBotanical = 'Herbal and Botanical',
|
||||
@ -51,7 +51,7 @@ export enum SpiritCharacteristics {
|
||||
RichAndFullBodied = 'Rich and Full-Bodied'
|
||||
}
|
||||
|
||||
export enum WhiteSpiritVariants {
|
||||
export enum WhiteSpiritVariant {
|
||||
Absinthe = 'Absinthe',
|
||||
Pastis = 'Pastis',
|
||||
Vodka = 'Vodka',
|
||||
@ -67,7 +67,7 @@ export enum WhiteSpiritVariants {
|
||||
Arrack = 'Arrack'
|
||||
}
|
||||
|
||||
export enum DarkSpiritVariants {
|
||||
export enum DarkSpiritVariant {
|
||||
Absinthe = 'Absinthe',
|
||||
Brandy = 'Brandy',
|
||||
Calvados = 'Calvados',
|
||||
@ -80,7 +80,7 @@ export enum DarkSpiritVariants {
|
||||
Whiskey = 'Whiskey'
|
||||
}
|
||||
|
||||
export enum LiqueursSpiritVariants {
|
||||
export enum LiqueursSpiritVariant {
|
||||
Amaro = 'Amaro',
|
||||
Coffee = 'Coffee',
|
||||
Cream = 'Cream',
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { SakeVolume, SpiritVolume, StandardDrinks, WineVolume } from '../types'
|
||||
import { SakeVolume, SpiritVolume, StandardDrink, WineVolume } from '../types'
|
||||
import { roundToOneDecimal } from './'
|
||||
|
||||
export const alcoholToStandardDrinks = (
|
||||
alcohol: number,
|
||||
bottle: number
|
||||
): StandardDrinks => {
|
||||
): StandardDrink => {
|
||||
const UK100ml = roundToOneDecimal(10 * alcohol)
|
||||
const AU100ml = roundToOneDecimal(7.91 * alcohol)
|
||||
const US100ml = roundToOneDecimal(5.64 * alcohol)
|
||||
|
@ -1,30 +1,27 @@
|
||||
import {
|
||||
SpiritCharacteristics,
|
||||
SpiritCharacteristic,
|
||||
SpiritType,
|
||||
WhiteSpiritVariants,
|
||||
DarkSpiritVariants,
|
||||
LiqueursSpiritVariants
|
||||
WhiteSpiritVariant,
|
||||
DarkSpiritVariant,
|
||||
LiqueursSpiritVariant
|
||||
} from '../types'
|
||||
|
||||
// TODO: improve types
|
||||
export const spiritVariantMap: {
|
||||
[key in SpiritType]:
|
||||
| {
|
||||
[key in WhiteSpiritVariants]: (string | { [key: string]: string[] })[]
|
||||
[key in WhiteSpiritVariant]: (string | { [key: string]: string[] })[]
|
||||
}
|
||||
| {
|
||||
[key in DarkSpiritVariants]: (string | { [key: string]: string[] })[]
|
||||
[key in DarkSpiritVariant]: (string | { [key: string]: string[] })[]
|
||||
}
|
||||
| {
|
||||
[key in LiqueursSpiritVariants]: (
|
||||
| string
|
||||
| { [key: string]: string[] }
|
||||
)[]
|
||||
[key in LiqueursSpiritVariant]: (string | { [key: string]: string[] })[]
|
||||
}
|
||||
} = {
|
||||
[SpiritType.White]: {
|
||||
[WhiteSpiritVariants.Absinthe]: ['Blanche'],
|
||||
[WhiteSpiritVariants.Pastis]: [
|
||||
[WhiteSpiritVariant.Absinthe]: ['Blanche'],
|
||||
[WhiteSpiritVariant.Pastis]: [
|
||||
'Anise',
|
||||
'Fennel',
|
||||
'Licorice Root',
|
||||
@ -36,7 +33,7 @@ export const spiritVariantMap: {
|
||||
'Cinnamon',
|
||||
'Clove'
|
||||
],
|
||||
[WhiteSpiritVariants.Vodka]: [
|
||||
[WhiteSpiritVariant.Vodka]: [
|
||||
'Wheat',
|
||||
'Rye',
|
||||
'Corn',
|
||||
@ -46,12 +43,12 @@ export const spiritVariantMap: {
|
||||
'Fruits',
|
||||
'Grains'
|
||||
],
|
||||
[WhiteSpiritVariants.Genever]: [
|
||||
[WhiteSpiritVariant.Genever]: [
|
||||
{
|
||||
Young: ['Juniper']
|
||||
}
|
||||
],
|
||||
[WhiteSpiritVariants.Gin]: [
|
||||
[WhiteSpiritVariant.Gin]: [
|
||||
{
|
||||
'London Dry': [
|
||||
'Juniper',
|
||||
@ -68,11 +65,11 @@ export const spiritVariantMap: {
|
||||
},
|
||||
'Plymouth'
|
||||
],
|
||||
[WhiteSpiritVariants.Mezcal]: [
|
||||
[WhiteSpiritVariant.Mezcal]: [
|
||||
{ Joven: ['Espadín', 'Tepeztate', 'Tequilana (blue)', 'Tobalá'] }
|
||||
],
|
||||
[WhiteSpiritVariants.Rum]: ['Blanco', 'Cachaça', 'Platino', 'Agricole'],
|
||||
[WhiteSpiritVariants.EauDeVie]: [
|
||||
[WhiteSpiritVariant.Rum]: ['Blanco', 'Cachaça', 'Platino', 'Agricole'],
|
||||
[WhiteSpiritVariant.EauDeVie]: [
|
||||
'Apple',
|
||||
'Blackcurrant',
|
||||
'Butterscotch',
|
||||
@ -81,27 +78,27 @@ export const spiritVariantMap: {
|
||||
'Plum',
|
||||
'Raspberries'
|
||||
],
|
||||
[WhiteSpiritVariants.Grappa]: ['Marc', 'Pisco'],
|
||||
[WhiteSpiritVariants.Baijiu]: [
|
||||
[WhiteSpiritVariant.Grappa]: ['Marc', 'Pisco'],
|
||||
[WhiteSpiritVariant.Baijiu]: [
|
||||
'Sorghum',
|
||||
'Wheat',
|
||||
'Barley',
|
||||
'Rice',
|
||||
'Millet'
|
||||
],
|
||||
[WhiteSpiritVariants.Soju]: [
|
||||
[WhiteSpiritVariant.Soju]: [
|
||||
'Barley',
|
||||
'Brown sugar',
|
||||
'Buckwheat',
|
||||
'Rice',
|
||||
'Sweet Potato'
|
||||
],
|
||||
[WhiteSpiritVariants.Aquavit]: [],
|
||||
[WhiteSpiritVariants.Arrack]: []
|
||||
[WhiteSpiritVariant.Aquavit]: [],
|
||||
[WhiteSpiritVariant.Arrack]: []
|
||||
},
|
||||
[SpiritType.Dark]: {
|
||||
[DarkSpiritVariants.Absinthe]: ['Jaune', 'Verte'],
|
||||
[DarkSpiritVariants.Brandy]: [
|
||||
[DarkSpiritVariant.Absinthe]: ['Jaune', 'Verte'],
|
||||
[DarkSpiritVariant.Brandy]: [
|
||||
{
|
||||
Grape: [
|
||||
'VS',
|
||||
@ -114,14 +111,11 @@ export const spiritVariantMap: {
|
||||
]
|
||||
}
|
||||
],
|
||||
[DarkSpiritVariants.Calvados]: ['Apple', 'Pear'],
|
||||
[DarkSpiritVariants.Chartreuse]: ['Green', 'Yellow'],
|
||||
[DarkSpiritVariants.Genever]: [
|
||||
{ Old: ['Juniper'] },
|
||||
{ Coren: ['Juniper'] }
|
||||
],
|
||||
[DarkSpiritVariants.Mezcal]: ['Reposado', 'Abuelo', 'Añejo', 'Extra Añejo'],
|
||||
[DarkSpiritVariants.Rum]: [
|
||||
[DarkSpiritVariant.Calvados]: ['Apple', 'Pear'],
|
||||
[DarkSpiritVariant.Chartreuse]: ['Green', 'Yellow'],
|
||||
[DarkSpiritVariant.Genever]: [{ Old: ['Juniper'] }, { Coren: ['Juniper'] }],
|
||||
[DarkSpiritVariant.Mezcal]: ['Reposado', 'Abuelo', 'Añejo', 'Extra Añejo'],
|
||||
[DarkSpiritVariant.Rum]: [
|
||||
{
|
||||
Sugar: [
|
||||
'Cachaca (amarela/ouro)',
|
||||
@ -133,8 +127,8 @@ export const spiritVariantMap: {
|
||||
]
|
||||
}
|
||||
],
|
||||
[DarkSpiritVariants.Slivovitz]: [],
|
||||
[DarkSpiritVariants.Whiskey]: [
|
||||
[DarkSpiritVariant.Slivovitz]: [],
|
||||
[DarkSpiritVariant.Whiskey]: [
|
||||
'Barley',
|
||||
'Rye',
|
||||
'Wheat',
|
||||
@ -142,18 +136,18 @@ export const spiritVariantMap: {
|
||||
'Oat',
|
||||
'Rice'
|
||||
],
|
||||
[DarkSpiritVariants.Arrack]: []
|
||||
[DarkSpiritVariant.Arrack]: []
|
||||
},
|
||||
[SpiritType.Liqueurs]: {
|
||||
[LiqueursSpiritVariants.Amaro]: [],
|
||||
[LiqueursSpiritVariants.Coffee]: [],
|
||||
[LiqueursSpiritVariants.Cream]: [
|
||||
[LiqueursSpiritVariant.Amaro]: [],
|
||||
[LiqueursSpiritVariant.Coffee]: [],
|
||||
[LiqueursSpiritVariant.Cream]: [
|
||||
'Egg (Advocaat)',
|
||||
'Rum',
|
||||
'Strawberry',
|
||||
'Whiskey (Baileys etc)'
|
||||
],
|
||||
[LiqueursSpiritVariants.Creme]: [
|
||||
[LiqueursSpiritVariant.Creme]: [
|
||||
'Almond',
|
||||
'Banana',
|
||||
'Blackcurrant',
|
||||
@ -162,8 +156,8 @@ export const spiritVariantMap: {
|
||||
'Sour Cherry',
|
||||
'Violet'
|
||||
],
|
||||
[LiqueursSpiritVariants.Flowers]: ['Rose', 'Violet', 'Elderflower'],
|
||||
[LiqueursSpiritVariants.Fruit]: [
|
||||
[LiqueursSpiritVariant.Flowers]: ['Rose', 'Violet', 'Elderflower'],
|
||||
[LiqueursSpiritVariant.Fruit]: [
|
||||
'Blackcurrant',
|
||||
'Lemon',
|
||||
'Melon',
|
||||
@ -173,7 +167,7 @@ export const spiritVariantMap: {
|
||||
'Raspberry',
|
||||
'Yuzu'
|
||||
],
|
||||
[LiqueursSpiritVariants.Herb]: [
|
||||
[LiqueursSpiritVariant.Herb]: [
|
||||
'Anise',
|
||||
'Dom Benedictine',
|
||||
'Bitters',
|
||||
@ -182,8 +176,8 @@ export const spiritVariantMap: {
|
||||
'Metaxa',
|
||||
'Mint'
|
||||
],
|
||||
[LiqueursSpiritVariants.Honey]: ['Licor 43', 'Rum', 'Vodka', 'Whiskey'],
|
||||
[LiqueursSpiritVariants.Nut]: [
|
||||
[LiqueursSpiritVariant.Honey]: ['Licor 43', 'Rum', 'Vodka', 'Whiskey'],
|
||||
[LiqueursSpiritVariant.Nut]: [
|
||||
'Almond',
|
||||
'Apricot Kernel',
|
||||
'Hazelnut',
|
||||
@ -196,62 +190,62 @@ export const spiritVariantMap: {
|
||||
}
|
||||
|
||||
export const spiritCharacteristicsMap: {
|
||||
[key in SpiritCharacteristics]: string[]
|
||||
[key in SpiritCharacteristic]: string[]
|
||||
} = {
|
||||
[SpiritCharacteristics.LightAndNeutral]: [
|
||||
WhiteSpiritVariants.Mezcal,
|
||||
WhiteSpiritVariants.Soju,
|
||||
WhiteSpiritVariants.Vodka,
|
||||
WhiteSpiritVariants.Rum,
|
||||
WhiteSpiritVariants.Aquavit
|
||||
[SpiritCharacteristic.LightAndNeutral]: [
|
||||
WhiteSpiritVariant.Mezcal,
|
||||
WhiteSpiritVariant.Soju,
|
||||
WhiteSpiritVariant.Vodka,
|
||||
WhiteSpiritVariant.Rum,
|
||||
WhiteSpiritVariant.Aquavit
|
||||
],
|
||||
[SpiritCharacteristics.FruityAndAromatic]: [
|
||||
WhiteSpiritVariants.Rum,
|
||||
DarkSpiritVariants.Calvados,
|
||||
WhiteSpiritVariants.EauDeVie,
|
||||
LiqueursSpiritVariants.Fruit,
|
||||
WhiteSpiritVariants.Gin,
|
||||
WhiteSpiritVariants.Grappa
|
||||
[SpiritCharacteristic.FruityAndAromatic]: [
|
||||
WhiteSpiritVariant.Rum,
|
||||
DarkSpiritVariant.Calvados,
|
||||
WhiteSpiritVariant.EauDeVie,
|
||||
LiqueursSpiritVariant.Fruit,
|
||||
WhiteSpiritVariant.Gin,
|
||||
WhiteSpiritVariant.Grappa
|
||||
],
|
||||
[SpiritCharacteristics.HerbalAndBotanical]: [
|
||||
WhiteSpiritVariants.Absinthe,
|
||||
DarkSpiritVariants.Absinthe,
|
||||
LiqueursSpiritVariants.Amaro,
|
||||
WhiteSpiritVariants.Genever,
|
||||
DarkSpiritVariants.Genever,
|
||||
WhiteSpiritVariants.Gin,
|
||||
WhiteSpiritVariants.Pastis,
|
||||
DarkSpiritVariants.Chartreuse,
|
||||
WhiteSpiritVariants.Aquavit
|
||||
[SpiritCharacteristic.HerbalAndBotanical]: [
|
||||
WhiteSpiritVariant.Absinthe,
|
||||
DarkSpiritVariant.Absinthe,
|
||||
LiqueursSpiritVariant.Amaro,
|
||||
WhiteSpiritVariant.Genever,
|
||||
DarkSpiritVariant.Genever,
|
||||
WhiteSpiritVariant.Gin,
|
||||
WhiteSpiritVariant.Pastis,
|
||||
DarkSpiritVariant.Chartreuse,
|
||||
WhiteSpiritVariant.Aquavit
|
||||
],
|
||||
|
||||
[SpiritCharacteristics.SweetAndSyrupy]: [
|
||||
DarkSpiritVariants.Brandy,
|
||||
LiqueursSpiritVariants.Cream,
|
||||
LiqueursSpiritVariants.Creme,
|
||||
DarkSpiritVariants.Rum,
|
||||
LiqueursSpiritVariants.Nut
|
||||
[SpiritCharacteristic.SweetAndSyrupy]: [
|
||||
DarkSpiritVariant.Brandy,
|
||||
LiqueursSpiritVariant.Cream,
|
||||
LiqueursSpiritVariant.Creme,
|
||||
DarkSpiritVariant.Rum,
|
||||
LiqueursSpiritVariant.Nut
|
||||
],
|
||||
[SpiritCharacteristics.SmokyAndSpicy]: [
|
||||
WhiteSpiritVariants.Baijiu,
|
||||
DarkSpiritVariants.Rum,
|
||||
WhiteSpiritVariants.Gin,
|
||||
WhiteSpiritVariants.EauDeVie,
|
||||
DarkSpiritVariants.Mezcal,
|
||||
DarkSpiritVariants.Whiskey,
|
||||
LiqueursSpiritVariants.Cream,
|
||||
LiqueursSpiritVariants.Honey,
|
||||
WhiteSpiritVariants.Arrack,
|
||||
DarkSpiritVariants.Arrack
|
||||
[SpiritCharacteristic.SmokyAndSpicy]: [
|
||||
WhiteSpiritVariant.Baijiu,
|
||||
DarkSpiritVariant.Rum,
|
||||
WhiteSpiritVariant.Gin,
|
||||
WhiteSpiritVariant.EauDeVie,
|
||||
DarkSpiritVariant.Mezcal,
|
||||
DarkSpiritVariant.Whiskey,
|
||||
LiqueursSpiritVariant.Cream,
|
||||
LiqueursSpiritVariant.Honey,
|
||||
WhiteSpiritVariant.Arrack,
|
||||
DarkSpiritVariant.Arrack
|
||||
],
|
||||
[SpiritCharacteristics.RichAndFullBodied]: [
|
||||
WhiteSpiritVariants.Baijiu,
|
||||
DarkSpiritVariants.Brandy,
|
||||
DarkSpiritVariants.Rum,
|
||||
WhiteSpiritVariants.Grappa,
|
||||
WhiteSpiritVariants.EauDeVie,
|
||||
LiqueursSpiritVariants.Cream,
|
||||
LiqueursSpiritVariants.Honey,
|
||||
DarkSpiritVariants.Whiskey
|
||||
[SpiritCharacteristic.RichAndFullBodied]: [
|
||||
WhiteSpiritVariant.Baijiu,
|
||||
DarkSpiritVariant.Brandy,
|
||||
DarkSpiritVariant.Rum,
|
||||
WhiteSpiritVariant.Grappa,
|
||||
WhiteSpiritVariant.EauDeVie,
|
||||
LiqueursSpiritVariant.Cream,
|
||||
LiqueursSpiritVariant.Honey,
|
||||
DarkSpiritVariant.Whiskey
|
||||
]
|
||||
}
|
||||
|
@ -3,3 +3,6 @@ export const roundToOneDecimal = (number: number) =>
|
||||
|
||||
export const isObject = (item: unknown) =>
|
||||
typeof item === 'object' && !Array.isArray(item) && item !== null
|
||||
|
||||
export const compareArrays = (a: unknown[], b: unknown[]) =>
|
||||
JSON.stringify(a.sort()) === JSON.stringify(b.sort())
|
||||
|
@ -1,6 +1,7 @@
|
||||
export * from './user'
|
||||
export * from './nostr'
|
||||
export * from './review'
|
||||
export * from './review'
|
||||
export * from './wine'
|
||||
export * from './spirit'
|
||||
export * from './product'
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { collections } from '../../services/database.service'
|
||||
import { DBcollections, ProductType } from '../../types'
|
||||
import { DBcollection, ProductType } from '../../types'
|
||||
|
||||
export const productCodeValidation = async (
|
||||
ean: string,
|
||||
upc: string,
|
||||
sku: string,
|
||||
collection: DBcollections,
|
||||
collection: DBcollection,
|
||||
productType: ProductType
|
||||
) => {
|
||||
if (!ean && !upc && !sku) {
|
||||
|
@ -1,16 +1,797 @@
|
||||
import Joi from 'joi'
|
||||
import { RatingOptions } from '../../types'
|
||||
import {
|
||||
RatingOption,
|
||||
TastingNote,
|
||||
TastingNoteKey,
|
||||
VisualAssessmentKey,
|
||||
ClarityVisualAssessment,
|
||||
NatureVisualAssessment,
|
||||
WhiteColour,
|
||||
AmberColour,
|
||||
RoseColour,
|
||||
RedColour,
|
||||
BlueColour,
|
||||
GreenColour,
|
||||
ProductType,
|
||||
WineColour,
|
||||
SakeColour,
|
||||
SpiritColour,
|
||||
PrimaryFlavoursAndAromasKey,
|
||||
Condition,
|
||||
Intensity,
|
||||
Age,
|
||||
CitrusFruit,
|
||||
AppleFruit,
|
||||
StoneFruit,
|
||||
RedFruit,
|
||||
BlackFruit,
|
||||
ChocolateFruit,
|
||||
TropicalFruit,
|
||||
MelonFruit,
|
||||
Floral,
|
||||
Vegetal,
|
||||
Earth,
|
||||
Microbial,
|
||||
Oak,
|
||||
Chocolate,
|
||||
Oxidation,
|
||||
Umami,
|
||||
Balsamic,
|
||||
Grain,
|
||||
Dairy,
|
||||
Anisoles,
|
||||
Brettanomyces,
|
||||
VolatileAcidity,
|
||||
Reduction,
|
||||
TextureAndBalanceKey,
|
||||
Sweetness,
|
||||
Concentration,
|
||||
TanninType,
|
||||
RipeTannin,
|
||||
UnripeTannin,
|
||||
Body,
|
||||
FlavourIntensity,
|
||||
PalateLength,
|
||||
ReasoningKey,
|
||||
ReasoningConcentration,
|
||||
Quality,
|
||||
ReadinessToDrink
|
||||
} from '../../types'
|
||||
import { compareArrays, isObject } from '../utils'
|
||||
import { producerIdValidation } from './'
|
||||
|
||||
export const reviewValidation = (data: unknown): Joi.ValidationResult =>
|
||||
Joi.object({
|
||||
eventId: Joi.string().required(),
|
||||
productId: Joi.string().required(),
|
||||
productId: producerIdValidation,
|
||||
productType: Joi.string()
|
||||
.valid(...Object.values(ProductType))
|
||||
.required(),
|
||||
rating: Joi.alternatives()
|
||||
.try(
|
||||
Joi.string().valid(...Object.values(RatingOptions)),
|
||||
Joi.string().valid(...Object.values(RatingOption)),
|
||||
Joi.number().min(84).max(100)
|
||||
)
|
||||
.required(),
|
||||
reviewText: Joi.string().required(),
|
||||
tastingNotes: Joi.array().items(Joi.string())
|
||||
tastingNote: Joi.object()
|
||||
.custom((tastingNote: TastingNote, helper) => {
|
||||
const message = (str: string) =>
|
||||
helper.message({
|
||||
custom: Joi.expression(str)
|
||||
})
|
||||
|
||||
/**
|
||||
* Root keys validation
|
||||
*/
|
||||
const tastingNoteKeys = Object.keys(tastingNote)
|
||||
const validTastingNoteKeys = Object.values(TastingNoteKey)
|
||||
|
||||
if (!compareArrays(tastingNoteKeys, validTastingNoteKeys)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "tastingNote" object has to include the following keys: [${validTastingNoteKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* visualAssessment validation
|
||||
*/
|
||||
const visualAssessmentKeys = Object.keys(
|
||||
tastingNote[TastingNoteKey.VisualAssessment]
|
||||
)
|
||||
const validVisualAssessmentKeys = Object.values(VisualAssessmentKey)
|
||||
|
||||
if (!compareArrays(visualAssessmentKeys, validVisualAssessmentKeys)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "visualAssessment-visualAssessment" object has to include the following keys: [${validVisualAssessmentKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* visualAssessment-clarity validation
|
||||
*/
|
||||
const clarity =
|
||||
tastingNote[TastingNoteKey.VisualAssessment][
|
||||
VisualAssessmentKey.Clarity
|
||||
]
|
||||
|
||||
if (
|
||||
typeof clarity !== 'string' ||
|
||||
!(clarity in ClarityVisualAssessment)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-clarity" are: [${Object.values(ClarityVisualAssessment).join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* visualAssessment-nature validation
|
||||
*/
|
||||
const nature =
|
||||
tastingNote[TastingNoteKey.VisualAssessment][
|
||||
VisualAssessmentKey.Nature
|
||||
]
|
||||
|
||||
if (typeof nature !== 'string' || !(nature in NatureVisualAssessment)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-nature" are: [${Object.values(NatureVisualAssessment).join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* visualAssessment-colour validation
|
||||
*/
|
||||
const colour =
|
||||
tastingNote[TastingNoteKey.VisualAssessment][
|
||||
VisualAssessmentKey.Colour
|
||||
]
|
||||
const validWhiteColourOptions: WhiteColour[] =
|
||||
Object.values(WhiteColour)
|
||||
const validAmberColourOptions: AmberColour[] =
|
||||
Object.values(AmberColour)
|
||||
const validRoseColourOptions = Object.values(RoseColour)
|
||||
const validRedColourOptions = Object.values(RedColour)
|
||||
const validBlueColourOptions = Object.values(BlueColour)
|
||||
const validGreenColourOptions = Object.values(GreenColour)
|
||||
|
||||
if (
|
||||
typeof colour !== 'string' ||
|
||||
(!validWhiteColourOptions.includes(colour as WhiteColour) &&
|
||||
!validAmberColourOptions.includes(colour as AmberColour) &&
|
||||
!validRoseColourOptions.includes(colour as RoseColour) &&
|
||||
!validRedColourOptions.includes(colour as RedColour) &&
|
||||
!validBlueColourOptions.includes(colour as BlueColour) &&
|
||||
!validGreenColourOptions.includes(colour as GreenColour))
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-colour" are: [${[
|
||||
...validWhiteColourOptions,
|
||||
...validAmberColourOptions,
|
||||
...validRoseColourOptions,
|
||||
...validRedColourOptions,
|
||||
...validBlueColourOptions,
|
||||
...validGreenColourOptions
|
||||
].join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// check if colour is applicable to the product
|
||||
const productType: ProductType = helper.state.ancestors[0].productType
|
||||
|
||||
switch (productType) {
|
||||
case ProductType.Wine:
|
||||
{
|
||||
const validWineColours = Object.values(WineColour)
|
||||
|
||||
if (!(validWineColours as string[]).includes(colour)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-colour" for ${productType} are: [${validWineColours.join(', ')}]`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case ProductType.Sake:
|
||||
{
|
||||
const validSakeColours = Object.values(SakeColour)
|
||||
|
||||
if (!(validSakeColours as string[]).includes(colour)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-colour" for ${productType} are: [${validSakeColours.join(', ')}]`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case ProductType.Spirit:
|
||||
{
|
||||
const validSpiritColours = Object.values(SpiritColour)
|
||||
|
||||
if (!(validSpiritColours as string[]).includes(colour)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-colour" for ${productType} are: [${validSpiritColours.join(', ')}]`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
/**
|
||||
* primaryFlavoursAndAromas validation
|
||||
*/
|
||||
const primaryFlavoursAndAromasKeys = Object.keys(
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas]
|
||||
)
|
||||
const validPrimaryFlavoursAndAromasKeys = Object.values(
|
||||
PrimaryFlavoursAndAromasKey
|
||||
)
|
||||
|
||||
if (
|
||||
!compareArrays(
|
||||
primaryFlavoursAndAromasKeys,
|
||||
validPrimaryFlavoursAndAromasKeys
|
||||
)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "visualAssessment-primaryFlavoursAndAromas" object has to include the following keys: [${validPrimaryFlavoursAndAromasKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-condition validation
|
||||
const condition =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Condition
|
||||
]
|
||||
const validConditionOptions: Condition[] = Object.values(Condition)
|
||||
|
||||
if (
|
||||
typeof condition !== 'string' ||
|
||||
!validConditionOptions.includes(condition)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-condition" are: [${validConditionOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-intensity validation
|
||||
const intensity =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Intensity
|
||||
]
|
||||
const validIntensityOptions: Intensity[] = Object.values(Intensity)
|
||||
|
||||
if (
|
||||
typeof intensity !== 'string' ||
|
||||
!validIntensityOptions.includes(intensity)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-intensity" are: [${validIntensityOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-age validation
|
||||
const age =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Age
|
||||
]
|
||||
const validAgeOptions: Age[] = Object.values(Age)
|
||||
|
||||
if (typeof age !== 'string' || !validAgeOptions.includes(age)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-age" are: [${validAgeOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-fruit validation
|
||||
const fruit =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Fruit
|
||||
]
|
||||
const validFruitOptions: (
|
||||
| CitrusFruit
|
||||
| AppleFruit
|
||||
| StoneFruit
|
||||
| RedFruit
|
||||
| BlackFruit
|
||||
| ChocolateFruit
|
||||
| TropicalFruit
|
||||
| MelonFruit
|
||||
)[] = [
|
||||
...Object.values(CitrusFruit),
|
||||
...Object.values(AppleFruit),
|
||||
...Object.values(StoneFruit),
|
||||
...Object.values(RedFruit),
|
||||
...Object.values(BlackFruit),
|
||||
...Object.values(ChocolateFruit),
|
||||
...Object.values(TropicalFruit),
|
||||
...Object.values(MelonFruit)
|
||||
]
|
||||
|
||||
if (typeof fruit !== 'string' || !validFruitOptions.includes(fruit)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-fruit" are: [${validFruitOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-floral validation
|
||||
const floral =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Floral
|
||||
]
|
||||
const validFloralOptions: Floral[] = Object.values(Floral)
|
||||
|
||||
if (
|
||||
typeof floral !== 'string' ||
|
||||
!validFloralOptions.includes(floral)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-floral" are: [${validFloralOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-vegetal validation
|
||||
const vegetal =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Vegetal
|
||||
]
|
||||
const validVegetalOptions: Vegetal[] = Object.values(Vegetal)
|
||||
|
||||
if (
|
||||
typeof vegetal !== 'string' ||
|
||||
!validVegetalOptions.includes(vegetal)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-vegetal" are: [${validVegetalOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-earth validation
|
||||
const earth =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Earth
|
||||
]
|
||||
const validEarthOptions: Earth[] = Object.values(Earth)
|
||||
|
||||
if (typeof earth !== 'string' || !validEarthOptions.includes(earth)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-earth" are: [${validEarthOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-earth validation
|
||||
const microbial =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Microbial
|
||||
]
|
||||
const validMicrobialOptions: Microbial[] = Object.values(Microbial)
|
||||
|
||||
if (
|
||||
typeof microbial !== 'string' ||
|
||||
!validMicrobialOptions.includes(microbial)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-microbial" are: [${validMicrobialOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-oak validation
|
||||
const oak =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Oak
|
||||
]
|
||||
const validOakOptions: Oak[] = Object.values(Oak)
|
||||
|
||||
if (typeof oak !== 'string' || !validOakOptions.includes(oak)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-oak" are: [${validOakOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-chocolate validation
|
||||
const chocolate =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Chocolate
|
||||
]
|
||||
const validChocolateOptions: Chocolate[] = Object.values(Chocolate)
|
||||
|
||||
if (
|
||||
typeof chocolate !== 'string' ||
|
||||
!validChocolateOptions.includes(chocolate)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-chocolate" are: [${validChocolateOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-oxidation validation
|
||||
const oxidation =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Oxidation
|
||||
]
|
||||
const validOxidationOptions: Oxidation[] = Object.values(Oxidation)
|
||||
|
||||
if (
|
||||
typeof oxidation !== 'string' ||
|
||||
!validOxidationOptions.includes(oxidation)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-oxidation" are: [${validOxidationOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-umami validation
|
||||
const umami =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Umami
|
||||
]
|
||||
const validUmamiOptions: Umami[] = Object.values(Umami)
|
||||
|
||||
if (typeof umami !== 'string' || !validUmamiOptions.includes(umami)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-umami" are: [${validUmamiOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-balsamic validation
|
||||
const balsamic =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Balsamic
|
||||
]
|
||||
const validBalsamicOptions: Balsamic[] = Object.values(Balsamic)
|
||||
|
||||
if (
|
||||
typeof balsamic !== 'string' ||
|
||||
!validBalsamicOptions.includes(balsamic)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-balsamic" are: [${validBalsamicOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-grain validation
|
||||
const grain =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Grain
|
||||
]
|
||||
const validGrainOptions: Grain[] = Object.values(Grain)
|
||||
|
||||
if (typeof grain !== 'string' || !validGrainOptions.includes(grain)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-grain" are: [${validGrainOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-dairy validation
|
||||
const dairy =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Dairy
|
||||
]
|
||||
const validDairyOptions: Dairy[] = Object.values(Dairy)
|
||||
|
||||
if (typeof dairy !== 'string' || !validDairyOptions.includes(dairy)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-dairy" are: [${validDairyOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// primaryFlavoursAndAromas-faults validation
|
||||
const faults =
|
||||
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
|
||||
PrimaryFlavoursAndAromasKey.Faults
|
||||
]
|
||||
const validFaultsOptions: (
|
||||
| Anisoles
|
||||
| Brettanomyces
|
||||
| VolatileAcidity
|
||||
| Reduction
|
||||
)[] = [
|
||||
...Object.values(Anisoles),
|
||||
...Object.values(Brettanomyces),
|
||||
...Object.values(VolatileAcidity),
|
||||
...Object.values(Reduction)
|
||||
]
|
||||
|
||||
if (
|
||||
typeof faults !== 'string' ||
|
||||
!validFaultsOptions.includes(faults)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "visualAssessment-faults" are: [${validFaultsOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* textureAndBalance validation
|
||||
*/
|
||||
const textureAndBalanceKeys = Object.keys(
|
||||
tastingNote[TastingNoteKey.TextureAndBalance]
|
||||
)
|
||||
const validTextureAndBalanceKeys = Object.values(TextureAndBalanceKey)
|
||||
|
||||
if (!compareArrays(textureAndBalanceKeys, validTextureAndBalanceKeys)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "visualAssessment-textureAndBalance" object has to include the following keys: [${validTextureAndBalanceKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-sweetness validation
|
||||
const sweetness =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Sweetness
|
||||
]
|
||||
const validSweetnessOptions: Sweetness[] = Object.values(Sweetness)
|
||||
|
||||
if (
|
||||
typeof sweetness !== 'string' ||
|
||||
!validSweetnessOptions.includes(sweetness)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-sweetness" are: [${validSweetnessOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-acidity validation
|
||||
const acidity =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Acidity
|
||||
]
|
||||
const validAcidityOptions: Concentration[] =
|
||||
Object.values(Concentration)
|
||||
|
||||
if (
|
||||
typeof acidity !== 'string' ||
|
||||
!validAcidityOptions.includes(acidity)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-acidity" are: [${validAcidityOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-tannin validation
|
||||
const tannin =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Tannin
|
||||
]
|
||||
const tanninKeys = Object.keys(tannin)
|
||||
const tanninKey = tanninKeys[0] as Concentration
|
||||
const validTanninKeys = Object.values(Concentration)
|
||||
|
||||
if (!isObject(tannin)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "textureAndBalance-tannin" should be an object with the following properties: [${validTanninKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
if (tanninKeys.length !== 1 || !validTanninKeys.includes(tanninKey)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-tannin" are: [${validTanninKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-tannin-type validation
|
||||
const tanninType =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Tannin
|
||||
][tanninKey]
|
||||
const tanninTypeKeys = Object.keys(tanninType)
|
||||
const tanninTypeKey = tanninTypeKeys[0] as TanninType
|
||||
const validTanninTypeKeys = Object.values(TanninType)
|
||||
|
||||
if (!isObject(tanninType)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "textureAndBalance-tannin-type" should be an object with the following properties: [${validTanninTypeKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
tanninTypeKeys.length !== 1 ||
|
||||
!validTanninTypeKeys.includes(tanninTypeKey)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-tannin-type" are: [${validTanninTypeKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-tannin-value validation
|
||||
const tanninValue =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Tannin
|
||||
][tanninKey][tanninTypeKey]
|
||||
const validTanninValueOptions = [
|
||||
...Object.values(RipeTannin),
|
||||
...Object.values(UnripeTannin)
|
||||
]
|
||||
|
||||
if (
|
||||
typeof tanninValue !== 'string' ||
|
||||
!validTanninValueOptions.includes(tanninValue)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-tannin-type-value" are: [${validTanninValueOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-alcohol validation
|
||||
const alcohol =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Alcohol
|
||||
]
|
||||
const validAlcoholOptions: Concentration[] =
|
||||
Object.values(Concentration)
|
||||
|
||||
if (
|
||||
typeof alcohol !== 'string' ||
|
||||
!validAlcoholOptions.includes(alcohol)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-alcohol" are: [${validAlcoholOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-body validation
|
||||
const body =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Body
|
||||
]
|
||||
const validBodyOptions: Body[] = Object.values(Body)
|
||||
|
||||
if (typeof body !== 'string' || !validBodyOptions.includes(body)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-body" are: [${validBodyOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-flavourIntensity validation
|
||||
const flavourIntensity =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.FlavourIntensity
|
||||
]
|
||||
const validFlavourIntensityOptions: FlavourIntensity[] =
|
||||
Object.values(FlavourIntensity)
|
||||
|
||||
if (
|
||||
typeof flavourIntensity !== 'string' ||
|
||||
!validFlavourIntensityOptions.includes(flavourIntensity)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-flavourIntensity" are: [${validFlavourIntensityOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-palateLength validation
|
||||
const palateLength =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.PalateLength
|
||||
]
|
||||
const validPalateLengthOptions: PalateLength[] =
|
||||
Object.values(PalateLength)
|
||||
|
||||
if (
|
||||
typeof palateLength !== 'string' ||
|
||||
!validPalateLengthOptions.includes(palateLength)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-palateLength" are: [${validPalateLengthOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-reasoning validation
|
||||
const reasoning =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Reasoning
|
||||
]
|
||||
const reasoningKeys = Object.keys(reasoning)
|
||||
const validReasoningKeys = Object.values(ReasoningKey)
|
||||
|
||||
if (!isObject(reasoning)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "textureAndBalance-reasoning" should be an object with the following properties: [${validReasoningKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
if (!compareArrays(reasoningKeys, validReasoningKeys)) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-reasoning" are: [${validReasoningKeys.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-reasoning-balance validation
|
||||
const reasoningBalance =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Reasoning
|
||||
][ReasoningKey.Balance]
|
||||
|
||||
if (typeof reasoningBalance !== 'boolean') {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "textureAndBalance-reasoning-balance" should be a boolean`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-reasoning-concentration validation
|
||||
const reasoningConcentration =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Reasoning
|
||||
][ReasoningKey.Concentration]
|
||||
const validReasoningConcentrationOptions = Object.values(
|
||||
ReasoningConcentration
|
||||
)
|
||||
|
||||
if (
|
||||
typeof reasoningConcentration !== 'string' ||
|
||||
!validReasoningConcentrationOptions.includes(reasoningConcentration)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-reasoning-concentration" are: [${validReasoningConcentrationOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-reasoning-complex validation
|
||||
const reasoningComplex =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Reasoning
|
||||
][ReasoningKey.Complex]
|
||||
|
||||
if (
|
||||
typeof reasoningComplex !== 'boolean' ||
|
||||
!validReasoningConcentrationOptions.includes(reasoningConcentration)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. "textureAndBalance-reasoning-complex" should be a boolean`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-quality validation
|
||||
const quality =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Quality
|
||||
]
|
||||
const validQualityOptions: Quality[] = Object.values(Quality)
|
||||
|
||||
if (
|
||||
typeof quality !== 'string' ||
|
||||
!validQualityOptions.includes(quality)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-quality" are: [${validQualityOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-age validation
|
||||
const textureAndBalanceKeyAge =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.Age
|
||||
]
|
||||
|
||||
if (typeof textureAndBalanceKeyAge !== 'number') {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-reasoning-age" should be a number`
|
||||
)
|
||||
}
|
||||
|
||||
// textureAndBalance-readinessToDrink validation
|
||||
const readinessToDrink =
|
||||
tastingNote[TastingNoteKey.TextureAndBalance][
|
||||
TextureAndBalanceKey.ReadinessToDrink
|
||||
]
|
||||
const validReadinessToDrinkOptions: ReadinessToDrink[] =
|
||||
Object.values(ReadinessToDrink)
|
||||
|
||||
if (
|
||||
typeof readinessToDrink !== 'string' ||
|
||||
!validReadinessToDrinkOptions.includes(readinessToDrink)
|
||||
) {
|
||||
return message(
|
||||
`provided "tastingNote" is not valid. Valid options for "textureAndBalance-readinessToDrink" are: [${validReadinessToDrinkOptions.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
return tastingNote
|
||||
})
|
||||
.required()
|
||||
}).validate(data)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import {
|
||||
SakeDesignation,
|
||||
SakeCharacteristics,
|
||||
SakeCharacteristic,
|
||||
SakeVolume,
|
||||
SakeStarter,
|
||||
RiceVarietal,
|
||||
@ -90,40 +90,36 @@ export const sakeValidation = (data: unknown): Joi.ValidationResult =>
|
||||
return designation
|
||||
})
|
||||
.required(),
|
||||
polishRate: Joi.number()
|
||||
.custom((polishRate, helper) => {
|
||||
// return if no state ancestors
|
||||
if (!helper.state.ancestors) {
|
||||
return polishRate
|
||||
}
|
||||
|
||||
const designation = helper.state.ancestors[0].designation
|
||||
const designationKey = Object.keys(designation)[0]
|
||||
const designationValue: string = Object.values(
|
||||
designation as { [key: string]: string }
|
||||
)[0]
|
||||
const minPolishRate: number = (
|
||||
sakePolishMap as { [key: string]: { [key: string]: SakePolishMin } }
|
||||
)[designationKey][designationValue].min
|
||||
|
||||
if (polishRate < minPolishRate || polishRate > 0.99) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`provide polishRate "${polishRate}" is not valid for "${designationKey} -> ${designationValue}". Valid range is "${minPolishRate} - 0.99"`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
polishRate: Joi.number().custom((polishRate, helper) => {
|
||||
// return if no state ancestors
|
||||
if (!helper.state.ancestors) {
|
||||
return polishRate
|
||||
})
|
||||
.required(),
|
||||
characteristics: Joi.array()
|
||||
.items(Joi.string().valid(...Object.values(SakeCharacteristics)))
|
||||
}
|
||||
|
||||
const designation = helper.state.ancestors[0].designation
|
||||
const designationKey = Object.keys(designation)[0]
|
||||
const designationValue: string = Object.values(
|
||||
designation as { [key: string]: string }
|
||||
)[0]
|
||||
const minPolishRate: number = (
|
||||
sakePolishMap as { [key: string]: { [key: string]: SakePolishMin } }
|
||||
)[designationKey][designationValue].min
|
||||
|
||||
if (polishRate < minPolishRate || polishRate > 0.99) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`provide polishRate "${polishRate}" is not valid for "${designationKey} -> ${designationValue}". Valid range is "${minPolishRate} - 0.99"`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return polishRate
|
||||
}),
|
||||
characteristic: Joi.string()
|
||||
.valid(...Object.values(SakeCharacteristic))
|
||||
.required(),
|
||||
starter: Joi.string().valid(...Object.values(SakeStarter)),
|
||||
yeastStrain: Joi.string()
|
||||
.valid(...Object.values(SakeYeastStrain))
|
||||
.required(),
|
||||
yeastStrain: Joi.string().valid(...Object.values(SakeYeastStrain)),
|
||||
volume: volumeValidation(SakeVolume),
|
||||
alcohol: alcoholValidation,
|
||||
riceVarietal: Joi.array()
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
Ingredient,
|
||||
SpiritType,
|
||||
SpiritVolume,
|
||||
SpiritCharacteristics
|
||||
SpiritCharacteristic
|
||||
} from '../../types'
|
||||
import { isObject, spiritVariantMap, spiritCharacteristicsMap } from '../'
|
||||
import {
|
||||
@ -231,55 +231,53 @@ export const spiritValidation = (data: unknown): Joi.ValidationResult =>
|
||||
}
|
||||
)
|
||||
),
|
||||
characteristics: Joi.array()
|
||||
.items(
|
||||
Joi.string().custom((characteristic, helper) => {
|
||||
if (!Object.values(SpiritCharacteristics).includes(characteristic)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`"${characteristic}" is not a valid characteristic. Valid options are [${Object.values(
|
||||
SpiritCharacteristics
|
||||
)
|
||||
.map((option) => `"${option}"`)
|
||||
.join(', ')}]`
|
||||
characteristic: Joi.string()
|
||||
.custom((characteristic, helper) => {
|
||||
if (!Object.values(SpiritCharacteristic).includes(characteristic)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`"${characteristic}" is not a valid characteristic. Valid options are [${Object.values(
|
||||
SpiritCharacteristic
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const spiritType: SpiritType = helper.state.ancestors[1].type
|
||||
|
||||
const spiritVariant: string | { [key: string]: unknown } =
|
||||
helper.state.ancestors[1].variant
|
||||
|
||||
const spiritVariantName =
|
||||
typeof spiritVariant === 'string'
|
||||
? spiritVariant
|
||||
: Object.keys(spiritVariant)[0]
|
||||
|
||||
const variantsInCharacteristic =
|
||||
spiritCharacteristicsMap[characteristic as SpiritCharacteristics]
|
||||
|
||||
const characteristicsInVariant = Object.keys(
|
||||
spiritCharacteristicsMap
|
||||
).filter((char) =>
|
||||
spiritCharacteristicsMap[char as SpiritCharacteristics].includes(
|
||||
spiritVariantName
|
||||
.map((option) => `"${option}"`)
|
||||
.join(', ')}]`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const spiritType: SpiritType = helper.state.ancestors[1].type
|
||||
|
||||
const spiritVariant: string | { [key: string]: unknown } =
|
||||
helper.state.ancestors[1].variant
|
||||
|
||||
const spiritVariantName =
|
||||
typeof spiritVariant === 'string'
|
||||
? spiritVariant
|
||||
: Object.keys(spiritVariant)[0]
|
||||
|
||||
const variantsInCharacteristic =
|
||||
spiritCharacteristicsMap[characteristic as SpiritCharacteristic]
|
||||
|
||||
const characteristicsInVariant = Object.keys(
|
||||
spiritCharacteristicsMap
|
||||
).filter((char) =>
|
||||
spiritCharacteristicsMap[char as SpiritCharacteristic].includes(
|
||||
spiritVariantName
|
||||
)
|
||||
)
|
||||
|
||||
if (!variantsInCharacteristic.includes(spiritVariantName)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`"${characteristic}" is not a valid characteristic for "${spiritType} -> ${spiritVariantName}". Valid options are [${characteristicsInVariant
|
||||
.map((option) => `"${option}"`)
|
||||
.join(', ')}]`
|
||||
)
|
||||
})
|
||||
}
|
||||
if (!variantsInCharacteristic.includes(spiritVariantName)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`"${characteristic}" is not a valid characteristic for "${spiritType} -> ${spiritVariantName}". Valid options are [${characteristicsInVariant
|
||||
.map((option) => `"${option}"`)
|
||||
.join(', ')}]`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return characteristic
|
||||
})
|
||||
)
|
||||
return characteristic
|
||||
})
|
||||
.required(),
|
||||
ingredients: Joi.array()
|
||||
.items(Joi.string().valid(...Object.values(Ingredient)))
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Joi from 'joi'
|
||||
import { VintageOptions } from '../../types'
|
||||
import { VintageOption } from '../../types'
|
||||
|
||||
export const vintageValidation = Joi.alternatives()
|
||||
.try(
|
||||
Joi.string().valid(...Object.values(VintageOptions)),
|
||||
Joi.string().valid(...Object.values(VintageOption)),
|
||||
Joi.number().min(1700).max(new Date().getFullYear())
|
||||
)
|
||||
.required()
|
||||
|
@ -302,77 +302,75 @@ export const wineValidation = (data: unknown): Joi.ValidationResult =>
|
||||
style: Joi.string()
|
||||
.valid(...Object.values(WineStyle))
|
||||
.required(),
|
||||
characteristics: Joi.array()
|
||||
.items(Joi.string())
|
||||
.custom((value: string[], helper) => {
|
||||
// return if no value
|
||||
if (!value) {
|
||||
return value
|
||||
}
|
||||
// return if no state ancestors
|
||||
if (!helper.state.ancestors) {
|
||||
return value
|
||||
}
|
||||
characteristic: Joi.string().custom((value: string[], helper) => {
|
||||
// return if no value
|
||||
if (!value) {
|
||||
return value
|
||||
}
|
||||
// return if no state ancestors
|
||||
if (!helper.state.ancestors) {
|
||||
return value
|
||||
}
|
||||
|
||||
const wineType: WineType = helper.state.ancestors[0].type
|
||||
const wineType: WineType = helper.state.ancestors[0].type
|
||||
|
||||
// return if no wineType
|
||||
if (!wineType) {
|
||||
return value
|
||||
}
|
||||
// return if no wineType
|
||||
if (!wineType) {
|
||||
return value
|
||||
}
|
||||
|
||||
let options: string[] = []
|
||||
let options: string[] = []
|
||||
|
||||
switch (wineType) {
|
||||
case WineType.White:
|
||||
{
|
||||
options = Object.values(WhiteWineCharacteristic)
|
||||
}
|
||||
switch (wineType) {
|
||||
case WineType.White:
|
||||
{
|
||||
options = Object.values(WhiteWineCharacteristic)
|
||||
}
|
||||
|
||||
break
|
||||
case WineType.Amber:
|
||||
{
|
||||
options = Object.values(AmberWineCharacteristic)
|
||||
}
|
||||
break
|
||||
case WineType.Amber:
|
||||
{
|
||||
options = Object.values(AmberWineCharacteristic)
|
||||
}
|
||||
|
||||
break
|
||||
case WineType.Rose:
|
||||
{
|
||||
options = Object.values(RoseWineCharacteristic)
|
||||
}
|
||||
break
|
||||
case WineType.Rose:
|
||||
{
|
||||
options = Object.values(RoseWineCharacteristic)
|
||||
}
|
||||
|
||||
break
|
||||
case WineType.Red:
|
||||
{
|
||||
options = Object.values(RedWineCharacteristic)
|
||||
}
|
||||
break
|
||||
case WineType.Red:
|
||||
{
|
||||
options = Object.values(RedWineCharacteristic)
|
||||
}
|
||||
|
||||
break
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (!options.length) {
|
||||
if (!options.length) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`no characteristics found for provided type of wine`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
for (const characteristic of value) {
|
||||
if (!options.includes(characteristic)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`no characteristics found for provided type of wine`
|
||||
`"${characteristic}" is not a valid characteristic for "${wineType}" wine. Valid options are [${options.map((option) => `"${option}"`).join(', ')}]`
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const characteristic of value) {
|
||||
if (!options.includes(characteristic)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression(
|
||||
`"${characteristic}" is not a valid characteristic for "${wineType}" wine. Valid options are [${options.map((option) => `"${option}"`).join(', ')}]`
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}),
|
||||
return value
|
||||
}),
|
||||
volume: volumeValidation(WineVolume),
|
||||
alcohol: alcoholValidation,
|
||||
grapeVarietal: Joi.array()
|
||||
|
Loading…
x
Reference in New Issue
Block a user