Merge pull request 'tasting-notes' () from tasting-notes into staging

Reviewed-on: 
This commit is contained in:
Otto 2025-04-17 10:59:51 +00:00
commit d7e008f308
9 changed files with 342 additions and 334 deletions

@ -1,9 +1,9 @@
import express, { Request, Response } from 'express'
import { collections } from '../services/database.service'
import { Review } from '../models'
import { Coffee, Review, Sake, Spirit, Wine } from '../models'
import { reviewValidation, handleReqError, handleReqSuccess } from '../utils'
import Joi from 'joi'
import { DBcollection } from '../types'
import { DBcollection, TastingNoteKey } from '../types'
import { BSON } from 'mongodb'
export const reviewsRouter = express.Router()
@ -49,36 +49,46 @@ reviewsRouter.post('/', async (req: Request, res: Response) => {
const { productId } = review
const _id = new BSON.ObjectId(productId)
const existingWine = await collections[DBcollection.Wines]?.findOne({
let product: Wine | Spirit | Sake | null | undefined = await collections[
DBcollection.Wines
]?.findOne<Wine>({
_id
})
if (!existingWine) {
const existingSake = await collections[DBcollection.Sake]?.findOne({
if (!product) {
product = await collections[DBcollection.Sake]?.findOne<Sake>({
_id
})
if (!existingSake) {
const existingSpirit = await collections[DBcollection.Spirits]?.findOne(
{
_id
}
)
if (!product) {
product = await collections[DBcollection.Spirits]?.findOne<Spirit>({
_id
})
if (!existingSpirit) {
const existingCoffee = await collections[
if (!product) {
const coffee = await collections[
DBcollection.Coffee
]?.findOne({
]?.findOne<Coffee>({
_id
})
if (!existingCoffee) {
if (!coffee) {
throw new Error('product with provided "productId" does not exists')
}
}
}
}
// Add age property to tasting note, if product has vintage property
if (product) {
const { vintage } = product
if (typeof vintage === 'number') {
review.tastingNote[TastingNoteKey.TextureAndBalance].age =
new Date().getFullYear() - vintage
}
}
const result = await collections[DBcollection.Reviews]?.insertOne(review)
handleReqSuccess(res, result, 'review')

@ -38,16 +38,15 @@ import {
TextureAndBalanceKey,
Sweetness,
Concentration,
TanninType,
RipeTannin,
UnripeTannin,
Body,
FlavourIntensity,
PalateLength,
ReasoningConcentration,
Quality,
ReadinessToDrink,
ReasoningKey
ReasoningKey,
TanninString,
TanninObject
} from './review/'
export enum RatingOption {
@ -106,9 +105,7 @@ export interface TastingNote {
[TastingNoteKey.TextureAndBalance]: {
[TextureAndBalanceKey.Sweetness]: Sweetness
[TextureAndBalanceKey.Acidity]: Concentration
[TextureAndBalanceKey.Tannin]: {
[key in Concentration]: { [key in TanninType]: RipeTannin | UnripeTannin }
}
[TextureAndBalanceKey.Tannin]: TanninString | TanninObject
[TextureAndBalanceKey.Alcohol]: Concentration
[TextureAndBalanceKey.Body]: Body
[TextureAndBalanceKey.FlavourIntensity]: FlavourIntensity

@ -4,7 +4,7 @@ export enum WhiteColour {
LemonGreen = 'Lemon-Green',
Lemon = 'Lemon',
Gold = 'Gold',
WhiteBrown = 'White-Brown' // FIXME: duplicate
WhiteBrown = 'White-Brown'
}
// Skin Contact (wine, spirits)
@ -17,7 +17,7 @@ export enum AmberColour {
export enum RoseColour {
Pink = 'Pink',
Salmon = 'Salmon',
RoseOrange = 'Rose-Orange', // FIXME: duplicate
RoseOrange = 'Rose-Orange',
OnionSkin = 'Onion-Skin'
}

@ -17,6 +17,12 @@ export enum PrimaryFlavoursAndAromasKey {
Faults = 'faults'
}
export enum RequiredPrimaryFlavoursAndAromasKey {
Condition = PrimaryFlavoursAndAromasKey.Condition,
Intensity = PrimaryFlavoursAndAromasKey.Intensity,
Age = PrimaryFlavoursAndAromasKey.Age
}
export enum Condition {
Clean = 'Clean',
Unclean = 'Unclean'

@ -26,6 +26,22 @@ export enum Concentration {
High = 'High'
}
export enum TanninString {
Low = 'Low'
}
export interface TanninObject {
[Concentration.Low]: {
[key in TanninType]: RipeTannin | UnripeTannin
}
[Concentration.Medium]: {
[key in TanninType]: RipeTannin | UnripeTannin
}
[Concentration.High]: {
[key in TanninType]: RipeTannin | UnripeTannin
}
}
export enum TanninType {
Ripe = 'Ripe',
Unripe = 'Unripe'

@ -55,10 +55,13 @@ import {
ReasoningKey,
ReasoningConcentration,
Quality,
ReadinessToDrink
ReadinessToDrink,
RequiredPrimaryFlavoursAndAromasKey,
TanninString,
TanninObject
} from '../../types'
import { compareArrays, isObject } from '../utils'
import { producerIdValidation } from './'
import { producerIdValidation, validateStringValue } from './'
export const reviewValidation = (data: unknown): Joi.ValidationResult =>
Joi.object({
@ -103,10 +106,23 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
if (!compareArrays(visualAssessmentKeys, validVisualAssessmentKeys)) {
return message(
`provided "tastingNote" is not valid. "visualAssessment-visualAssessment" object has to include the following keys: [${validVisualAssessmentKeys.join(', ')}]`
`provided "tastingNote" is not valid. "visualAssessment" object has to include the following keys: [${validVisualAssessmentKeys.join(', ')}]`
)
}
const messageWithValidOptions = (
noteKey: TastingNoteKey,
noteSubKey:
| VisualAssessmentKey
| PrimaryFlavoursAndAromasKey
| TextureAndBalanceKey,
options: object,
product?: string
) =>
message(
`provided "tastingNote" is not valid. Valid options for "${[noteKey, noteSubKey].join('-')}"${product ? ` for "${product}"` : ''} are: [${Object.values(options).join(', ')}]`
)
/**
* visualAssessment-clarity validation
*/
@ -115,12 +131,11 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(clarity, ClarityVisualAssessment)) {
return messageWithValidOptions(
TastingNoteKey.VisualAssessment,
VisualAssessmentKey.Clarity,
ClarityVisualAssessment
)
}
@ -132,9 +147,11 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(nature, NatureVisualAssessment)) {
return messageWithValidOptions(
TastingNoteKey.VisualAssessment,
VisualAssessmentKey.Nature,
NatureVisualAssessment
)
}
@ -145,33 +162,20 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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)
const validColorOptions = {
...WhiteColour,
...AmberColour,
...RoseColour,
...RedColour,
...BlueColour,
...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(', ')}]`
if (validateStringValue(colour, validColorOptions)) {
return messageWithValidOptions(
TastingNoteKey.VisualAssessment,
VisualAssessmentKey.Colour,
validColorOptions
)
}
@ -181,11 +185,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(colour, WineColour)) {
return messageWithValidOptions(
TastingNoteKey.VisualAssessment,
VisualAssessmentKey.Colour,
WineColour,
productType
)
}
}
@ -194,11 +199,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(colour, SakeColour)) {
return messageWithValidOptions(
TastingNoteKey.VisualAssessment,
VisualAssessmentKey.Colour,
SakeColour,
productType
)
}
}
@ -207,11 +213,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(colour, SpiritColour)) {
return messageWithValidOptions(
TastingNoteKey.VisualAssessment,
VisualAssessmentKey.Colour,
SpiritColour,
productType
)
}
}
@ -231,16 +238,28 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
const validPrimaryFlavoursAndAromasKeys = Object.values(
PrimaryFlavoursAndAromasKey
)
const requiredPrimaryFlavoursAndAromasKeys = Object.values(
RequiredPrimaryFlavoursAndAromasKey
)
if (
!compareArrays(
primaryFlavoursAndAromasKeys,
validPrimaryFlavoursAndAromasKeys
)
) {
return message(
`provided "tastingNote" is not valid. "visualAssessment-primaryFlavoursAndAromas" object has to include the following keys: [${validPrimaryFlavoursAndAromasKeys.join(', ')}]`
)
for (const requiredKey of requiredPrimaryFlavoursAndAromasKeys) {
if (!primaryFlavoursAndAromasKeys.includes(requiredKey)) {
return message(
`provided "tastingNote" is not valid. "visualAssessment-primaryFlavoursAndAromas" object has to include the following keys: [${requiredPrimaryFlavoursAndAromasKeys.join(', ')}]`
)
}
}
for (const key of primaryFlavoursAndAromasKeys) {
if (
!validPrimaryFlavoursAndAromasKeys.includes(
key as PrimaryFlavoursAndAromasKey
)
) {
return message(
`provided "tastingNote" is not valid. "${key}" is not a valid key for "visualAssessment-primaryFlavoursAndAromas" object, valid keys are: [${validPrimaryFlavoursAndAromasKeys.join(', ')}]`
)
}
}
// primaryFlavoursAndAromas-condition validation
@ -248,14 +267,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(condition, Condition)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Condition,
Condition
)
}
@ -264,14 +281,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(intensity, Intensity)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Intensity,
Intensity
)
}
@ -280,11 +295,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(age, Age)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Age,
Age
)
}
@ -293,29 +309,22 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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)
]
const validFruitOptions = {
...CitrusFruit,
...AppleFruit,
...StoneFruit,
...RedFruit,
...BlackFruit,
...ChocolateFruit,
...TropicalFruit,
...MelonFruit
}
if (typeof fruit !== 'string' || !validFruitOptions.includes(fruit)) {
return message(
`provided "tastingNote" is not valid. Valid options for "visualAssessment-fruit" are: [${validFruitOptions.join(', ')}]`
if (fruit && validateStringValue(fruit, validFruitOptions)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Fruit,
validFruitOptions
)
}
@ -324,14 +333,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (floral && validateStringValue(floral, Floral)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Floral,
Floral
)
}
@ -340,14 +347,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (vegetal && validateStringValue(vegetal, Vegetal)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Vegetal,
Vegetal
)
}
@ -356,11 +361,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (earth && validateStringValue(earth, Earth)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Earth,
Earth
)
}
@ -369,14 +375,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (microbial && validateStringValue(microbial, Microbial)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Microbial,
Microbial
)
}
@ -385,11 +389,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (oak && validateStringValue(oak, Oak)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Oak,
Oak
)
}
@ -398,14 +403,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (chocolate && validateStringValue(chocolate, Chocolate)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Chocolate,
Chocolate
)
}
@ -414,14 +417,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (oxidation && validateStringValue(oxidation, Oxidation)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Oxidation,
Oxidation
)
}
@ -430,11 +431,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (umami && validateStringValue(umami, Umami)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Umami,
Umami
)
}
@ -443,14 +445,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (balsamic && validateStringValue(balsamic, Balsamic)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Balsamic,
Balsamic
)
}
@ -459,11 +459,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (grain && validateStringValue(grain, Grain)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Grain,
Grain
)
}
@ -472,11 +473,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (dairy && validateStringValue(dairy, Dairy)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Dairy,
Dairy
)
}
@ -485,24 +487,18 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
tastingNote[TastingNoteKey.PrimaryFlavoursAndAromas][
PrimaryFlavoursAndAromasKey.Faults
]
const validFaultsOptions: (
| Anisoles
| Brettanomyces
| VolatileAcidity
| Reduction
)[] = [
...Object.values(Anisoles),
...Object.values(Brettanomyces),
...Object.values(VolatileAcidity),
...Object.values(Reduction)
]
const validFaultsOptions = {
...Anisoles,
...Brettanomyces,
...VolatileAcidity,
...Reduction
}
if (
typeof faults !== 'string' ||
!validFaultsOptions.includes(faults)
) {
return message(
`provided "tastingNote" is not valid. Valid options for "visualAssessment-faults" are: [${validFaultsOptions.join(', ')}]`
if (faults && validateStringValue(faults, validFaultsOptions)) {
return messageWithValidOptions(
TastingNoteKey.PrimaryFlavoursAndAromas,
PrimaryFlavoursAndAromasKey.Faults,
validFaultsOptions
)
}
@ -512,7 +508,9 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
const textureAndBalanceKeys = Object.keys(
tastingNote[TastingNoteKey.TextureAndBalance]
)
const validTextureAndBalanceKeys = Object.values(TextureAndBalanceKey)
const validTextureAndBalanceKeys = Object.values(
TextureAndBalanceKey
).filter((key) => key !== TextureAndBalanceKey.Age)
if (!compareArrays(textureAndBalanceKeys, validTextureAndBalanceKeys)) {
return message(
@ -525,14 +523,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(sweetness, Sweetness)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.Sweetness,
Sweetness
)
}
@ -541,15 +537,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(acidity, Concentration)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.Acidity,
Concentration
)
}
@ -562,59 +555,67 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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 (typeof tannin === 'string') {
if (validateStringValue(tannin, TanninString)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.Tannin,
TanninString
)
}
} else {
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(', ')}]`
)
}
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)
// textureAndBalance-tannin-type validation
const tanninType = (
tastingNote[TastingNoteKey.TextureAndBalance][
TextureAndBalanceKey.Tannin
] as TanninObject
)[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 (!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(', ')}]`
)
}
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)
]
// textureAndBalance-tannin-value validation
const tanninValue = (
tastingNote[TastingNoteKey.TextureAndBalance][
TextureAndBalanceKey.Tannin
] as TanninObject
)[tanninKey][tanninTypeKey]
const validTanninValueOptions = { ...RipeTannin, ...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(', ')}]`
)
if (validateStringValue(tanninValue, validTanninValueOptions)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.Tannin,
validTanninValueOptions
)
}
}
// textureAndBalance-alcohol validation
@ -622,15 +623,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(alcohol, Concentration)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.Alcohol,
Concentration
)
}
@ -639,11 +637,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(body, Body)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.Body,
Body
)
}
@ -652,15 +651,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(flavourIntensity, FlavourIntensity)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.FlavourIntensity,
FlavourIntensity
)
}
@ -669,15 +665,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(palateLength, PalateLength)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.PalateLength,
PalateLength
)
}
@ -751,26 +744,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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`
if (validateStringValue(quality, Quality)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.Quality,
Quality
)
}
@ -779,15 +758,12 @@ export const reviewValidation = (data: unknown): Joi.ValidationResult =>
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(', ')}]`
if (validateStringValue(readinessToDrink, ReadinessToDrink)) {
return messageWithValidOptions(
TastingNoteKey.TextureAndBalance,
TextureAndBalanceKey.ReadinessToDrink,
ReadinessToDrink
)
}

@ -245,10 +245,10 @@ export const spiritValidation = (data: unknown): Joi.ValidationResult =>
})
}
const spiritType: SpiritType = helper.state.ancestors[1].type
const spiritType: SpiritType = helper.state.ancestors[0].type
const spiritVariant: string | { [key: string]: unknown } =
helper.state.ancestors[1].variant
helper.state.ancestors[0].variant
const spiritVariantName =
typeof spiritVariant === 'string'

@ -36,3 +36,8 @@ export const RRPcurrencyValidation = Joi.string().length(3).required()
export const descriptionValidation = Joi.string().required()
export const urlValidation = Joi.string()
export const imageValidation = Joi.string()
export const validateStringValue = (
value: unknown,
validOptions: { [key: string]: string }
) => typeof value !== 'string' || !Object.values(validOptions).includes(value)

@ -302,7 +302,7 @@ export const wineValidation = (data: unknown): Joi.ValidationResult =>
style: Joi.string()
.valid(...Object.values(WineStyle))
.required(),
characteristic: Joi.string().custom((value: string[], helper) => {
characteristic: Joi.string().custom((value, helper) => {
// return if no value
if (!value) {
return value
@ -359,14 +359,12 @@ export const wineValidation = (data: unknown): Joi.ValidationResult =>
})
}
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(', ')}]`
)
})
}
if (!options.includes(value)) {
return helper.message({
custom: Joi.expression(
`"${value}" is not a valid characteristic for "${wineType}" wine. Valid options are [${options.map((option) => `"${option}"`).join(', ')}]`
)
})
}
return value